npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@superapp_men/device-storage

v1.0.0

Published

Key-value device storage for SuperApp partner applications via iframe/Capacitor

Readme

@superapp_men/device-storage

Version: 1.0.0 License: MIT Author: SuperApp Team

Package for partner applications (running inside the SuperApp iframe or Capacitor WebView) to perform key-value storage operations on the device. The SuperApp handles actual storage, auto-namespaces keys per partner (e.g. bewize:myKey), and returns results via postMessage.


Table of Contents


Features

  • Simple async APIget, set, remove, has, keys, clear with Promise-based returns.
  • Client-side validation before calling the SuperApp: key format, value size, and partner code are validated locally.
  • Auto-namespacing — The SuperApp prefixes all keys with the partner code (e.g. bewize:myKey), preventing collisions between partner apps.
  • Dual transport support — works in both iframe (postMessage to parent) and Capacitor WebView environments.
  • Type-safe — Full TypeScript types for all operations, payloads, and responses.

Installation

npm install @superapp_men/device-storage

Architecture Overview

Partner App (iframe / Capacitor WebView)
  |
  +-- DeviceStorageClient          -> validates inputs, sends via bridge
        |
        +-- 1. Client-side validation (key, value, partnerCode)
        |     +-- If invalid -> throws Error immediately
        |
        +-- 2. If valid -> sends postMessage to SuperApp
              |
              SuperApp (host)
              +-- Listens for "storage:request"
              +-- Namespaces key with partnerCode (e.g. "bewize:myKey")
              +-- Performs storage operation (localStorage, SQLite, etc.)
              +-- Returns "storage:response" with result

Client API

DeviceStorageClient

import { DeviceStorageClient } from "@superapp_men/device-storage";

const storage = new DeviceStorageClient({
  partnerCode: "bewize",
  timeout: 10000,
  debug: true,
});

Constructor config (DeviceStorageClientConfig):

| Option | Type | Required | Default | Description | |--------|------|----------|---------|-------------| | partnerCode | string | Yes | — | Partner application code. Must be one of: "bewize", "ltm", "cantoo", "ekinox", "math-scan" (case-insensitive). Validated at construction time. | | timeout | number | No | 10000 | Request timeout in milliseconds. | | debug | boolean | No | false | Enable debug console logging. |

Methods

| Method | Returns | Description | |--------|---------|-------------| | get(key) | Promise<string \| null> | Retrieve a value by key. Returns null if the key does not exist. | | set(key, value) | Promise<void> | Store a key-value pair. | | remove(key) | Promise<void> | Delete a key. | | has(key) | Promise<boolean> | Check if a key exists. | | keys() | Promise<string[]> | List all keys for this partner (unprefixed). | | clear() | Promise<void> | Clear all keys for this partner. | | setDebug(enabled) | void | Toggle debug logging at runtime. | | destroy() | void | Clean up listeners and pending timeouts. Call when done. |

Error handling: All methods throw an Error if:

  • Client-side validation fails (invalid key, value, or partnerCode).
  • The SuperApp returns a non-2xx status code.
  • The request times out.
  • No target window is found (not in iframe or Capacitor).

Data Dictionary

StorageRequestPayload (per action)

Each request includes an action field that determines the payload shape:

| Action | Payload Fields | Description | |--------|---------------|-------------| | "get" | { action: "get", key: string } | Retrieve a value. | | "set" | { action: "set", key: string, value: string } | Store a value. | | "remove" | { action: "remove", key: string } | Delete a key. | | "has" | { action: "has", key: string } | Check existence. | | "keys" | { action: "keys" } | List all keys. | | "clear" | { action: "clear" } | Clear all keys. |

The partnerCode is automatically appended to the payload by the bridge before sending.

StorageResponseBody (per action)

| Action | Response Body | Description | |--------|--------------|-------------| | "get" | { value: string \| null } | The stored value, or null if key doesn't exist. | | "set" | { success: true } | Confirmation of successful write. | | "remove" | { success: true } | Confirmation of successful delete. | | "has" | { exists: boolean } | Whether the key exists. | | "keys" | { keys: string[] } | Array of unprefixed keys for this partner. | | "clear" | { success: true } | Confirmation of successful clear. |

StorageErrorBody

Returned by the SuperApp when an operation fails (non-2xx status).

| Field | Type | Description | |-------|------|-------------| | error | string | Error message. | | detail | string? | Optional detailed error information. |


Validation Rules

All validation runs client-side before sending to the SuperApp.

Partner Code Validation

| Rule | Error Message | |------|---------------| | Required, non-empty string | "partnerCode is required." | | Must be one of: bewize, ltm, cantoo, ekinox, math-scan (case-insensitive) | "partnerCode must be one of: bewize, ltm, cantoo, ekinox, math-scan." |

Key Validation

| Rule | Error Message | |------|---------------| | Required, non-empty string | "key is required and must be a non-empty string." | | Max 256 characters | "key must not exceed 256 characters." | | No invalid characters (< > : " / \ \| ? * and control characters) | "key contains invalid characters. Avoid: < > : \" / \\ \| ? * and control characters." |

Value Validation (for set() only)

| Rule | Error Message | |------|---------------| | Required, must be a string | "value is required for set()." / "value must be a string." | | Max 1 MB (1,048,576 bytes UTF-8) | "value must not exceed 1048576 bytes (1 MB)." |


Usage Examples

Basic CRUD Operations

import { DeviceStorageClient } from "@superapp_men/device-storage";

const storage = new DeviceStorageClient({
  partnerCode: "bewize",
  timeout: 10000,
  debug: false,
});

// Store a value
await storage.set("user-preference", "dark-mode");

// Retrieve a value
const value = await storage.get("user-preference");
console.log(value); // "dark-mode"

// Check if a key exists
const exists = await storage.has("user-preference");
console.log(exists); // true

// Delete a key
await storage.remove("user-preference");

// Clean up when done
storage.destroy();

Listing and Clearing Keys

// Store multiple values
await storage.set("theme", "dark");
await storage.set("language", "fr");
await storage.set("font-size", "16");

// List all keys for this partner
const allKeys = await storage.keys();
console.log(allKeys); // ["theme", "language", "font-size"]

// Clear all keys for this partner
await storage.clear();

const afterClear = await storage.keys();
console.log(afterClear); // []

Storing Complex Data (JSON)

const userData = { name: "Alice", score: 95, level: 3 };
await storage.set("user-data", JSON.stringify(userData));

const raw = await storage.get("user-data");
if (raw !== null) {
  const parsed = JSON.parse(raw);
  console.log(parsed.name); // "Alice"
}

Error Handling

try {
  await storage.get("my-key");
} catch (error) {
  if (error instanceof Error) {
    console.error("Storage error:", error.message);
    // Possible messages:
    // - "key is required and must be a non-empty string."  (validation)
    // - "Storage get failed"                                (SuperApp error)
    // - "Device storage request timeout after 10000ms"      (timeout)
    // - "Cannot find target window (not in iframe or Capacitor?)" (environment)
  }
}

Communication Protocol

Message Types

| Enum | Value | Direction | Description | |------|-------|-----------|-------------| | StorageMessageType.REQUEST | "storage:request" | Partner -> SuperApp | Partner sends a storage operation. | | StorageMessageType.RESPONSE | "storage:response" | SuperApp -> Partner | SuperApp returns the result. |

Request Message (StorageRequestMessage)

| Field | Type | Description | |-------|------|-------------| | type | "storage:request" | Message type constant. | | requestId | string | Unique ID for matching the response. | | payload | StorageRequestPayload & { partnerCode: string } | The action payload with partner code. | | timestamp | number | Date.now() at time of sending. |

Example request (set):

{
  "type": "storage:request",
  "requestId": "1709123456789-abc123def",
  "payload": {
    "action": "set",
    "key": "theme",
    "value": "dark",
    "partnerCode": "bewize"
  },
  "timestamp": 1709123456789
}

Response Message (StorageResponseMessage)

| Field | Type | Description | |-------|------|-------------| | type | "storage:response" | Message type constant. | | requestId | string | Matches the request's requestId. | | statusCode | number | HTTP-style status code (200 = success, 400 = error, 500 = internal). | | body | StorageResponseBody \| StorageErrorBody | Result or error body. | | timestamp | number | Date.now() at time of response. |

Example response (set success):

{
  "type": "storage:response",
  "requestId": "1709123456789-abc123def",
  "statusCode": 200,
  "body": { "success": true },
  "timestamp": 1709123456790
}

Example response (get):

{
  "type": "storage:response",
  "requestId": "1709123456789-abc123def",
  "statusCode": 200,
  "body": { "value": "dark" },
  "timestamp": 1709123456790
}

SuperApp Integration

The SuperApp must:

  1. Listen for messages with type: "storage:request".
  2. Read payload.partnerCode and payload.action.
  3. Namespace the key: ${partnerCode}:${key} (e.g. bewize:theme).
  4. Perform the storage operation (localStorage, Capacitor Preferences, SQLite, etc.).
  5. Send response via postMessage with type: "storage:response", matching requestId.

Key namespacing: For keys(), return only keys matching the partner prefix and strip the prefix. For clear(), delete only keys matching the partner prefix.

SuperApp imports:

import type {
  StorageRequestMessage,
  StorageResponseMessage,
  StorageRequestPayload,
} from "@superapp_men/device-storage/superapp";
import { StorageMessageType } from "@superapp_men/device-storage/superapp";

Example SuperApp handler:

window.addEventListener("message", async (event) => {
  const msg = event.data;
  if (msg?.type !== StorageMessageType.REQUEST) return;

  const { requestId, payload, timestamp } = msg as StorageRequestMessage;
  const prefix = `${payload.partnerCode}:`;

  let statusCode = 200;
  let body: unknown;

  try {
    switch (payload.action) {
      case "get":
        body = { value: localStorage.getItem(prefix + payload.key) };
        break;
      case "set":
        localStorage.setItem(prefix + payload.key, payload.value);
        body = { success: true };
        break;
      case "remove":
        localStorage.removeItem(prefix + payload.key);
        body = { success: true };
        break;
      case "has":
        body = { exists: localStorage.getItem(prefix + payload.key) !== null };
        break;
      case "keys": {
        const allKeys: string[] = [];
        for (let i = 0; i < localStorage.length; i++) {
          const k = localStorage.key(i);
          if (k?.startsWith(prefix)) allKeys.push(k.slice(prefix.length));
        }
        body = { keys: allKeys };
        break;
      }
      case "clear":
        for (let i = localStorage.length - 1; i >= 0; i--) {
          const k = localStorage.key(i);
          if (k?.startsWith(prefix)) localStorage.removeItem(k);
        }
        body = { success: true };
        break;
    }
  } catch (err) {
    statusCode = 500;
    body = { error: "Internal storage error", detail: String(err) };
  }

  event.source?.postMessage(
    { type: StorageMessageType.RESPONSE, requestId, statusCode, body, timestamp: Date.now() },
    { targetOrigin: event.origin },
  );
});

Exported Members

Main entry (@superapp_men/device-storage)

| Export | Kind | Description | |--------|------|-------------| | DeviceStorageClient | Class | Main client for storage operations. | | DeviceStorageClientConfig | Type | Constructor config interface. | | StorageMessageType | Enum | REQUEST, RESPONSE message type constants. | | StorageAction | Type | "get" \| "set" \| "remove" \| "has" \| "keys" \| "clear". | | StorageRequestPayload | Type | Union of all action payloads. | | StorageGetPayload | Type | { action: "get", key }. | | StorageSetPayload | Type | { action: "set", key, value }. | | StorageRemovePayload | Type | { action: "remove", key }. | | StorageHasPayload | Type | { action: "has", key }. | | StorageKeysPayload | Type | { action: "keys" }. | | StorageClearPayload | Type | { action: "clear" }. | | StorageResponseBody | Type | Union of all response bodies. | | StorageGetResponseBody | Type | { value: string \| null }. | | StorageSetResponseBody | Type | { success: true }. | | StorageRemoveResponseBody | Type | { success: true }. | | StorageHasResponseBody | Type | { exists: boolean }. | | StorageKeysResponseBody | Type | { keys: string[] }. | | StorageClearResponseBody | Type | { success: true }. | | StorageErrorBody | Type | { error: string, detail?: string }. | | StorageRequestMessage | Type | postMessage request structure. | | StorageResponseMessage | Type | postMessage response structure. | | StorageApiResult | Type | Generic result type from bridge. | | validatePartnerCode | Function | Validate partner code. | | validateKey | Function | Validate storage key. | | validateValue | Function | Validate storage value. | | ValidationResult | Type | { valid: boolean, errors: ValidationError[] }. | | ValidationError | Type | { key: string, message: string }. |

SuperApp entry (@superapp_men/device-storage/superapp)

| Export | Kind | Description | |--------|------|-------------| | StorageMessageType | Enum | Message type constants. | | All payload/response types | Types | Same type exports as main entry (types only, no client/bridge/validator). |


Constants

| Constant | Value | Used In | |----------|-------|---------| | MAX_KEY_LENGTH | 256 | Key max length validation. | | MAX_VALUE_SIZE | 1,048,576 (1 MB) | Value max byte size validation. | | VALID_PARTNER_CODES | ["bewize", "ltm", "cantoo", "ekinox", "math-scan"] | Allowed partner code values (case-insensitive). | | Default timeout | 10,000 ms | Client request timeout. |