@superapp_men/device-storage
v1.0.0
Published
Key-value device storage for SuperApp partner applications via iframe/Capacitor
Maintainers
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
- Installation
- Architecture Overview
- Client API
- Data Dictionary
- Validation Rules
- Usage Examples
- Communication Protocol
- SuperApp Integration
- Exported Members
- Constants
Features
- Simple async API —
get,set,remove,has,keys,clearwith 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-storageArchitecture 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 resultClient 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:
- Listen for messages with
type: "storage:request". - Read
payload.partnerCodeandpayload.action. - Namespace the key:
${partnerCode}:${key}(e.g.bewize:theme). - Perform the storage operation (localStorage, Capacitor Preferences, SQLite, etc.).
- Send response via postMessage with
type: "storage:response", matchingrequestId.
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. |
