@whatalo/plugin-sdk 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge/index.cjs +190 -4
- package/dist/bridge/index.cjs.map +1 -1
- package/dist/bridge/index.d.cts +109 -2
- package/dist/bridge/index.d.ts +109 -2
- package/dist/bridge/index.mjs +185 -3
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/client/index.cjs +5 -0
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.mjs +5 -0
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.cjs +193 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +189 -2
- package/dist/index.mjs.map +1 -1
- package/dist/server/verify-session-token.cjs +103 -0
- package/dist/server/verify-session-token.cjs.map +1 -0
- package/dist/server/verify-session-token.d.cts +1 -0
- package/dist/server/verify-session-token.d.ts +1 -0
- package/dist/server/verify-session-token.mjs +76 -0
- package/dist/server/verify-session-token.mjs.map +1 -0
- package/dist/session-token.cjs +134 -0
- package/dist/session-token.cjs.map +1 -0
- package/dist/session-token.d.cts +77 -0
- package/dist/session-token.d.ts +77 -0
- package/dist/session-token.mjs +108 -0
- package/dist/session-token.mjs.map +1 -0
- package/package.json +26 -30
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client/errors.ts","../src/client/whatalo-client.ts","../src/webhooks/verify.ts","../src/manifest/validate.ts","../src/bridge/use-whatalo-context.ts","../src/bridge/use-whatalo-action.ts","../src/bridge/types.ts","../src/bridge/use-app-bridge.ts","../src/index.ts"],"sourcesContent":["/**\n * Base error for all SDK errors.\n * Contains the HTTP status code and a machine-readable error code.\n */\nexport class WhataloAPIError extends Error {\n readonly statusCode: number;\n readonly code: string;\n readonly requestId?: string;\n\n constructor(\n message: string,\n statusCode: number,\n code: string,\n requestId?: string\n ) {\n super(message);\n this.name = \"WhataloAPIError\";\n this.statusCode = statusCode;\n this.code = code;\n this.requestId = requestId;\n }\n}\n\n/** 401 — Invalid or missing API key */\nexport class AuthenticationError extends WhataloAPIError {\n constructor(\n message = \"Invalid or missing API key\",\n requestId?: string\n ) {\n super(message, 401, \"authentication_error\", requestId);\n this.name = \"AuthenticationError\";\n }\n}\n\n/** 403 — API key lacks required permission scope */\nexport class AuthorizationError extends WhataloAPIError {\n readonly requiredScope: string;\n\n constructor(\n message: string,\n requiredScope: string,\n requestId?: string\n ) {\n super(message, 403, \"authorization_error\", requestId);\n this.name = \"AuthorizationError\";\n this.requiredScope = requiredScope;\n }\n}\n\n/**\n * 403 — Installation does not have the required permission scope.\n *\n * Thrown when the merchant has not granted a scope that the current\n * API call requires. This is distinct from AuthorizationError:\n * - AuthorizationError = generic 403 (could be any auth reason)\n * - InsufficientScopeError = specific scope missing from granted_scopes\n *\n * The `requiredScope` field tells the developer exactly which scope to add\n * to their plugin manifest so they can request merchant re-consent.\n *\n * Example:\n * try {\n * await client.customers.list()\n * } catch (err) {\n * if (err instanceof InsufficientScopeError) {\n * console.error(`Add \"${err.requiredScope}\" to your manifest.`)\n * }\n * }\n */\nexport class InsufficientScopeError extends WhataloAPIError {\n /** The exact scope string missing from the installation's granted_scopes */\n readonly requiredScope: string;\n /** The scopes currently granted to this installation */\n readonly grantedScopes: string[];\n\n constructor(\n message: string,\n requiredScope: string,\n grantedScopes: string[],\n requestId?: string\n ) {\n super(message, 403, \"insufficient_scope\", requestId);\n this.name = \"InsufficientScopeError\";\n this.requiredScope = requiredScope;\n this.grantedScopes = grantedScopes;\n }\n}\n\n/** 404 — Resource not found */\nexport class NotFoundError extends WhataloAPIError {\n readonly resourceType: string;\n readonly resourceId: string;\n\n constructor(\n resourceType: string,\n resourceId: string,\n requestId?: string\n ) {\n super(\n `${resourceType} '${resourceId}' not found`,\n 404,\n \"not_found\",\n requestId\n );\n this.name = \"NotFoundError\";\n this.resourceType = resourceType;\n this.resourceId = resourceId;\n }\n}\n\n/** 422 — Request body validation failed */\nexport class ValidationError extends WhataloAPIError {\n readonly fieldErrors: Array<{ field: string; message: string }>;\n\n constructor(\n fieldErrors: Array<{ field: string; message: string }>,\n requestId?: string\n ) {\n const message = `Validation failed: ${fieldErrors\n .map((e) => `${e.field} — ${e.message}`)\n .join(\", \")}`;\n super(message, 422, \"validation_error\", requestId);\n this.name = \"ValidationError\";\n this.fieldErrors = fieldErrors;\n }\n}\n\n/** 429 — Rate limit exceeded */\nexport class RateLimitError extends WhataloAPIError {\n /** Seconds until the rate limit resets */\n readonly retryAfter: number;\n\n constructor(retryAfter: number, requestId?: string) {\n super(\n `Rate limit exceeded. Retry after ${retryAfter} seconds.`,\n 429,\n \"rate_limit_exceeded\",\n requestId\n );\n this.name = \"RateLimitError\";\n this.retryAfter = retryAfter;\n }\n}\n\n/** 500 — Server-side error */\nexport class InternalError extends WhataloAPIError {\n constructor(\n message = \"An internal error occurred\",\n requestId?: string\n ) {\n super(message, 500, \"internal_error\", requestId);\n this.name = \"InternalError\";\n }\n}\n","import type {\n WhataloClientOptions,\n RateLimitInfo,\n PaginatedResponse,\n SingleResponse,\n Product,\n Order,\n Customer,\n Category,\n Discount,\n Store,\n InventoryItem,\n Webhook,\n ListProductsParams,\n CreateProductParams,\n UpdateProductParams,\n ListOrdersParams,\n ListCustomersParams,\n ListCategoriesParams,\n CreateCategoryParams,\n UpdateCategoryParams,\n ListDiscountsParams,\n CreateDiscountParams,\n UpdateDiscountParams,\n UpsertWebhookParams,\n UpdateWebhookParams,\n AdjustInventoryParams,\n} from \"./types.js\";\nimport {\n WhataloAPIError,\n AuthenticationError,\n AuthorizationError,\n InsufficientScopeError,\n RateLimitError,\n InternalError,\n NotFoundError,\n ValidationError,\n} from \"./errors.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.whatalo.com/v1\";\nconst DEFAULT_TIMEOUT = 30_000;\n\n/**\n * Official TypeScript client for the Whatalo REST API.\n * Zero runtime dependencies — uses native fetch.\n */\nexport class WhataloClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n private readonly maxRetries: number;\n private readonly fetchFn: typeof globalThis.fetch;\n private readonly onRequest?: (url: string, init: RequestInit) => void;\n private readonly onResponse?: (response: Response, durationMs: number) => void;\n\n /** Rate limit info from the most recent API response */\n rateLimit: RateLimitInfo = { limit: 0, remaining: 0, reset: 0 };\n\n /** Product resource methods */\n readonly products: ProductResource;\n /** Order resource methods */\n readonly orders: OrderResource;\n /** Customer resource methods */\n readonly customers: CustomerResource;\n /** Category resource methods */\n readonly categories: CategoryResource;\n /** Discount resource methods */\n readonly discounts: DiscountResource;\n /** Store resource methods */\n readonly store: StoreResource;\n /** Inventory resource methods */\n readonly inventory: InventoryResource;\n /** Webhook resource methods */\n readonly webhooks: WebhookResource;\n\n constructor(options: WhataloClientOptions) {\n this.apiKey = options.apiKey;\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT;\n this.maxRetries = Math.min(options.retries ?? 0, 3);\n this.fetchFn = options.fetch ?? globalThis.fetch;\n this.onRequest = options.onRequest;\n this.onResponse = options.onResponse;\n\n this.products = new ProductResource(this);\n this.orders = new OrderResource(this);\n this.customers = new CustomerResource(this);\n this.categories = new CategoryResource(this);\n this.discounts = new DiscountResource(this);\n this.store = new StoreResource(this);\n this.inventory = new InventoryResource(this);\n this.webhooks = new WebhookResource(this);\n }\n\n /**\n * Internal request method shared by all resource namespaces.\n * Handles headers, timeout, error parsing, and rate limit extraction.\n */\n async request<T>(\n method: string,\n path: string,\n options?: {\n body?: unknown;\n params?: Record<string, string | number | boolean | undefined>;\n }\n ): Promise<T> {\n let url = `${this.baseUrl}${path}`;\n\n // Add query params\n if (options?.params) {\n const searchParams = new URLSearchParams();\n for (const [key, value] of Object.entries(options.params)) {\n if (value !== undefined) {\n searchParams.set(key, String(value));\n }\n }\n const qs = searchParams.toString();\n if (qs) url += `?${qs}`;\n }\n\n const headers: Record<string, string> = {\n \"X-API-Key\": this.apiKey,\n \"Content-Type\": \"application/json\",\n };\n\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n const requestInit: RequestInit = {\n method,\n headers,\n body: options?.body ? JSON.stringify(options.body) : undefined,\n signal: controller.signal,\n };\n const startedAt = Date.now();\n this.onRequest?.(url, requestInit);\n\n try {\n const response = await this.fetchFn(url, requestInit);\n\n clearTimeout(timeoutId);\n this.onResponse?.(response, Date.now() - startedAt);\n\n // Extract rate limit headers\n this.rateLimit = {\n limit: Number(response.headers.get(\"X-RateLimit-Limit\") ?? 0),\n remaining: Number(\n response.headers.get(\"X-RateLimit-Remaining\") ?? 0\n ),\n reset: Number(response.headers.get(\"X-RateLimit-Reset\") ?? 0),\n };\n\n const requestId =\n response.headers.get(\"X-Request-Id\") ?? undefined;\n\n if (response.ok) {\n return (await response.json()) as T;\n }\n\n // Handle error responses\n const errorBody = await response.json().catch(() => ({\n error: { code: \"unknown\", message: response.statusText },\n }));\n const errData = (errorBody as { error?: { code?: string; message?: string; details?: Array<{ field: string; message: string }> } }).error;\n\n switch (response.status) {\n case 401:\n throw new AuthenticationError(errData?.message, requestId);\n case 403:\n // Distinguish between a specific scope denial and a generic 403.\n // insufficient_scope means the merchant never granted this permission.\n if (errData?.code === \"insufficient_scope\") {\n const details = errorBody as {\n error?: { required?: string; granted?: string[] };\n };\n throw new InsufficientScopeError(\n errData.message ?? \"Insufficient scope\",\n details.error?.required ?? \"\",\n details.error?.granted ?? [],\n requestId\n );\n }\n throw new AuthorizationError(\n errData?.message ?? \"Forbidden\",\n errData?.code ?? \"unknown_scope\",\n requestId\n );\n case 404:\n throw new NotFoundError(\"Resource\", path, requestId);\n case 422:\n throw new ValidationError(\n errData?.details ?? [\n { field: \"unknown\", message: errData?.message ?? \"Validation failed\" },\n ],\n requestId\n );\n case 429: {\n const retryAfter = Number(\n response.headers.get(\"Retry-After\") ?? 60\n );\n throw new RateLimitError(retryAfter, requestId);\n }\n default:\n if (response.status >= 500 && attempt < this.maxRetries) {\n // Retry on 5xx\n lastError = new InternalError(errData?.message, requestId);\n await this.sleep(Math.pow(2, attempt) * 1000);\n continue;\n }\n throw new WhataloAPIError(\n errData?.message ?? \"API error\",\n response.status,\n errData?.code ?? \"unknown\",\n requestId\n );\n }\n } catch (err) {\n clearTimeout(timeoutId);\n if (err instanceof WhataloAPIError) throw err;\n if ((err as Error).name === \"AbortError\") {\n throw new WhataloAPIError(\n \"Request timed out\",\n 408,\n \"timeout\"\n );\n }\n throw err;\n }\n }\n\n throw lastError ?? new InternalError(\"Request failed after retries\");\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\n/** Products API namespace */\nclass ProductResource {\n constructor(private readonly client: WhataloClient) {}\n\n async list(\n params?: ListProductsParams\n ): Promise<PaginatedResponse<Product>> {\n return this.client.request(\"GET\", \"/products\", {\n params: params as Record<string, string | number | boolean | undefined>,\n });\n }\n\n async get(id: string): Promise<SingleResponse<Product>> {\n return this.client.request(\"GET\", `/products/${id}`);\n }\n\n async create(\n data: CreateProductParams\n ): Promise<SingleResponse<Product>> {\n return this.client.request(\"POST\", \"/products\", { body: data });\n }\n\n async update(\n id: string,\n data: UpdateProductParams\n ): Promise<SingleResponse<Product>> {\n return this.client.request(\"PATCH\", `/products/${id}`, { body: data });\n }\n\n async delete(id: string): Promise<SingleResponse<{ deleted: boolean }>> {\n return this.client.request(\"DELETE\", `/products/${id}`);\n }\n\n async count(\n status?: \"active\" | \"inactive\" | \"all\"\n ): Promise<SingleResponse<{ count: number }>> {\n return this.client.request(\"GET\", \"/products/count\", {\n params: status ? { status } : undefined,\n });\n }\n}\n\n/** Orders API namespace */\nclass OrderResource {\n constructor(private readonly client: WhataloClient) {}\n\n async list(\n params?: ListOrdersParams\n ): Promise<PaginatedResponse<Order>> {\n return this.client.request(\"GET\", \"/orders\", {\n params: params as Record<string, string | number | boolean | undefined>,\n });\n }\n\n async get(id: string): Promise<SingleResponse<Order>> {\n return this.client.request(\"GET\", `/orders/${id}`);\n }\n\n async updateStatus(\n id: string,\n data: { status: string }\n ): Promise<SingleResponse<Order>> {\n return this.client.request(\"PATCH\", `/orders/${id}`, { body: data });\n }\n\n async count(\n status?: string\n ): Promise<SingleResponse<{ count: number }>> {\n return this.client.request(\"GET\", \"/orders/count\", {\n params: status ? { status } : undefined,\n });\n }\n}\n\n/** Customers API namespace */\nclass CustomerResource {\n constructor(private readonly client: WhataloClient) {}\n\n async list(params?: ListCustomersParams): Promise<PaginatedResponse<Customer>> {\n return this.client.request(\"GET\", \"/customers\", {\n params: params as Record<string, string | number | boolean | undefined>,\n });\n }\n\n async get(id: string): Promise<SingleResponse<Customer>> {\n return this.client.request(\"GET\", `/customers/${id}`);\n }\n}\n\n/** Categories API namespace */\nclass CategoryResource {\n constructor(private readonly client: WhataloClient) {}\n\n async list(\n params?: ListCategoriesParams\n ): Promise<PaginatedResponse<Category>> {\n return this.client.request(\"GET\", \"/categories\", {\n params: params as Record<string, string | number | boolean | undefined>,\n });\n }\n\n async get(id: string): Promise<SingleResponse<Category>> {\n return this.client.request(\"GET\", `/categories/${id}`);\n }\n\n async create(\n data: CreateCategoryParams\n ): Promise<SingleResponse<Category>> {\n return this.client.request(\"POST\", \"/categories\", { body: data });\n }\n\n async update(\n id: string,\n data: UpdateCategoryParams\n ): Promise<SingleResponse<Category>> {\n return this.client.request(\"PATCH\", `/categories/${id}`, { body: data });\n }\n\n async delete(id: string): Promise<SingleResponse<{ deleted: boolean }>> {\n return this.client.request(\"DELETE\", `/categories/${id}`);\n }\n\n async count(): Promise<SingleResponse<{ count: number }>> {\n return this.client.request(\"GET\", \"/categories/count\");\n }\n}\n\n/** Discounts API namespace */\nclass DiscountResource {\n constructor(private readonly client: WhataloClient) {}\n\n async list(\n params?: ListDiscountsParams\n ): Promise<PaginatedResponse<Discount>> {\n return this.client.request(\"GET\", \"/discounts\", {\n params: params as Record<string, string | number | boolean | undefined>,\n });\n }\n\n async get(id: string): Promise<SingleResponse<Discount>> {\n return this.client.request(\"GET\", `/discounts/${id}`);\n }\n\n async create(\n data: CreateDiscountParams\n ): Promise<SingleResponse<Discount>> {\n return this.client.request(\"POST\", \"/discounts\", { body: data });\n }\n\n async update(\n id: string,\n data: UpdateDiscountParams\n ): Promise<SingleResponse<Discount>> {\n return this.client.request(\"PATCH\", `/discounts/${id}`, { body: data });\n }\n\n async delete(id: string): Promise<SingleResponse<{ deleted: boolean }>> {\n return this.client.request(\"DELETE\", `/discounts/${id}`);\n }\n}\n\n/** Store API namespace */\nclass StoreResource {\n constructor(private readonly client: WhataloClient) {}\n\n async get(): Promise<SingleResponse<Store>> {\n return this.client.request(\"GET\", \"/store\");\n }\n}\n\n/** Inventory API namespace */\nclass InventoryResource {\n constructor(private readonly client: WhataloClient) {}\n\n async get(productId: string): Promise<SingleResponse<InventoryItem>> {\n return this.client.request(\"GET\", `/products/${productId}/inventory`);\n }\n\n async adjust(\n productId: string,\n data: AdjustInventoryParams\n ): Promise<SingleResponse<InventoryItem>> {\n return this.client.request(\"PATCH\", `/products/${productId}/inventory`, {\n body: data,\n });\n }\n}\n\n/** Webhooks API namespace */\nclass WebhookResource {\n constructor(private readonly client: WhataloClient) {}\n\n async list(): Promise<PaginatedResponse<Webhook>> {\n return this.client.request(\"GET\", \"/webhooks\");\n }\n\n async create(data: UpsertWebhookParams): Promise<SingleResponse<Webhook>> {\n return this.client.request(\"POST\", \"/webhooks\", { body: data });\n }\n\n async update(\n id: string,\n data: UpdateWebhookParams\n ): Promise<SingleResponse<Webhook>> {\n return this.client.request(\"PATCH\", `/webhooks/${id}`, { body: data });\n }\n\n async delete(id: string): Promise<SingleResponse<{ deleted: boolean }>> {\n return this.client.request(\"DELETE\", `/webhooks/${id}`);\n }\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\nexport interface VerifyWebhookParams {\n /** Raw request body as a string (NOT parsed JSON) */\n payload: string;\n /** Value of the X-Webhook-Signature header */\n signature: string;\n /** App's webhook secret (provided during app installation) */\n secret: string;\n}\n\n/**\n * Verify webhook signature using HMAC-SHA256.\n * Uses timing-safe comparison to prevent timing attacks.\n */\nexport function verifyWebhook({\n payload,\n signature,\n secret,\n}: VerifyWebhookParams): boolean {\n if (!payload || !signature || !secret) return false;\n\n const expected = createHmac(\"sha256\", secret)\n .update(payload, \"utf8\")\n .digest(\"hex\");\n\n if (expected.length !== signature.length) return false;\n\n try {\n return timingSafeEqual(\n Buffer.from(expected, \"hex\"),\n Buffer.from(signature, \"hex\")\n );\n } catch {\n return false;\n }\n}\n","import type { AppManifest } from \"./types.js\";\n\n/** Error thrown when manifest validation fails */\nexport class ManifestValidationError extends Error {\n readonly errors: Array<{ field: string; message: string }>;\n\n constructor(errors: Array<{ field: string; message: string }>) {\n const message = `Invalid app manifest: ${errors\n .map((e) => `${e.field}: ${e.message}`)\n .join(\"; \")}`;\n super(message);\n this.name = \"ManifestValidationError\";\n this.errors = errors;\n }\n}\n\nconst ID_REGEX = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;\nconst SEMVER_REGEX = /^\\d+\\.\\d+\\.\\d+$/;\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\nconst VALID_PERMISSIONS = new Set([\n \"read:products\",\n \"write:products\",\n \"read:orders\",\n \"write:orders\",\n \"read:customers\",\n \"write:customers\",\n \"read:inventory\",\n \"write:inventory\",\n \"read:store\",\n \"write:store\",\n \"read:webhooks\",\n \"write:webhooks\",\n \"read:discounts\",\n \"write:discounts\",\n \"read:analytics\",\n]);\n\nconst VALID_CATEGORIES = new Set([\n \"analytics\",\n \"marketing\",\n \"shipping\",\n \"payments\",\n \"payment\",\n \"inventory\",\n \"communication\",\n \"productivity\",\n \"accounting\",\n \"developer\",\n \"other\",\n]);\n\n/**\n * Validate and return a typed AppManifest.\n * Throws ManifestValidationError with detailed field-level errors.\n */\nexport function defineApp(manifest: AppManifest): AppManifest {\n const errors: Array<{ field: string; message: string }> = [];\n\n // id\n if (!manifest.id || manifest.id.length < 3 || manifest.id.length > 50) {\n errors.push({ field: \"id\", message: \"Must be 3-50 characters\" });\n } else if (!ID_REGEX.test(manifest.id)) {\n errors.push({\n field: \"id\",\n message: \"Must be lowercase alphanumeric with hyphens\",\n });\n }\n\n // name\n if (!manifest.name || manifest.name.length < 3 || manifest.name.length > 50) {\n errors.push({ field: \"name\", message: \"Must be 3-50 characters\" });\n }\n\n // description\n if (\n !manifest.description ||\n manifest.description.length < 10 ||\n manifest.description.length > 200\n ) {\n errors.push({\n field: \"description\",\n message: \"Must be 10-200 characters\",\n });\n }\n\n // version\n if (!manifest.version || !SEMVER_REGEX.test(manifest.version)) {\n errors.push({\n field: \"version\",\n message: \"Must be valid semver (e.g., 1.0.0)\",\n });\n }\n\n // author\n if (!manifest.author?.name) {\n errors.push({ field: \"author.name\", message: \"Required\" });\n }\n if (!manifest.author?.email || !EMAIL_REGEX.test(manifest.author.email)) {\n errors.push({\n field: \"author.email\",\n message: \"Must be a valid email address\",\n });\n }\n\n // permissions\n if (!manifest.permissions || manifest.permissions.length === 0) {\n errors.push({\n field: \"permissions\",\n message: \"At least one permission is required\",\n });\n } else {\n for (const perm of manifest.permissions) {\n if (!VALID_PERMISSIONS.has(perm)) {\n errors.push({\n field: \"permissions\",\n message: `Invalid permission: ${perm}`,\n });\n }\n }\n }\n\n // appUrl\n if (!manifest.appUrl) {\n errors.push({ field: \"appUrl\", message: \"Required\" });\n }\n\n // pricing\n if (manifest.pricing !== \"free\" && manifest.pricing !== \"paid\") {\n errors.push({\n field: \"pricing\",\n message: 'Must be \"free\" or \"paid\"',\n });\n }\n\n // category\n if (!VALID_CATEGORIES.has(manifest.category)) {\n errors.push({\n field: \"category\",\n message: `Invalid category: ${manifest.category}`,\n });\n }\n\n if (errors.length > 0) {\n throw new ManifestValidationError(errors);\n }\n\n return manifest;\n}\n","import { useState, useEffect, useCallback } from \"react\";\nimport type { WhataloContext, ContextMessage } from \"./types.js\";\nimport { attachInitListener, waitForParentOrigin } from \"./use-whatalo-action.js\";\n\nexport interface UseWhataloContextReturn extends WhataloContext {\n /** Whether the plugin has received context from the admin host */\n isReady: boolean;\n}\n\nconst DEFAULT_CONTEXT: WhataloContext = {\n storeId: \"\",\n storeName: \"\",\n user: { id: \"\", name: \"\", email: \"\", role: \"owner\" },\n appId: \"\",\n currentPage: \"\",\n locale: \"es\",\n theme: \"light\",\n initialHeight: 200,\n};\n\n/**\n * Listens for context messages from the admin host and signals readiness.\n * Automatically applies the theme attribute to the document root.\n */\nexport function useWhataloContext(): UseWhataloContextReturn {\n const [context, setContext] = useState<WhataloContext>(DEFAULT_CONTEXT);\n const [isReady, setIsReady] = useState(false);\n\n const handleMessage = useCallback((event: MessageEvent) => {\n if (!event.data || typeof event.data !== \"object\") return;\n const msg = event.data as ContextMessage & { context?: WhataloContext };\n if (msg.type !== \"whatalo:context\") return;\n\n const payload = msg.data ?? msg.context;\n if (!payload || typeof payload.storeId !== \"string\") return;\n\n setContext(payload);\n setIsReady(true);\n\n if (payload.theme) {\n document.documentElement.setAttribute(\"data-theme\", payload.theme);\n }\n }, []);\n\n useEffect(() => {\n // Ensure the whatalo:init listener is attached before doing anything else.\n // This is idempotent — safe to call multiple times.\n attachInitListener();\n\n window.addEventListener(\"message\", handleMessage);\n\n // Send the ready signal to the admin host. We wait for parentOrigin from\n // the whatalo:init handshake before sending so we never broadcast to \"*\".\n // The admin sends whatalo:init on iframe onLoad, which fires before the\n // plugin's React tree hydrates — so the wait is typically sub-millisecond.\n waitForParentOrigin().then((origin) => {\n window.parent.postMessage(\n {\n type: \"whatalo:action\",\n actionId: crypto.randomUUID(),\n action: \"ready\",\n payload: {},\n },\n origin,\n );\n });\n\n return () => window.removeEventListener(\"message\", handleMessage);\n }, [handleMessage]);\n\n return { ...context, isReady };\n}\n","import { useCallback, useRef, useEffect } from \"react\";\nimport type {\n BridgeAction,\n ActionAck,\n ToastPayload,\n ModalPayload,\n BillingOperation,\n BillingPayload,\n BillingPlanResponse,\n BillingSubscriptionResponse,\n} from \"./types.js\";\nimport { BILLING_OPERATION } from \"./types.js\";\n\nconst ACK_TIMEOUT_MS = 5_000;\n\n// ---------------------------------------------------------------------------\n// Module-level origin store (singleton shared across all hook instances)\n//\n// This module owns the handshake state. Both useWhataloAction and\n// useWhataloContext import from here so they target the same parentOrigin\n// once the admin sends the whatalo:init handshake.\n// ---------------------------------------------------------------------------\n\n/** Origin of the admin host, received via whatalo:init handshake. */\nlet parentOrigin: string | null = null;\n\n/** Callbacks waiting for parentOrigin to be resolved. */\nconst originResolvers: Array<(origin: string) => void> = [];\n\n/**\n * Returns a Promise that resolves with the parent origin.\n * If the handshake already completed, resolves immediately.\n */\nexport function waitForParentOrigin(): Promise<string> {\n if (parentOrigin !== null) {\n return Promise.resolve(parentOrigin);\n }\n return new Promise((resolve) => {\n originResolvers.push(resolve);\n });\n}\n\n/**\n * Initializes the origin listener exactly once (idempotent).\n * Listens for the whatalo:init message from the admin host and stores\n * the origin so all subsequent postMessage calls can target it explicitly.\n */\nlet initListenerAttached = false;\n\nexport function attachInitListener(): void {\n if (initListenerAttached) return;\n initListenerAttached = true;\n\n window.addEventListener(\"message\", (event: MessageEvent) => {\n // Only accept the init message from the direct parent window\n if (event.source !== window.parent) return;\n if (!event.data || typeof event.data !== \"object\") return;\n if (event.data.type !== \"whatalo:init\") return;\n\n const origin = event.data.origin;\n if (typeof origin !== \"string\" || !origin) return;\n\n // Store origin and flush any queued resolvers\n parentOrigin = origin;\n for (const resolve of originResolvers) {\n resolve(origin);\n }\n originResolvers.length = 0;\n });\n}\n\n/** Billing namespace returned by useWhataloAction. */\nexport interface BillingActions {\n /** Fetch all active pricing plans for this plugin. */\n getPlans(): Promise<BillingPlanResponse[]>;\n /** Fetch the store's current subscription to this plugin, or null if none. */\n getSubscription(): Promise<BillingSubscriptionResponse | null>;\n /**\n * Request a subscription to the given plan.\n * The host will redirect the admin to an approval page.\n */\n requestSubscription(planSlug: string): Promise<void>;\n /** Initiate a cancellation for the current subscription. */\n cancelSubscription(): Promise<void>;\n /** Reactivate a subscription that was scheduled for cancellation at period end. */\n reactivateSubscription(): Promise<void>;\n /** Switch the current subscription to a different plan with proration. */\n switchPlan(newPlanSlug: string): Promise<void>;\n}\n\nexport interface UseWhataloActionReturn {\n /** Send a raw bridge action to the admin host */\n sendAction: (\n action: BridgeAction,\n payload: Record<string, unknown>,\n ) => Promise<ActionAck>;\n /** Display a toast notification in the admin */\n showToast: (options: ToastPayload) => Promise<ActionAck>;\n /** Navigate the admin to a path */\n navigate: (path: string) => Promise<ActionAck>;\n /** Open a modal in the admin */\n openModal: (options: Omit<ModalPayload, \"operation\">) => Promise<ActionAck>;\n /** Close the currently open modal */\n closeModal: () => Promise<ActionAck>;\n /** Request an iframe resize */\n resize: (height: number) => Promise<ActionAck>;\n /** Billing actions — query plans, manage subscriptions. */\n billing: BillingActions;\n}\n\n/**\n * Sends actions to the admin host via postMessage and waits for acknowledgements.\n * Each action has a unique ID and a timeout to prevent hanging promises.\n *\n * All postMessage calls target parentOrigin (set via whatalo:init handshake)\n * instead of \"*\" to prevent message interception by malicious embedders.\n * If the handshake has not completed yet, the message is queued and flushed\n * once the origin is received.\n */\nexport function useWhataloAction(): UseWhataloActionReturn {\n const pendingAcks = useRef(\n new Map<string, (ack: ActionAck) => void>(),\n );\n\n useEffect(() => {\n // Ensure the init listener is running whenever this hook mounts\n attachInitListener();\n\n const handleMessage = (event: MessageEvent) => {\n if (!event.data || typeof event.data !== \"object\") return;\n const msg = event.data as ActionAck;\n if (msg.type !== \"whatalo:ack\") return;\n\n const resolve = pendingAcks.current.get(msg.actionId);\n if (resolve) {\n resolve(msg);\n pendingAcks.current.delete(msg.actionId);\n }\n };\n\n window.addEventListener(\"message\", handleMessage);\n return () => window.removeEventListener(\"message\", handleMessage);\n }, []);\n\n const sendAction = useCallback(\n (\n action: BridgeAction,\n payload: Record<string, unknown>,\n ): Promise<ActionAck> => {\n return new Promise((resolve) => {\n const actionId = crypto.randomUUID();\n\n const timeout = setTimeout(() => {\n pendingAcks.current.delete(actionId);\n resolve({\n type: \"whatalo:ack\",\n actionId,\n success: false,\n error: \"timeout\",\n });\n }, ACK_TIMEOUT_MS);\n\n pendingAcks.current.set(actionId, (ack) => {\n clearTimeout(timeout);\n resolve(ack);\n });\n\n // Wait for the parent origin from the whatalo:init handshake before\n // sending. If the handshake already completed this resolves synchronously.\n waitForParentOrigin().then((origin) => {\n window.parent.postMessage(\n { type: \"whatalo:action\", actionId, action, payload },\n origin,\n );\n });\n });\n },\n [],\n );\n\n const showToast = useCallback(\n (options: ToastPayload) =>\n sendAction(\"toast\", options as unknown as Record<string, unknown>),\n [sendAction],\n );\n\n const navigate = useCallback(\n (path: string) => sendAction(\"navigate\", { path }),\n [sendAction],\n );\n\n const openModal = useCallback(\n (options: Omit<ModalPayload, \"operation\">) =>\n sendAction(\"modal\", {\n operation: \"open\",\n ...options,\n } as unknown as Record<string, unknown>),\n [sendAction],\n );\n\n const closeModal = useCallback(\n () => sendAction(\"modal\", { operation: \"close\" }),\n [sendAction],\n );\n\n const resize = useCallback(\n (height: number) => sendAction(\"resize\", { height }),\n [sendAction],\n );\n\n // ---------------------------------------------------------------------------\n // Billing helpers\n //\n // Each billing method sends a \"billing\" action with a typed operation field.\n // The host resolves the operation against the store's app_plans /\n // app_subscriptions records and returns data in the ack payload.\n // ---------------------------------------------------------------------------\n\n /**\n * Extracts typed data from a billing ack payload.\n * Throws on failure so callers can use try/catch for error handling.\n */\n const sendBillingAction = useCallback(\n (operation: BillingOperation, extra?: Omit<BillingPayload, \"operation\">) =>\n sendAction(\"billing\", {\n operation,\n ...extra,\n } as unknown as Record<string, unknown>),\n [sendAction],\n );\n\n const billingGetPlans = useCallback(async (): Promise<BillingPlanResponse[]> => {\n const ack = await sendBillingAction(BILLING_OPERATION.GET_PLANS);\n if (!ack.success) {\n throw new Error(ack.error ?? \"billing.getPlans failed\");\n }\n // The host embeds the plans array in the ack under a `data` key\n return (ack as ActionAck & { data: BillingPlanResponse[] }).data ?? [];\n }, [sendBillingAction]);\n\n const billingGetSubscription = useCallback(\n async (): Promise<BillingSubscriptionResponse | null> => {\n const ack = await sendBillingAction(BILLING_OPERATION.GET_SUBSCRIPTION);\n if (!ack.success) {\n throw new Error(ack.error ?? \"billing.getSubscription failed\");\n }\n return (\n (ack as ActionAck & { data: BillingSubscriptionResponse | null }).data ??\n null\n );\n },\n [sendBillingAction],\n );\n\n const billingRequestSubscription = useCallback(\n async (planSlug: string): Promise<void> => {\n const ack = await sendBillingAction(BILLING_OPERATION.REQUEST_SUBSCRIPTION, {\n planSlug,\n });\n if (!ack.success) {\n throw new Error(ack.error ?? \"billing.requestSubscription failed\");\n }\n },\n [sendBillingAction],\n );\n\n const billingCancelSubscription = useCallback(async (): Promise<void> => {\n const ack = await sendBillingAction(BILLING_OPERATION.CANCEL_SUBSCRIPTION);\n if (!ack.success) {\n throw new Error(ack.error ?? \"billing.cancelSubscription failed\");\n }\n }, [sendBillingAction]);\n\n const billingReactivateSubscription = useCallback(async (): Promise<void> => {\n const ack = await sendBillingAction(BILLING_OPERATION.REACTIVATE_SUBSCRIPTION);\n if (!ack.success) {\n throw new Error(ack.error ?? \"billing.reactivateSubscription failed\");\n }\n }, [sendBillingAction]);\n\n const billingSwitchPlan = useCallback(\n async (newPlanSlug: string): Promise<void> => {\n const ack = await sendBillingAction(BILLING_OPERATION.SWITCH_PLAN, {\n planSlug: newPlanSlug,\n });\n if (!ack.success) {\n throw new Error(ack.error ?? \"billing.switchPlan failed\");\n }\n },\n [sendBillingAction],\n );\n\n const billing: BillingActions = {\n getPlans: billingGetPlans,\n getSubscription: billingGetSubscription,\n requestSubscription: billingRequestSubscription,\n cancelSubscription: billingCancelSubscription,\n reactivateSubscription: billingReactivateSubscription,\n switchPlan: billingSwitchPlan,\n };\n\n return { sendAction, showToast, navigate, openModal, closeModal, resize, billing };\n}\n","/** Context data sent from the admin host to a plugin iframe */\nexport interface WhataloContext {\n /** Store UUID */\n storeId: string;\n /** Store display name */\n storeName: string;\n /** Current order ID (set when plugin opens from order detail) */\n orderId?: string;\n /** Current order status */\n orderStatus?: string;\n /** Current product ID (set when plugin opens from product detail) */\n productId?: string;\n /** Authenticated user info */\n user: WhataloUser;\n /** Plugin ID from the manifest */\n appId: string;\n /** Current admin page path (e.g., \"/integrations/my-plugin/settings\") */\n currentPage: string;\n /** Locale code (e.g., \"es\", \"en\") */\n locale: string;\n /** Active color scheme */\n theme: \"light\" | \"dark\";\n /** Suggested initial iframe height in pixels */\n initialHeight: number;\n}\n\n/**\n * Authenticated user information provided to the plugin.\n *\n * `role` reflects the user's actual role in the store, not a hardcoded value.\n * Current Whatalo stores are single-owner, so the role will always be \"owner\"\n * for now. The union type is forward-compatible for future multi-member support.\n */\nexport interface WhataloUser {\n id: string;\n name: string;\n email: string;\n role: \"owner\" | \"admin\" | \"editor\" | \"staff\" | \"viewer\";\n}\n\n/** Available bridge actions the plugin can dispatch */\nexport type BridgeAction =\n | \"navigate\"\n | \"toast\"\n | \"modal\"\n | \"resize\"\n | \"ready\"\n | \"billing\";\n\n// ---------------------------------------------------------------------------\n// Billing types\n// ---------------------------------------------------------------------------\n\n/** All billing operations supported by the bridge. createUsageRecord is reserved for v2. */\nexport const BILLING_OPERATION = {\n GET_PLANS: \"getPlans\",\n GET_SUBSCRIPTION: \"getSubscription\",\n REQUEST_SUBSCRIPTION: \"requestSubscription\",\n CANCEL_SUBSCRIPTION: \"cancelSubscription\",\n REACTIVATE_SUBSCRIPTION: \"reactivateSubscription\",\n SWITCH_PLAN: \"switchPlan\",\n CREATE_USAGE_RECORD: \"createUsageRecord\",\n} as const;\n\nexport type BillingOperation =\n (typeof BILLING_OPERATION)[keyof typeof BILLING_OPERATION];\n\n/** Payload sent from the plugin to the host for billing actions. */\nexport interface BillingPayload {\n operation: BillingOperation;\n /** Required for requestSubscription */\n planSlug?: string;\n /** Human-readable reason for the subscription request */\n description?: string;\n /** Reserved for createUsageRecord (v2) */\n amount?: number;\n /** Idempotency key for safe retries (v2) */\n idempotencyKey?: string;\n}\n\n/** A single pricing plan returned by getPlans. */\nexport interface BillingPlanResponse {\n slug: string;\n name: string;\n price: number;\n currency: string;\n /** \"monthly\" | \"annual\" | null (null for one-time or usage plans) */\n interval: string | null;\n trialDays: number;\n features: string[];\n isPopular: boolean;\n}\n\n/** The store's current subscription to this plugin, returned by getSubscription. */\nexport interface BillingSubscriptionResponse {\n planSlug: string;\n planName: string;\n /** \"pending\" | \"trialing\" | \"active\" | \"past_due\" | \"canceled\" */\n status: string;\n trialEndsAt: string | null;\n currentPeriodEnd: string | null;\n /** Whether the subscription is scheduled for cancellation at period end */\n cancelAtPeriodEnd: boolean;\n /** ISO timestamp of when the cancellation was initiated, or null */\n canceledAt: string | null;\n}\n\n/** Message sent from admin host to plugin with context data */\nexport interface ContextMessage {\n type: \"whatalo:context\";\n data: WhataloContext;\n}\n\n/** Action message sent from plugin to admin host */\nexport interface ActionMessage {\n type: \"whatalo:action\";\n actionId: string;\n action: BridgeAction;\n payload: Record<string, unknown>;\n}\n\n/** Acknowledgement message sent from admin host back to plugin */\nexport interface ActionAck {\n type: \"whatalo:ack\";\n actionId: string;\n success: boolean;\n error?: string;\n}\n\n/** Payload for toast notifications */\nexport interface ToastPayload {\n title: string;\n description?: string;\n variant?: \"success\" | \"error\" | \"warning\" | \"info\";\n}\n\n/** Payload for admin navigation */\nexport interface NavigatePayload {\n path: string;\n}\n\n/** Payload for modal operations */\nexport interface ModalPayload {\n operation: \"open\" | \"close\";\n title?: string;\n url?: string;\n width?: number;\n height?: number;\n}\n\n/** Payload for iframe resize */\nexport interface ResizePayload {\n height: number;\n}\n","import { useWhataloContext } from \"./use-whatalo-context.js\";\nimport { useWhataloAction } from \"./use-whatalo-action.js\";\nimport type {\n ActionAck,\n ToastPayload,\n ModalPayload,\n WhataloUser,\n BillingPlanResponse,\n BillingSubscriptionResponse,\n} from \"./types.js\";\nimport type { BillingActions } from \"./use-whatalo-action.js\";\n\nexport interface AppBridgeToast {\n /** Show a toast notification in the admin host */\n show: (\n title: string,\n options?: Omit<ToastPayload, \"title\">,\n ) => Promise<ActionAck>;\n}\n\nexport interface AppBridgeModal {\n /** Open a modal in the admin host */\n open: (options: Omit<ModalPayload, \"operation\">) => Promise<ActionAck>;\n /** Close the currently open modal */\n close: () => Promise<ActionAck>;\n}\n\n/** Re-export for consumers who import directly from use-app-bridge */\nexport type { BillingActions, BillingPlanResponse, BillingSubscriptionResponse };\n\nexport interface AppBridge {\n /** Store UUID */\n storeId: string;\n /** Store display name */\n storeName: string;\n /** Locale code (e.g., \"es\", \"en\") */\n locale: string;\n /** Active color scheme */\n theme: \"light\" | \"dark\";\n /** Authenticated user info */\n user: WhataloUser;\n /** Plugin ID from the manifest */\n appId: string;\n /** Whether the plugin has received context from the admin host */\n isReady: boolean;\n /** Toast notification helpers */\n toast: AppBridgeToast;\n /** Navigate the admin to a path */\n navigate: (path: string) => Promise<ActionAck>;\n /** Modal helpers */\n modal: AppBridgeModal;\n /** Request an iframe resize */\n resize: (height: number) => Promise<ActionAck>;\n /** Billing actions — query plans and manage subscriptions */\n billing: BillingActions;\n}\n\n/**\n * Unified bridge hook that combines context and actions into a single API.\n * This is the primary hook plugin developers should use.\n *\n * @example\n * ```tsx\n * const bridge = useAppBridge();\n * bridge.toast.show(\"Saved!\", { variant: \"success\" });\n * bridge.navigate(\"/orders\");\n * ```\n */\nexport function useAppBridge(): AppBridge {\n const ctx = useWhataloContext();\n const actions = useWhataloAction();\n\n return {\n storeId: ctx.storeId,\n storeName: ctx.storeName,\n locale: ctx.locale,\n theme: ctx.theme,\n user: ctx.user,\n appId: ctx.appId,\n isReady: ctx.isReady,\n toast: {\n show: (title, options) =>\n actions.showToast({ title, ...options }),\n },\n navigate: actions.navigate,\n modal: {\n open: actions.openModal,\n close: actions.closeModal,\n },\n resize: actions.resize,\n billing: actions.billing,\n };\n}\n","// @whatalo/plugin-sdk — Official TypeScript SDK for building Whatalo plugins\nexport const SDK_VERSION = \"0.0.1\";\n\n// Client\nexport { WhataloClient } from \"./client/whatalo-client.js\";\nexport type {\n WhataloClientOptions,\n RateLimitInfo,\n PaginatedResponse,\n SingleResponse,\n Product,\n Order,\n Customer,\n Category,\n Discount,\n Store,\n InventoryItem,\n Webhook,\n ListProductsParams,\n CreateProductParams,\n UpdateProductParams,\n ListOrdersParams,\n ListCustomersParams,\n ListCategoriesParams,\n CreateCategoryParams,\n UpdateCategoryParams,\n ListDiscountsParams,\n CreateDiscountParams,\n UpdateDiscountParams,\n UpsertWebhookParams,\n UpdateWebhookParams,\n AdjustInventoryParams,\n} from \"./client/types.js\";\n\n// Error classes\nexport {\n WhataloAPIError,\n AuthenticationError,\n AuthorizationError,\n NotFoundError,\n ValidationError,\n RateLimitError,\n InternalError,\n} from \"./client/errors.js\";\n\n// Webhook verification\nexport { verifyWebhook } from \"./webhooks/verify.js\";\nexport type {\n VerifyWebhookParams,\n WebhookEvent,\n WebhookEventData,\n WebhookPayload,\n} from \"./webhooks/index.js\";\n\n// Manifest\nexport { defineApp, ManifestValidationError } from \"./manifest/index.js\";\nexport type {\n AppManifest,\n AppPermission,\n AppCategory,\n} from \"./manifest/index.js\";\n\n// Bridge (React hooks for plugin ↔ admin postMessage communication)\nexport { useAppBridge, useWhataloAction, useWhataloContext } from \"./bridge/index.js\";\nexport { BILLING_OPERATION } from \"./bridge/index.js\";\nexport type {\n AppBridge,\n AppBridgeToast,\n AppBridgeModal,\n BillingActions,\n UseWhataloActionReturn,\n UseWhataloContextReturn,\n WhataloContext,\n WhataloUser,\n BridgeAction,\n ActionAck,\n ToastPayload,\n NavigatePayload,\n ModalPayload,\n ResizePayload,\n BillingOperation,\n BillingPayload,\n BillingPlanResponse,\n BillingSubscriptionResponse,\n} from \"./bridge/index.js\";\n"],"mappings":"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","names":["useEffect","useCallback","useCallback","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/client/errors.ts","../src/client/whatalo-client.ts","../src/webhooks/verify.ts","../src/manifest/validate.ts","../src/bridge/use-whatalo-context.ts","../src/bridge/use-whatalo-action.ts","../src/bridge/types.ts","../src/bridge/use-app-bridge.ts","../src/bridge/session-token.ts","../src/bridge/use-whatalo-data.ts","../src/index.ts"],"sourcesContent":["/**\n * Base error for all SDK errors.\n * Contains the HTTP status code and a machine-readable error code.\n */\nexport class WhataloAPIError extends Error {\n readonly statusCode: number;\n readonly code: string;\n readonly requestId?: string;\n\n constructor(\n message: string,\n statusCode: number,\n code: string,\n requestId?: string\n ) {\n super(message);\n this.name = \"WhataloAPIError\";\n this.statusCode = statusCode;\n this.code = code;\n this.requestId = requestId;\n }\n}\n\n/** 401 — Invalid or missing API key */\nexport class AuthenticationError extends WhataloAPIError {\n constructor(\n message = \"Invalid or missing API key\",\n requestId?: string\n ) {\n super(message, 401, \"authentication_error\", requestId);\n this.name = \"AuthenticationError\";\n }\n}\n\n/** 403 — API key lacks required permission scope */\nexport class AuthorizationError extends WhataloAPIError {\n readonly requiredScope: string;\n\n constructor(\n message: string,\n requiredScope: string,\n requestId?: string\n ) {\n super(message, 403, \"authorization_error\", requestId);\n this.name = \"AuthorizationError\";\n this.requiredScope = requiredScope;\n }\n}\n\n/**\n * 403 — Installation does not have the required permission scope.\n *\n * Thrown when the merchant has not granted a scope that the current\n * API call requires. This is distinct from AuthorizationError:\n * - AuthorizationError = generic 403 (could be any auth reason)\n * - InsufficientScopeError = specific scope missing from granted_scopes\n *\n * The `requiredScope` field tells the developer exactly which scope to add\n * to their plugin manifest so they can request merchant re-consent.\n *\n * Example:\n * try {\n * await client.customers.list()\n * } catch (err) {\n * if (err instanceof InsufficientScopeError) {\n * console.error(`Add \"${err.requiredScope}\" to your manifest.`)\n * }\n * }\n */\nexport class InsufficientScopeError extends WhataloAPIError {\n /** The exact scope string missing from the installation's granted_scopes */\n readonly requiredScope: string;\n /** The scopes currently granted to this installation */\n readonly grantedScopes: string[];\n\n constructor(\n message: string,\n requiredScope: string,\n grantedScopes: string[],\n requestId?: string\n ) {\n super(message, 403, \"insufficient_scope\", requestId);\n this.name = \"InsufficientScopeError\";\n this.requiredScope = requiredScope;\n this.grantedScopes = grantedScopes;\n }\n}\n\n/** 404 — Resource not found */\nexport class NotFoundError extends WhataloAPIError {\n readonly resourceType: string;\n readonly resourceId: string;\n\n constructor(\n resourceType: string,\n resourceId: string,\n requestId?: string\n ) {\n super(\n `${resourceType} '${resourceId}' not found`,\n 404,\n \"not_found\",\n requestId\n );\n this.name = \"NotFoundError\";\n this.resourceType = resourceType;\n this.resourceId = resourceId;\n }\n}\n\n/** 422 — Request body validation failed */\nexport class ValidationError extends WhataloAPIError {\n readonly fieldErrors: Array<{ field: string; message: string }>;\n\n constructor(\n fieldErrors: Array<{ field: string; message: string }>,\n requestId?: string\n ) {\n const message = `Validation failed: ${fieldErrors\n .map((e) => `${e.field} — ${e.message}`)\n .join(\", \")}`;\n super(message, 422, \"validation_error\", requestId);\n this.name = \"ValidationError\";\n this.fieldErrors = fieldErrors;\n }\n}\n\n/** 429 — Rate limit exceeded */\nexport class RateLimitError extends WhataloAPIError {\n /** Seconds until the rate limit resets */\n readonly retryAfter: number;\n\n constructor(retryAfter: number, requestId?: string) {\n super(\n `Rate limit exceeded. Retry after ${retryAfter} seconds.`,\n 429,\n \"rate_limit_exceeded\",\n requestId\n );\n this.name = \"RateLimitError\";\n this.retryAfter = retryAfter;\n }\n}\n\n/** 500 — Server-side error */\nexport class InternalError extends WhataloAPIError {\n constructor(\n message = \"An internal error occurred\",\n requestId?: string\n ) {\n super(message, 500, \"internal_error\", requestId);\n this.name = \"InternalError\";\n }\n}\n","import type {\n WhataloClientOptions,\n RateLimitInfo,\n PaginatedResponse,\n SingleResponse,\n Product,\n Order,\n Customer,\n Category,\n Discount,\n Store,\n InventoryItem,\n Webhook,\n ListProductsParams,\n CreateProductParams,\n UpdateProductParams,\n ListOrdersParams,\n ListCustomersParams,\n ListCategoriesParams,\n CreateCategoryParams,\n UpdateCategoryParams,\n ListDiscountsParams,\n CreateDiscountParams,\n UpdateDiscountParams,\n UpsertWebhookParams,\n UpdateWebhookParams,\n AdjustInventoryParams,\n} from \"./types.js\";\nimport {\n WhataloAPIError,\n AuthenticationError,\n AuthorizationError,\n InsufficientScopeError,\n RateLimitError,\n InternalError,\n NotFoundError,\n ValidationError,\n} from \"./errors.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.whatalo.com/v1\";\nconst DEFAULT_TIMEOUT = 30_000;\n\n/**\n * Official TypeScript client for the Whatalo REST API.\n * Zero runtime dependencies — uses native fetch.\n */\nexport class WhataloClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly timeout: number;\n private readonly maxRetries: number;\n private readonly fetchFn: typeof globalThis.fetch;\n private readonly onRequest?: (url: string, init: RequestInit) => void;\n private readonly onResponse?: (response: Response, durationMs: number) => void;\n\n /** Rate limit info from the most recent API response */\n rateLimit: RateLimitInfo = { limit: 0, remaining: 0, reset: 0 };\n\n /** Product resource methods */\n readonly products: ProductResource;\n /** Order resource methods */\n readonly orders: OrderResource;\n /** Customer resource methods */\n readonly customers: CustomerResource;\n /** Category resource methods */\n readonly categories: CategoryResource;\n /** Discount resource methods */\n readonly discounts: DiscountResource;\n /** Store resource methods */\n readonly store: StoreResource;\n /** Inventory resource methods */\n readonly inventory: InventoryResource;\n /** Webhook resource methods */\n readonly webhooks: WebhookResource;\n\n constructor(options: WhataloClientOptions) {\n // Guard against accidental browser-side instantiation.\n // WhataloClient embeds the API key in every request — exposing it in\n // client-side JavaScript would allow any visitor to make authenticated\n // API calls on behalf of the merchant. Use sessionToken() on the frontend\n // and validate on the backend instead.\n if (typeof window !== \"undefined\" && typeof process === \"undefined\") {\n console.warn(\n \"[WhataloClient] WARNING: WhataloClient should only be used in server-side code. \" +\n \"Exposing your API key in browser JavaScript is a security risk. \" +\n \"Use whatalo.sessionToken() in your frontend and validate it on your backend.\"\n );\n }\n\n this.apiKey = options.apiKey;\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT;\n this.maxRetries = Math.min(options.retries ?? 0, 3);\n this.fetchFn = options.fetch ?? globalThis.fetch;\n this.onRequest = options.onRequest;\n this.onResponse = options.onResponse;\n\n this.products = new ProductResource(this);\n this.orders = new OrderResource(this);\n this.customers = new CustomerResource(this);\n this.categories = new CategoryResource(this);\n this.discounts = new DiscountResource(this);\n this.store = new StoreResource(this);\n this.inventory = new InventoryResource(this);\n this.webhooks = new WebhookResource(this);\n }\n\n /**\n * Internal request method shared by all resource namespaces.\n * Handles headers, timeout, error parsing, and rate limit extraction.\n */\n async request<T>(\n method: string,\n path: string,\n options?: {\n body?: unknown;\n params?: Record<string, string | number | boolean | undefined>;\n }\n ): Promise<T> {\n let url = `${this.baseUrl}${path}`;\n\n // Add query params\n if (options?.params) {\n const searchParams = new URLSearchParams();\n for (const [key, value] of Object.entries(options.params)) {\n if (value !== undefined) {\n searchParams.set(key, String(value));\n }\n }\n const qs = searchParams.toString();\n if (qs) url += `?${qs}`;\n }\n\n const headers: Record<string, string> = {\n \"X-API-Key\": this.apiKey,\n \"Content-Type\": \"application/json\",\n };\n\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n const requestInit: RequestInit = {\n method,\n headers,\n body: options?.body ? JSON.stringify(options.body) : undefined,\n signal: controller.signal,\n };\n const startedAt = Date.now();\n this.onRequest?.(url, requestInit);\n\n try {\n const response = await this.fetchFn(url, requestInit);\n\n clearTimeout(timeoutId);\n this.onResponse?.(response, Date.now() - startedAt);\n\n // Extract rate limit headers\n this.rateLimit = {\n limit: Number(response.headers.get(\"X-RateLimit-Limit\") ?? 0),\n remaining: Number(\n response.headers.get(\"X-RateLimit-Remaining\") ?? 0\n ),\n reset: Number(response.headers.get(\"X-RateLimit-Reset\") ?? 0),\n };\n\n const requestId =\n response.headers.get(\"X-Request-Id\") ?? undefined;\n\n if (response.ok) {\n return (await response.json()) as T;\n }\n\n // Handle error responses\n const errorBody = await response.json().catch(() => ({\n error: { code: \"unknown\", message: response.statusText },\n }));\n const errData = (errorBody as { error?: { code?: string; message?: string; details?: Array<{ field: string; message: string }> } }).error;\n\n switch (response.status) {\n case 401:\n throw new AuthenticationError(errData?.message, requestId);\n case 403:\n // Distinguish between a specific scope denial and a generic 403.\n // insufficient_scope means the merchant never granted this permission.\n if (errData?.code === \"insufficient_scope\") {\n const details = errorBody as {\n error?: { required?: string; granted?: string[] };\n };\n throw new InsufficientScopeError(\n errData.message ?? \"Insufficient scope\",\n details.error?.required ?? \"\",\n details.error?.granted ?? [],\n requestId\n );\n }\n throw new AuthorizationError(\n errData?.message ?? \"Forbidden\",\n errData?.code ?? \"unknown_scope\",\n requestId\n );\n case 404:\n throw new NotFoundError(\"Resource\", path, requestId);\n case 422:\n throw new ValidationError(\n errData?.details ?? [\n { field: \"unknown\", message: errData?.message ?? \"Validation failed\" },\n ],\n requestId\n );\n case 429: {\n const retryAfter = Number(\n response.headers.get(\"Retry-After\") ?? 60\n );\n throw new RateLimitError(retryAfter, requestId);\n }\n default:\n if (response.status >= 500 && attempt < this.maxRetries) {\n // Retry on 5xx\n lastError = new InternalError(errData?.message, requestId);\n await this.sleep(Math.pow(2, attempt) * 1000);\n continue;\n }\n throw new WhataloAPIError(\n errData?.message ?? \"API error\",\n response.status,\n errData?.code ?? \"unknown\",\n requestId\n );\n }\n } catch (err) {\n clearTimeout(timeoutId);\n if (err instanceof WhataloAPIError) throw err;\n if ((err as Error).name === \"AbortError\") {\n throw new WhataloAPIError(\n \"Request timed out\",\n 408,\n \"timeout\"\n );\n }\n throw err;\n }\n }\n\n throw lastError ?? new InternalError(\"Request failed after retries\");\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\n/** Products API namespace */\nclass ProductResource {\n constructor(private readonly client: WhataloClient) {}\n\n async list(\n params?: ListProductsParams\n ): Promise<PaginatedResponse<Product>> {\n return this.client.request(\"GET\", \"/products\", {\n params: params as Record<string, string | number | boolean | undefined>,\n });\n }\n\n async get(id: string): Promise<SingleResponse<Product>> {\n return this.client.request(\"GET\", `/products/${id}`);\n }\n\n async create(\n data: CreateProductParams\n ): Promise<SingleResponse<Product>> {\n return this.client.request(\"POST\", \"/products\", { body: data });\n }\n\n async update(\n id: string,\n data: UpdateProductParams\n ): Promise<SingleResponse<Product>> {\n return this.client.request(\"PATCH\", `/products/${id}`, { body: data });\n }\n\n async delete(id: string): Promise<SingleResponse<{ deleted: boolean }>> {\n return this.client.request(\"DELETE\", `/products/${id}`);\n }\n\n async count(\n status?: \"active\" | \"inactive\" | \"all\"\n ): Promise<SingleResponse<{ count: number }>> {\n return this.client.request(\"GET\", \"/products/count\", {\n params: status ? { status } : undefined,\n });\n }\n}\n\n/** Orders API namespace */\nclass OrderResource {\n constructor(private readonly client: WhataloClient) {}\n\n async list(\n params?: ListOrdersParams\n ): Promise<PaginatedResponse<Order>> {\n return this.client.request(\"GET\", \"/orders\", {\n params: params as Record<string, string | number | boolean | undefined>,\n });\n }\n\n async get(id: string): Promise<SingleResponse<Order>> {\n return this.client.request(\"GET\", `/orders/${id}`);\n }\n\n async updateStatus(\n id: string,\n data: { status: string }\n ): Promise<SingleResponse<Order>> {\n return this.client.request(\"PATCH\", `/orders/${id}`, { body: data });\n }\n\n async count(\n status?: string\n ): Promise<SingleResponse<{ count: number }>> {\n return this.client.request(\"GET\", \"/orders/count\", {\n params: status ? { status } : undefined,\n });\n }\n}\n\n/** Customers API namespace */\nclass CustomerResource {\n constructor(private readonly client: WhataloClient) {}\n\n async list(params?: ListCustomersParams): Promise<PaginatedResponse<Customer>> {\n return this.client.request(\"GET\", \"/customers\", {\n params: params as Record<string, string | number | boolean | undefined>,\n });\n }\n\n async get(id: string): Promise<SingleResponse<Customer>> {\n return this.client.request(\"GET\", `/customers/${id}`);\n }\n}\n\n/** Categories API namespace */\nclass CategoryResource {\n constructor(private readonly client: WhataloClient) {}\n\n async list(\n params?: ListCategoriesParams\n ): Promise<PaginatedResponse<Category>> {\n return this.client.request(\"GET\", \"/categories\", {\n params: params as Record<string, string | number | boolean | undefined>,\n });\n }\n\n async get(id: string): Promise<SingleResponse<Category>> {\n return this.client.request(\"GET\", `/categories/${id}`);\n }\n\n async create(\n data: CreateCategoryParams\n ): Promise<SingleResponse<Category>> {\n return this.client.request(\"POST\", \"/categories\", { body: data });\n }\n\n async update(\n id: string,\n data: UpdateCategoryParams\n ): Promise<SingleResponse<Category>> {\n return this.client.request(\"PATCH\", `/categories/${id}`, { body: data });\n }\n\n async delete(id: string): Promise<SingleResponse<{ deleted: boolean }>> {\n return this.client.request(\"DELETE\", `/categories/${id}`);\n }\n\n async count(): Promise<SingleResponse<{ count: number }>> {\n return this.client.request(\"GET\", \"/categories/count\");\n }\n}\n\n/** Discounts API namespace */\nclass DiscountResource {\n constructor(private readonly client: WhataloClient) {}\n\n async list(\n params?: ListDiscountsParams\n ): Promise<PaginatedResponse<Discount>> {\n return this.client.request(\"GET\", \"/discounts\", {\n params: params as Record<string, string | number | boolean | undefined>,\n });\n }\n\n async get(id: string): Promise<SingleResponse<Discount>> {\n return this.client.request(\"GET\", `/discounts/${id}`);\n }\n\n async create(\n data: CreateDiscountParams\n ): Promise<SingleResponse<Discount>> {\n return this.client.request(\"POST\", \"/discounts\", { body: data });\n }\n\n async update(\n id: string,\n data: UpdateDiscountParams\n ): Promise<SingleResponse<Discount>> {\n return this.client.request(\"PATCH\", `/discounts/${id}`, { body: data });\n }\n\n async delete(id: string): Promise<SingleResponse<{ deleted: boolean }>> {\n return this.client.request(\"DELETE\", `/discounts/${id}`);\n }\n}\n\n/** Store API namespace */\nclass StoreResource {\n constructor(private readonly client: WhataloClient) {}\n\n async get(): Promise<SingleResponse<Store>> {\n return this.client.request(\"GET\", \"/store\");\n }\n}\n\n/** Inventory API namespace */\nclass InventoryResource {\n constructor(private readonly client: WhataloClient) {}\n\n async get(productId: string): Promise<SingleResponse<InventoryItem>> {\n return this.client.request(\"GET\", `/products/${productId}/inventory`);\n }\n\n async adjust(\n productId: string,\n data: AdjustInventoryParams\n ): Promise<SingleResponse<InventoryItem>> {\n return this.client.request(\"PATCH\", `/products/${productId}/inventory`, {\n body: data,\n });\n }\n}\n\n/** Webhooks API namespace */\nclass WebhookResource {\n constructor(private readonly client: WhataloClient) {}\n\n async list(): Promise<PaginatedResponse<Webhook>> {\n return this.client.request(\"GET\", \"/webhooks\");\n }\n\n async create(data: UpsertWebhookParams): Promise<SingleResponse<Webhook>> {\n return this.client.request(\"POST\", \"/webhooks\", { body: data });\n }\n\n async update(\n id: string,\n data: UpdateWebhookParams\n ): Promise<SingleResponse<Webhook>> {\n return this.client.request(\"PATCH\", `/webhooks/${id}`, { body: data });\n }\n\n async delete(id: string): Promise<SingleResponse<{ deleted: boolean }>> {\n return this.client.request(\"DELETE\", `/webhooks/${id}`);\n }\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\nexport interface VerifyWebhookParams {\n /** Raw request body as a string (NOT parsed JSON) */\n payload: string;\n /** Value of the X-Webhook-Signature header */\n signature: string;\n /** App's webhook secret (provided during app installation) */\n secret: string;\n}\n\n/**\n * Verify webhook signature using HMAC-SHA256.\n * Uses timing-safe comparison to prevent timing attacks.\n */\nexport function verifyWebhook({\n payload,\n signature,\n secret,\n}: VerifyWebhookParams): boolean {\n if (!payload || !signature || !secret) return false;\n\n const expected = createHmac(\"sha256\", secret)\n .update(payload, \"utf8\")\n .digest(\"hex\");\n\n if (expected.length !== signature.length) return false;\n\n try {\n return timingSafeEqual(\n Buffer.from(expected, \"hex\"),\n Buffer.from(signature, \"hex\")\n );\n } catch {\n return false;\n }\n}\n","import type { AppManifest } from \"./types.js\";\n\n/** Error thrown when manifest validation fails */\nexport class ManifestValidationError extends Error {\n readonly errors: Array<{ field: string; message: string }>;\n\n constructor(errors: Array<{ field: string; message: string }>) {\n const message = `Invalid app manifest: ${errors\n .map((e) => `${e.field}: ${e.message}`)\n .join(\"; \")}`;\n super(message);\n this.name = \"ManifestValidationError\";\n this.errors = errors;\n }\n}\n\nconst ID_REGEX = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;\nconst SEMVER_REGEX = /^\\d+\\.\\d+\\.\\d+$/;\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\nconst VALID_PERMISSIONS = new Set([\n \"read:products\",\n \"write:products\",\n \"read:orders\",\n \"write:orders\",\n \"read:customers\",\n \"write:customers\",\n \"read:inventory\",\n \"write:inventory\",\n \"read:store\",\n \"write:store\",\n \"read:webhooks\",\n \"write:webhooks\",\n \"read:discounts\",\n \"write:discounts\",\n \"read:analytics\",\n]);\n\nconst VALID_CATEGORIES = new Set([\n \"analytics\",\n \"marketing\",\n \"shipping\",\n \"payments\",\n \"payment\",\n \"inventory\",\n \"communication\",\n \"productivity\",\n \"accounting\",\n \"developer\",\n \"other\",\n]);\n\n/**\n * Validate and return a typed AppManifest.\n * Throws ManifestValidationError with detailed field-level errors.\n */\nexport function defineApp(manifest: AppManifest): AppManifest {\n const errors: Array<{ field: string; message: string }> = [];\n\n // id\n if (!manifest.id || manifest.id.length < 3 || manifest.id.length > 50) {\n errors.push({ field: \"id\", message: \"Must be 3-50 characters\" });\n } else if (!ID_REGEX.test(manifest.id)) {\n errors.push({\n field: \"id\",\n message: \"Must be lowercase alphanumeric with hyphens\",\n });\n }\n\n // name\n if (!manifest.name || manifest.name.length < 3 || manifest.name.length > 50) {\n errors.push({ field: \"name\", message: \"Must be 3-50 characters\" });\n }\n\n // description\n if (\n !manifest.description ||\n manifest.description.length < 10 ||\n manifest.description.length > 200\n ) {\n errors.push({\n field: \"description\",\n message: \"Must be 10-200 characters\",\n });\n }\n\n // version\n if (!manifest.version || !SEMVER_REGEX.test(manifest.version)) {\n errors.push({\n field: \"version\",\n message: \"Must be valid semver (e.g., 1.0.0)\",\n });\n }\n\n // author\n if (!manifest.author?.name) {\n errors.push({ field: \"author.name\", message: \"Required\" });\n }\n if (!manifest.author?.email || !EMAIL_REGEX.test(manifest.author.email)) {\n errors.push({\n field: \"author.email\",\n message: \"Must be a valid email address\",\n });\n }\n\n // permissions\n if (!manifest.permissions || manifest.permissions.length === 0) {\n errors.push({\n field: \"permissions\",\n message: \"At least one permission is required\",\n });\n } else {\n for (const perm of manifest.permissions) {\n if (!VALID_PERMISSIONS.has(perm)) {\n errors.push({\n field: \"permissions\",\n message: `Invalid permission: ${perm}`,\n });\n }\n }\n }\n\n // appUrl\n if (!manifest.appUrl) {\n errors.push({ field: \"appUrl\", message: \"Required\" });\n }\n\n // pricing\n if (manifest.pricing !== \"free\" && manifest.pricing !== \"paid\") {\n errors.push({\n field: \"pricing\",\n message: 'Must be \"free\" or \"paid\"',\n });\n }\n\n // category\n if (!VALID_CATEGORIES.has(manifest.category)) {\n errors.push({\n field: \"category\",\n message: `Invalid category: ${manifest.category}`,\n });\n }\n\n if (errors.length > 0) {\n throw new ManifestValidationError(errors);\n }\n\n return manifest;\n}\n","import { useState, useEffect, useCallback } from \"react\";\nimport type { WhataloContext, ContextMessage } from \"./types.js\";\nimport { attachInitListener, waitForParentOrigin } from \"./use-whatalo-action.js\";\n\nexport interface UseWhataloContextReturn extends WhataloContext {\n /** Whether the plugin has received context from the admin host */\n isReady: boolean;\n}\n\nconst DEFAULT_CONTEXT: WhataloContext = {\n storeId: \"\",\n storeName: \"\",\n user: { id: \"\", name: \"\", email: \"\", role: \"owner\" },\n appId: \"\",\n currentPage: \"\",\n locale: \"es\",\n theme: \"light\",\n initialHeight: 200,\n};\n\n/**\n * Listens for context messages from the admin host and signals readiness.\n * Automatically applies the theme attribute to the document root.\n */\nexport function useWhataloContext(): UseWhataloContextReturn {\n const [context, setContext] = useState<WhataloContext>(DEFAULT_CONTEXT);\n const [isReady, setIsReady] = useState(false);\n\n const handleMessage = useCallback((event: MessageEvent) => {\n if (!event.data || typeof event.data !== \"object\") return;\n const msg = event.data as ContextMessage & { context?: WhataloContext };\n if (msg.type !== \"whatalo:context\") return;\n\n const payload = msg.data ?? msg.context;\n if (!payload || typeof payload.storeId !== \"string\") return;\n\n setContext(payload);\n setIsReady(true);\n\n if (payload.theme) {\n document.documentElement.setAttribute(\"data-theme\", payload.theme);\n }\n }, []);\n\n useEffect(() => {\n // Ensure the whatalo:init listener is attached before doing anything else.\n // This is idempotent — safe to call multiple times.\n attachInitListener();\n\n window.addEventListener(\"message\", handleMessage);\n\n // Send the ready signal to the admin host. We wait for parentOrigin from\n // the whatalo:init handshake before sending so we never broadcast to \"*\".\n // The admin sends whatalo:init on iframe onLoad, which fires before the\n // plugin's React tree hydrates — so the wait is typically sub-millisecond.\n waitForParentOrigin().then((origin) => {\n window.parent.postMessage(\n {\n type: \"whatalo:action\",\n actionId: crypto.randomUUID(),\n action: \"ready\",\n payload: {},\n },\n origin,\n );\n });\n\n return () => window.removeEventListener(\"message\", handleMessage);\n }, [handleMessage]);\n\n return { ...context, isReady };\n}\n","import { useCallback, useRef, useEffect } from \"react\";\nimport type {\n BridgeAction,\n ActionAck,\n ToastPayload,\n ModalPayload,\n BillingOperation,\n BillingPayload,\n BillingPlanResponse,\n BillingSubscriptionResponse,\n} from \"./types.js\";\nimport { BILLING_OPERATION } from \"./types.js\";\n\nconst ACK_TIMEOUT_MS = 5_000;\n\n// ---------------------------------------------------------------------------\n// Module-level origin store (singleton shared across all hook instances)\n//\n// This module owns the handshake state. Both useWhataloAction and\n// useWhataloContext import from here so they target the same parentOrigin\n// once the admin sends the whatalo:init handshake.\n// ---------------------------------------------------------------------------\n\n/** Origin of the admin host, received via whatalo:init handshake. */\nlet parentOrigin: string | null = null;\n\n/** Callbacks waiting for parentOrigin to be resolved. */\nconst originResolvers: Array<(origin: string) => void> = [];\n\n/**\n * Returns a Promise that resolves with the parent origin.\n * If the handshake already completed, resolves immediately.\n */\nexport function waitForParentOrigin(): Promise<string> {\n if (parentOrigin !== null) {\n return Promise.resolve(parentOrigin);\n }\n return new Promise((resolve) => {\n originResolvers.push(resolve);\n });\n}\n\n/**\n * Initializes the origin listener exactly once (idempotent).\n * Listens for the whatalo:init message from the admin host and stores\n * the origin so all subsequent postMessage calls can target it explicitly.\n */\nlet initListenerAttached = false;\n\nexport function attachInitListener(): void {\n if (initListenerAttached) return;\n initListenerAttached = true;\n\n window.addEventListener(\"message\", (event: MessageEvent) => {\n // Only accept the init message from the direct parent window\n if (event.source !== window.parent) return;\n if (!event.data || typeof event.data !== \"object\") return;\n if (event.data.type !== \"whatalo:init\") return;\n\n // SECURITY: Use the browser-verified event.origin, NOT the\n // attacker-controllable event.data.origin. The event.origin property\n // is set by the browser and cannot be spoofed by the sender.\n const origin = event.origin;\n if (typeof origin !== \"string\" || !origin || origin === \"null\") return;\n\n // Prevent overwriting after initial handshake — a late-arriving\n // attacker message must not re-poison the stored origin.\n if (parentOrigin !== null) return;\n\n // Store origin and flush any queued resolvers\n parentOrigin = origin;\n for (const resolve of originResolvers) {\n resolve(origin);\n }\n originResolvers.length = 0;\n });\n}\n\n// Attach as early as possible to reduce handshake race windows.\nif (typeof window !== \"undefined\") {\n attachInitListener();\n}\n\n/** Billing namespace returned by useWhataloAction. */\nexport interface BillingActions {\n /** Fetch all active pricing plans for this plugin. */\n getPlans(): Promise<BillingPlanResponse[]>;\n /** Fetch the store's current subscription to this plugin, or null if none. */\n getSubscription(): Promise<BillingSubscriptionResponse | null>;\n /**\n * Request a subscription to the given plan.\n * The host will redirect the admin to an approval page.\n */\n requestSubscription(planSlug: string): Promise<void>;\n /** Initiate a cancellation for the current subscription. */\n cancelSubscription(): Promise<void>;\n /** Reactivate a subscription that was scheduled for cancellation at period end. */\n reactivateSubscription(): Promise<void>;\n /** Switch the current subscription to a different plan with proration. */\n switchPlan(newPlanSlug: string): Promise<void>;\n}\n\nexport interface UseWhataloActionReturn {\n /** Send a raw bridge action to the admin host */\n sendAction: (\n action: BridgeAction,\n payload: Record<string, unknown>,\n ) => Promise<ActionAck>;\n /** Display a toast notification in the admin */\n showToast: (options: ToastPayload) => Promise<ActionAck>;\n /** Navigate the admin to a path */\n navigate: (path: string) => Promise<ActionAck>;\n /** Open a modal in the admin */\n openModal: (options: Omit<ModalPayload, \"operation\">) => Promise<ActionAck>;\n /** Close the currently open modal */\n closeModal: () => Promise<ActionAck>;\n /** Request an iframe resize */\n resize: (height: number) => Promise<ActionAck>;\n /** Billing actions — query plans, manage subscriptions. */\n billing: BillingActions;\n}\n\n/**\n * Sends actions to the admin host via postMessage and waits for acknowledgements.\n * Each action has a unique ID and a timeout to prevent hanging promises.\n *\n * All postMessage calls target parentOrigin (set via whatalo:init handshake)\n * instead of \"*\" to prevent message interception by malicious embedders.\n * If the handshake has not completed yet, the message is queued and flushed\n * once the origin is received.\n */\nexport function useWhataloAction(): UseWhataloActionReturn {\n const pendingAcks = useRef(\n new Map<string, (ack: ActionAck) => void>(),\n );\n\n useEffect(() => {\n // Ensure the init listener is running whenever this hook mounts\n attachInitListener();\n\n const handleMessage = (event: MessageEvent) => {\n // Only accept ack messages from the verified parent origin\n if (parentOrigin && event.origin !== parentOrigin) return;\n if (!event.data || typeof event.data !== \"object\") return;\n const msg = event.data as ActionAck;\n if (msg.type !== \"whatalo:ack\") return;\n\n const resolve = pendingAcks.current.get(msg.actionId);\n if (resolve) {\n resolve(msg);\n pendingAcks.current.delete(msg.actionId);\n }\n };\n\n window.addEventListener(\"message\", handleMessage);\n return () => window.removeEventListener(\"message\", handleMessage);\n }, []);\n\n const sendAction = useCallback(\n (\n action: BridgeAction,\n payload: Record<string, unknown>,\n ): Promise<ActionAck> => {\n return new Promise((resolve) => {\n const actionId = crypto.randomUUID();\n\n const timeout = setTimeout(() => {\n pendingAcks.current.delete(actionId);\n resolve({\n type: \"whatalo:ack\",\n actionId,\n success: false,\n error: \"timeout\",\n });\n }, ACK_TIMEOUT_MS);\n\n pendingAcks.current.set(actionId, (ack) => {\n clearTimeout(timeout);\n resolve(ack);\n });\n\n // Wait for the parent origin from the whatalo:init handshake before\n // sending. If the handshake already completed this resolves synchronously.\n waitForParentOrigin().then((origin) => {\n window.parent.postMessage(\n { type: \"whatalo:action\", actionId, action, payload },\n origin,\n );\n });\n });\n },\n [],\n );\n\n const showToast = useCallback(\n (options: ToastPayload) =>\n sendAction(\"toast\", options as unknown as Record<string, unknown>),\n [sendAction],\n );\n\n const navigate = useCallback(\n (path: string) => sendAction(\"navigate\", { path }),\n [sendAction],\n );\n\n const openModal = useCallback(\n (options: Omit<ModalPayload, \"operation\">) =>\n sendAction(\"modal\", {\n operation: \"open\",\n ...options,\n } as unknown as Record<string, unknown>),\n [sendAction],\n );\n\n const closeModal = useCallback(\n () => sendAction(\"modal\", { operation: \"close\" }),\n [sendAction],\n );\n\n const resize = useCallback(\n (height: number) => sendAction(\"resize\", { height }),\n [sendAction],\n );\n\n // ---------------------------------------------------------------------------\n // Billing helpers\n //\n // Each billing method sends a \"billing\" action with a typed operation field.\n // The host resolves the operation against the store's app_plans /\n // app_subscriptions records and returns data in the ack payload.\n // ---------------------------------------------------------------------------\n\n /**\n * Extracts typed data from a billing ack payload.\n * Throws on failure so callers can use try/catch for error handling.\n */\n const sendBillingAction = useCallback(\n (operation: BillingOperation, extra?: Omit<BillingPayload, \"operation\">) =>\n sendAction(\"billing\", {\n operation,\n ...extra,\n } as unknown as Record<string, unknown>),\n [sendAction],\n );\n\n const billingGetPlans = useCallback(async (): Promise<BillingPlanResponse[]> => {\n const ack = await sendBillingAction(BILLING_OPERATION.GET_PLANS);\n if (!ack.success) {\n throw new Error(ack.error ?? \"billing.getPlans failed\");\n }\n // The host embeds the plans array in the ack under a `data` key\n return (ack as ActionAck & { data: BillingPlanResponse[] }).data ?? [];\n }, [sendBillingAction]);\n\n const billingGetSubscription = useCallback(\n async (): Promise<BillingSubscriptionResponse | null> => {\n const ack = await sendBillingAction(BILLING_OPERATION.GET_SUBSCRIPTION);\n if (!ack.success) {\n throw new Error(ack.error ?? \"billing.getSubscription failed\");\n }\n return (\n (ack as ActionAck & { data: BillingSubscriptionResponse | null }).data ??\n null\n );\n },\n [sendBillingAction],\n );\n\n const billingRequestSubscription = useCallback(\n async (planSlug: string): Promise<void> => {\n const ack = await sendBillingAction(BILLING_OPERATION.REQUEST_SUBSCRIPTION, {\n planSlug,\n });\n if (!ack.success) {\n throw new Error(ack.error ?? \"billing.requestSubscription failed\");\n }\n },\n [sendBillingAction],\n );\n\n const billingCancelSubscription = useCallback(async (): Promise<void> => {\n const ack = await sendBillingAction(BILLING_OPERATION.CANCEL_SUBSCRIPTION);\n if (!ack.success) {\n throw new Error(ack.error ?? \"billing.cancelSubscription failed\");\n }\n }, [sendBillingAction]);\n\n const billingReactivateSubscription = useCallback(async (): Promise<void> => {\n const ack = await sendBillingAction(BILLING_OPERATION.REACTIVATE_SUBSCRIPTION);\n if (!ack.success) {\n throw new Error(ack.error ?? \"billing.reactivateSubscription failed\");\n }\n }, [sendBillingAction]);\n\n const billingSwitchPlan = useCallback(\n async (newPlanSlug: string): Promise<void> => {\n const ack = await sendBillingAction(BILLING_OPERATION.SWITCH_PLAN, {\n planSlug: newPlanSlug,\n });\n if (!ack.success) {\n throw new Error(ack.error ?? \"billing.switchPlan failed\");\n }\n },\n [sendBillingAction],\n );\n\n const billing: BillingActions = {\n getPlans: billingGetPlans,\n getSubscription: billingGetSubscription,\n requestSubscription: billingRequestSubscription,\n cancelSubscription: billingCancelSubscription,\n reactivateSubscription: billingReactivateSubscription,\n switchPlan: billingSwitchPlan,\n };\n\n return { sendAction, showToast, navigate, openModal, closeModal, resize, billing };\n}\n","/** Context data sent from the admin host to a plugin iframe */\nexport interface WhataloContext {\n /** Store UUID */\n storeId: string;\n /** Store display name */\n storeName: string;\n /** Current order ID (set when plugin opens from order detail) */\n orderId?: string;\n /** Current order status */\n orderStatus?: string;\n /** Current product ID (set when plugin opens from product detail) */\n productId?: string;\n /** Authenticated user info */\n user: WhataloUser;\n /** Plugin ID from the manifest */\n appId: string;\n /** Current admin page path (e.g., \"/integrations/my-plugin/settings\") */\n currentPage: string;\n /** Locale code (e.g., \"es\", \"en\") */\n locale: string;\n /** Active color scheme */\n theme: \"light\" | \"dark\";\n /** Suggested initial iframe height in pixels */\n initialHeight: number;\n}\n\n/**\n * Authenticated user information provided to the plugin.\n *\n * `role` reflects the user's actual role in the store, not a hardcoded value.\n * Current Whatalo stores are single-owner, so the role will always be \"owner\"\n * for now. The union type is forward-compatible for future multi-member support.\n */\nexport interface WhataloUser {\n id: string;\n name: string;\n email: string;\n role: \"owner\" | \"admin\" | \"editor\" | \"staff\" | \"viewer\";\n}\n\n/** Available bridge actions the plugin can dispatch */\nexport type BridgeAction =\n | \"navigate\"\n | \"toast\"\n | \"modal\"\n | \"resize\"\n | \"ready\"\n | \"billing\"\n | \"data\";\n\n// ---------------------------------------------------------------------------\n// Data types (frontend-only plugins — bridge-proxied store data access)\n// ---------------------------------------------------------------------------\n\n/** Resources available through the data bridge. */\nexport type DataResource =\n | \"products\"\n | \"orders\"\n | \"customers\"\n | \"categories\";\n\n/** Operations available on data resources. */\nexport type DataOperation = \"list\" | \"get\";\n\n/** Scope required to read each resource type. */\nexport const DATA_RESOURCE_SCOPE: Record<DataResource, string> = {\n products: \"read:products\",\n orders: \"read:orders\",\n customers: \"read:customers\",\n categories: \"read:products\", // categories share the products scope\n};\n\n/** Payload sent from the plugin to the host for data actions. */\nexport interface DataPayload {\n resource: DataResource;\n operation: DataOperation;\n /** Resource ID — required for \"get\" operations. */\n id?: string;\n /** Pagination and filtering params for \"list\" operations. */\n params?: Record<string, unknown>;\n}\n\n/** Paginated list response returned by the data bridge. */\nexport interface DataListResponse<T = Record<string, unknown>> {\n data: T[];\n pagination: {\n page: number;\n per_page: number;\n total: number;\n total_pages: number;\n };\n}\n\n/** Single resource response returned by the data bridge. */\nexport interface DataSingleResponse<T = Record<string, unknown>> {\n data: T;\n}\n\n// ---------------------------------------------------------------------------\n// Billing types\n// ---------------------------------------------------------------------------\n\n/** All billing operations supported by the bridge. createUsageRecord is reserved for v2. */\nexport const BILLING_OPERATION = {\n GET_PLANS: \"getPlans\",\n GET_SUBSCRIPTION: \"getSubscription\",\n REQUEST_SUBSCRIPTION: \"requestSubscription\",\n CANCEL_SUBSCRIPTION: \"cancelSubscription\",\n REACTIVATE_SUBSCRIPTION: \"reactivateSubscription\",\n SWITCH_PLAN: \"switchPlan\",\n CREATE_USAGE_RECORD: \"createUsageRecord\",\n} as const;\n\nexport type BillingOperation =\n (typeof BILLING_OPERATION)[keyof typeof BILLING_OPERATION];\n\n/** Payload sent from the plugin to the host for billing actions. */\nexport interface BillingPayload {\n operation: BillingOperation;\n /** Required for requestSubscription */\n planSlug?: string;\n /** Human-readable reason for the subscription request */\n description?: string;\n /** Reserved for createUsageRecord (v2) */\n amount?: number;\n /** Idempotency key for safe retries (v2) */\n idempotencyKey?: string;\n}\n\n/** A single pricing plan returned by getPlans. */\nexport interface BillingPlanResponse {\n slug: string;\n name: string;\n price: number;\n currency: string;\n /** \"monthly\" | \"annual\" | null (null for one-time or usage plans) */\n interval: string | null;\n trialDays: number;\n features: string[];\n isPopular: boolean;\n}\n\n/** The store's current subscription to this plugin, returned by getSubscription. */\nexport interface BillingSubscriptionResponse {\n planSlug: string;\n planName: string;\n /** \"pending\" | \"trialing\" | \"active\" | \"past_due\" | \"canceled\" */\n status: string;\n trialEndsAt: string | null;\n currentPeriodEnd: string | null;\n /** Whether the subscription is scheduled for cancellation at period end */\n cancelAtPeriodEnd: boolean;\n /** ISO timestamp of when the cancellation was initiated, or null */\n canceledAt: string | null;\n}\n\n/** Message sent from admin host to plugin with context data */\nexport interface ContextMessage {\n type: \"whatalo:context\";\n data: WhataloContext;\n}\n\n/** Action message sent from plugin to admin host */\nexport interface ActionMessage {\n type: \"whatalo:action\";\n actionId: string;\n action: BridgeAction;\n payload: Record<string, unknown>;\n}\n\n/** Acknowledgement message sent from admin host back to plugin */\nexport interface ActionAck {\n type: \"whatalo:ack\";\n actionId: string;\n success: boolean;\n error?: string;\n}\n\n/** Payload for toast notifications */\nexport interface ToastPayload {\n title: string;\n description?: string;\n variant?: \"success\" | \"error\" | \"warning\" | \"info\";\n}\n\n/** Payload for admin navigation */\nexport interface NavigatePayload {\n path: string;\n}\n\n/** Payload for modal operations */\nexport interface ModalPayload {\n operation: \"open\" | \"close\";\n title?: string;\n url?: string;\n width?: number;\n height?: number;\n}\n\n/** Payload for iframe resize */\nexport interface ResizePayload {\n height: number;\n}\n","import { useWhataloContext } from \"./use-whatalo-context.js\";\nimport { useWhataloAction } from \"./use-whatalo-action.js\";\nimport type {\n ActionAck,\n ToastPayload,\n ModalPayload,\n WhataloUser,\n BillingPlanResponse,\n BillingSubscriptionResponse,\n} from \"./types.js\";\nimport type { BillingActions } from \"./use-whatalo-action.js\";\n\nexport interface AppBridgeToast {\n /** Show a toast notification in the admin host */\n show: (\n title: string,\n options?: Omit<ToastPayload, \"title\">,\n ) => Promise<ActionAck>;\n}\n\nexport interface AppBridgeModal {\n /** Open a modal in the admin host */\n open: (options: Omit<ModalPayload, \"operation\">) => Promise<ActionAck>;\n /** Close the currently open modal */\n close: () => Promise<ActionAck>;\n}\n\n/** Re-export for consumers who import directly from use-app-bridge */\nexport type { BillingActions, BillingPlanResponse, BillingSubscriptionResponse };\n\nexport interface AppBridge {\n /** Store UUID */\n storeId: string;\n /** Store display name */\n storeName: string;\n /** Locale code (e.g., \"es\", \"en\") */\n locale: string;\n /** Active color scheme */\n theme: \"light\" | \"dark\";\n /** Authenticated user info */\n user: WhataloUser;\n /** Plugin ID from the manifest */\n appId: string;\n /** Whether the plugin has received context from the admin host */\n isReady: boolean;\n /** Toast notification helpers */\n toast: AppBridgeToast;\n /** Navigate the admin to a path */\n navigate: (path: string) => Promise<ActionAck>;\n /** Modal helpers */\n modal: AppBridgeModal;\n /** Request an iframe resize */\n resize: (height: number) => Promise<ActionAck>;\n /** Billing actions — query plans and manage subscriptions */\n billing: BillingActions;\n}\n\n/**\n * Unified bridge hook that combines context and actions into a single API.\n * This is the primary hook plugin developers should use.\n *\n * @example\n * ```tsx\n * const bridge = useAppBridge();\n * bridge.toast.show(\"Saved!\", { variant: \"success\" });\n * bridge.navigate(\"/orders\");\n * ```\n */\nexport function useAppBridge(): AppBridge {\n const ctx = useWhataloContext();\n const actions = useWhataloAction();\n\n return {\n storeId: ctx.storeId,\n storeName: ctx.storeName,\n locale: ctx.locale,\n theme: ctx.theme,\n user: ctx.user,\n appId: ctx.appId,\n isReady: ctx.isReady,\n toast: {\n show: (title, options) =>\n actions.showToast({ title, ...options }),\n },\n navigate: actions.navigate,\n modal: {\n open: actions.openModal,\n close: actions.closeModal,\n },\n resize: actions.resize,\n billing: actions.billing,\n };\n}\n","/**\n * Session Token Bridge — browser-side.\n *\n * Provides the sessionToken() function that plugin frontends use to obtain a\n * short-lived JWT from the admin host via postMessage. The token is cached and\n * automatically refreshed when it is within 60 seconds of expiry.\n *\n * Protocol (postMessage):\n * Plugin → Host: { type: \"whatalo:session_token_request\", requestId: string }\n * Host → Plugin: { type: \"whatalo:session_token_response\", requestId: string,\n * token: string, expiresAt: number }\n * OR: { type: \"whatalo:session_token_response\", requestId: string,\n * error: string }\n */\n\nimport { waitForParentOrigin } from \"./use-whatalo-action.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Resolved value from sessionToken() */\nexport interface SessionTokenResult {\n /** Compact JWT string (header.payload.signature) */\n token: string;\n /** Token expiry as Unix seconds */\n expiresAt: number;\n}\n\n// ---------------------------------------------------------------------------\n// Internal state — module-level singleton cache\n// ---------------------------------------------------------------------------\n\n/** Currently cached token, null if never fetched or after a cache invalidation. */\nlet cachedToken: SessionTokenResult | null = null;\n\n/**\n * In-flight promise to prevent duplicate concurrent requests.\n * When a request is already in flight, subsequent callers await the same promise.\n */\nlet inFlightRequest: Promise<SessionTokenResult> | null = null;\n\n/**\n * Seconds before expiry at which the cache is considered stale and a new\n * token is proactively fetched. This gives the plugin backend enough buffer\n * to process the request before the token actually expires.\n */\nconst REFRESH_BUFFER_SECONDS = 60;\n\n/** Timeout (ms) to wait for the host to respond to a token request. */\nconst REQUEST_TIMEOUT_MS = 10_000;\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Returns true if the cached token is still valid (not within the refresh buffer). */\nfunction isCacheValid(cached: SessionTokenResult): boolean {\n const nowSeconds = Math.floor(Date.now() / 1000);\n return cached.expiresAt - nowSeconds > REFRESH_BUFFER_SECONDS;\n}\n\n/**\n * Sends a SESSION_TOKEN_REQUEST to the admin host and waits for the response.\n * Each request carries a unique requestId so concurrent callers can be matched.\n */\nasync function requestFromHost(): Promise<SessionTokenResult> {\n const requestId = crypto.randomUUID();\n\n // Wait for the origin handshake (whatalo:init) before sending — this is\n // idempotent and sub-millisecond if the handshake already completed.\n const parentOrigin = await waitForParentOrigin();\n\n return new Promise<SessionTokenResult>((resolve, reject) => {\n const timeout = setTimeout(() => {\n window.removeEventListener(\"message\", handler);\n reject(new Error(\"whatalo.sessionToken() timed out: no response from host\"));\n }, REQUEST_TIMEOUT_MS);\n\n function handler(event: MessageEvent): void {\n if (event.origin !== parentOrigin) return;\n if (!event.data || typeof event.data !== \"object\") return;\n\n const msg = event.data as {\n type?: string;\n requestId?: string;\n token?: string;\n expiresAt?: number;\n error?: string;\n };\n\n if (\n msg.type !== \"whatalo:session_token_response\" ||\n msg.requestId !== requestId\n ) {\n return;\n }\n\n clearTimeout(timeout);\n window.removeEventListener(\"message\", handler);\n\n if (msg.error) {\n reject(new Error(`Session token error: ${msg.error}`));\n return;\n }\n\n if (typeof msg.token !== \"string\" || typeof msg.expiresAt !== \"number\") {\n reject(new Error(\"Session token response missing token or expiresAt\"));\n return;\n }\n\n // Guard against a host returning an already-expired token (would cause\n // infinite refresh loops since the cache would never be valid).\n const nowSeconds = Math.floor(Date.now() / 1000);\n if (msg.expiresAt <= nowSeconds) {\n reject(new Error(\"Host returned an already-expired session token\"));\n return;\n }\n\n resolve({ token: msg.token, expiresAt: msg.expiresAt });\n }\n\n window.addEventListener(\"message\", handler);\n\n // Send the request AFTER the listener is registered to avoid a race condition\n window.parent.postMessage(\n { type: \"whatalo:session_token_request\", requestId },\n parentOrigin,\n );\n });\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Returns a valid Whatalo session token, fetching a new one from the admin host\n * when the cache is empty or within REFRESH_BUFFER_SECONDS of expiry.\n *\n * Multiple concurrent calls share a single in-flight request to avoid hammering\n * the host with duplicate postMessage round-trips.\n *\n * @example\n * ```typescript\n * const { token } = await sessionToken();\n * const res = await fetch(\"https://my-plugin.com/api/orders\", {\n * headers: { Authorization: `Bearer ${token}` },\n * });\n * ```\n */\nexport async function sessionToken(): Promise<SessionTokenResult> {\n // Fast path: return cached token if still valid\n if (cachedToken && isCacheValid(cachedToken)) {\n return cachedToken;\n }\n\n // Coalesce concurrent requests into one\n if (inFlightRequest) {\n return inFlightRequest;\n }\n\n inFlightRequest = requestFromHost()\n .then((result) => {\n cachedToken = result;\n return result;\n })\n .finally(() => {\n inFlightRequest = null;\n });\n\n return inFlightRequest;\n}\n\n/**\n * Clears the cached session token, forcing the next call to sessionToken()\n * to fetch a fresh one from the host. Useful after authentication errors.\n */\nexport function clearSessionTokenCache(): void {\n cachedToken = null;\n}\n","import { useCallback } from \"react\";\nimport { useWhataloAction } from \"./use-whatalo-action.js\";\nimport type {\n ActionAck,\n DataResource,\n DataListResponse,\n DataSingleResponse,\n} from \"./types.js\";\nimport type {\n Product,\n Order,\n Customer,\n Category,\n ListProductsParams,\n ListOrdersParams,\n ListCustomersParams,\n ListCategoriesParams,\n} from \"../client/types.js\";\n\n// ---------------------------------------------------------------------------\n// Helper — extract typed data from a data ack\n// ---------------------------------------------------------------------------\n\nfunction extractAckData<T>(ack: ActionAck): T {\n if (!ack.success) {\n throw new Error(ack.error ?? \"Data request failed\");\n }\n return (ack as ActionAck & { data: T }).data;\n}\n\n// ---------------------------------------------------------------------------\n// useWhataloData — read-only store data access for frontend-only plugins\n// ---------------------------------------------------------------------------\n\n/**\n * Provides typed, scope-enforced read access to store data through the\n * App Bridge. Designed for frontend-only plugins that don't have their own\n * backend server.\n *\n * Every request is proxied through the admin host (PluginFrame), which\n * checks `granted_scopes` on the installation before executing the query.\n *\n * All methods are read-only. Write operations require the plugin to have\n * its own backend and use the WhataloClient with an API key.\n *\n * @example\n * ```tsx\n * const { products, orders } = useWhataloData();\n *\n * const allProducts = await products.list({ page: 1, per_page: 20 });\n * const single = await products.get(\"prod_abc123\");\n * ```\n */\nexport function useWhataloData(): WhataloDataReturn {\n const { sendAction } = useWhataloAction();\n\n const fetchResource = useCallback(\n (\n resource: DataResource,\n operation: \"list\" | \"get\",\n params?: Record<string, unknown>,\n id?: string,\n ): Promise<ActionAck> =>\n sendAction(\"data\", {\n resource,\n operation,\n ...(id ? { id } : {}),\n ...(params && Object.keys(params).length > 0 ? { params } : {}),\n }),\n [sendAction],\n );\n\n // -- Products -------------------------------------------------------------\n\n const productsList = useCallback(\n async (params?: ListProductsParams): Promise<DataListResponse<Product>> => {\n const ack = await fetchResource(\n \"products\",\n \"list\",\n params as Record<string, unknown>,\n );\n return extractAckData(ack);\n },\n [fetchResource],\n );\n\n const productsGet = useCallback(\n async (id: string): Promise<DataSingleResponse<Product>> => {\n const ack = await fetchResource(\"products\", \"get\", undefined, id);\n return extractAckData(ack);\n },\n [fetchResource],\n );\n\n // -- Orders ---------------------------------------------------------------\n\n const ordersList = useCallback(\n async (params?: ListOrdersParams): Promise<DataListResponse<Order>> => {\n const ack = await fetchResource(\n \"orders\",\n \"list\",\n params as Record<string, unknown>,\n );\n return extractAckData(ack);\n },\n [fetchResource],\n );\n\n const ordersGet = useCallback(\n async (id: string): Promise<DataSingleResponse<Order>> => {\n const ack = await fetchResource(\"orders\", \"get\", undefined, id);\n return extractAckData(ack);\n },\n [fetchResource],\n );\n\n // -- Customers ------------------------------------------------------------\n\n const customersList = useCallback(\n async (\n params?: ListCustomersParams,\n ): Promise<DataListResponse<Customer>> => {\n const ack = await fetchResource(\n \"customers\",\n \"list\",\n params as Record<string, unknown>,\n );\n return extractAckData(ack);\n },\n [fetchResource],\n );\n\n const customersGet = useCallback(\n async (id: string): Promise<DataSingleResponse<Customer>> => {\n const ack = await fetchResource(\"customers\", \"get\", undefined, id);\n return extractAckData(ack);\n },\n [fetchResource],\n );\n\n // -- Categories -----------------------------------------------------------\n\n const categoriesList = useCallback(\n async (\n params?: ListCategoriesParams,\n ): Promise<DataListResponse<Category>> => {\n const ack = await fetchResource(\n \"categories\",\n \"list\",\n params as Record<string, unknown>,\n );\n return extractAckData(ack);\n },\n [fetchResource],\n );\n\n const categoriesGet = useCallback(\n async (id: string): Promise<DataSingleResponse<Category>> => {\n const ack = await fetchResource(\"categories\", \"get\", undefined, id);\n return extractAckData(ack);\n },\n [fetchResource],\n );\n\n return {\n products: { list: productsList, get: productsGet },\n orders: { list: ordersList, get: ordersGet },\n customers: { list: customersList, get: customersGet },\n categories: { list: categoriesList, get: categoriesGet },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Resource accessor with list and get methods. */\nexport interface ResourceAccessor<\n T,\n P = Record<string, unknown>,\n> {\n list(params?: P): Promise<DataListResponse<T>>;\n get(id: string): Promise<DataSingleResponse<T>>;\n}\n\n/** Return type of useWhataloData. */\nexport interface WhataloDataReturn {\n products: ResourceAccessor<Product, ListProductsParams>;\n orders: ResourceAccessor<Order, ListOrdersParams>;\n customers: ResourceAccessor<Customer, ListCustomersParams>;\n categories: ResourceAccessor<Category, ListCategoriesParams>;\n}\n","// @whatalo/plugin-sdk — Official TypeScript SDK for building Whatalo plugins\nexport const SDK_VERSION = \"0.0.1\";\n\n// Client\nexport { WhataloClient } from \"./client/whatalo-client.js\";\nexport type {\n WhataloClientOptions,\n RateLimitInfo,\n PaginatedResponse,\n SingleResponse,\n Product,\n Order,\n Customer,\n Category,\n Discount,\n Store,\n InventoryItem,\n Webhook,\n ListProductsParams,\n CreateProductParams,\n UpdateProductParams,\n ListOrdersParams,\n ListCustomersParams,\n ListCategoriesParams,\n CreateCategoryParams,\n UpdateCategoryParams,\n ListDiscountsParams,\n CreateDiscountParams,\n UpdateDiscountParams,\n UpsertWebhookParams,\n UpdateWebhookParams,\n AdjustInventoryParams,\n} from \"./client/types.js\";\n\n// Error classes\nexport {\n WhataloAPIError,\n AuthenticationError,\n AuthorizationError,\n NotFoundError,\n ValidationError,\n RateLimitError,\n InternalError,\n} from \"./client/errors.js\";\n\n// Webhook verification\nexport { verifyWebhook } from \"./webhooks/verify.js\";\nexport type {\n VerifyWebhookParams,\n WebhookEvent,\n WebhookEventData,\n WebhookPayload,\n} from \"./webhooks/index.js\";\n\n// Manifest\nexport { defineApp, ManifestValidationError } from \"./manifest/index.js\";\nexport type {\n AppManifest,\n AppPermission,\n AppCategory,\n} from \"./manifest/index.js\";\n\n// Bridge (React hooks for plugin ↔ admin postMessage communication)\nexport { useAppBridge, useWhataloAction, useWhataloContext, useWhataloData } from \"./bridge/index.js\";\nexport { BILLING_OPERATION, DATA_RESOURCE_SCOPE } from \"./bridge/index.js\";\nexport { sessionToken, clearSessionTokenCache } from \"./bridge/index.js\";\nexport type {\n AppBridge,\n AppBridgeToast,\n AppBridgeModal,\n BillingActions,\n UseWhataloActionReturn,\n UseWhataloContextReturn,\n WhataloContext,\n WhataloUser,\n BridgeAction,\n ActionAck,\n ToastPayload,\n NavigatePayload,\n ModalPayload,\n ResizePayload,\n BillingOperation,\n BillingPayload,\n BillingPlanResponse,\n BillingSubscriptionResponse,\n SessionTokenResult,\n // Data types (frontend-only plugins)\n WhataloDataReturn,\n ResourceAccessor,\n DataResource,\n DataOperation,\n DataPayload,\n DataListResponse,\n DataSingleResponse,\n} from \"./bridge/index.js\";\n"],"mappings":"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","names":["useEffect","useCallback","useCallback","useEffect","parentOrigin","useCallback","useCallback"]}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/server/verify-session-token.ts
|
|
21
|
+
var verify_session_token_exports = {};
|
|
22
|
+
__export(verify_session_token_exports, {
|
|
23
|
+
verifyWhataloSessionToken: () => verifySessionToken
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(verify_session_token_exports);
|
|
26
|
+
|
|
27
|
+
// src/session-token.ts
|
|
28
|
+
var import_node_crypto = require("crypto");
|
|
29
|
+
var HEADER_B64 = Buffer.from(
|
|
30
|
+
JSON.stringify({ alg: "HS256", typ: "JWT" })
|
|
31
|
+
).toString("base64url");
|
|
32
|
+
function decodeB64(encoded) {
|
|
33
|
+
const json = Buffer.from(encoded, "base64url").toString("utf8");
|
|
34
|
+
return JSON.parse(json);
|
|
35
|
+
}
|
|
36
|
+
function computeHmac(data, secret) {
|
|
37
|
+
return (0, import_node_crypto.createHmac)("sha256", secret).update(data, "utf8").digest("hex");
|
|
38
|
+
}
|
|
39
|
+
function verifySessionToken(token, clientSecret) {
|
|
40
|
+
if (!token || !clientSecret) {
|
|
41
|
+
throw new Error("Token and client_secret are required");
|
|
42
|
+
}
|
|
43
|
+
const parts = token.split(".");
|
|
44
|
+
if (parts.length !== 3) {
|
|
45
|
+
throw new Error("Invalid token structure: expected header.payload.signature");
|
|
46
|
+
}
|
|
47
|
+
const [headerB64, payloadB64, receivedSig] = parts;
|
|
48
|
+
let header;
|
|
49
|
+
try {
|
|
50
|
+
header = decodeB64(headerB64);
|
|
51
|
+
} catch {
|
|
52
|
+
throw new Error("Invalid token: malformed header");
|
|
53
|
+
}
|
|
54
|
+
if (header.alg !== "HS256" || header.typ !== "JWT") {
|
|
55
|
+
throw new Error("Invalid token: unsupported algorithm or type");
|
|
56
|
+
}
|
|
57
|
+
const signingInput = `${headerB64}.${payloadB64}`;
|
|
58
|
+
const expectedSig = computeHmac(signingInput, clientSecret);
|
|
59
|
+
const normalizeKey = expectedSig;
|
|
60
|
+
const receivedNorm = (0, import_node_crypto.createHmac)("sha256", normalizeKey).update(receivedSig).digest();
|
|
61
|
+
const expectedNorm = (0, import_node_crypto.createHmac)("sha256", normalizeKey).update(expectedSig).digest();
|
|
62
|
+
if (!(0, import_node_crypto.timingSafeEqual)(receivedNorm, expectedNorm)) {
|
|
63
|
+
throw new Error("Invalid token: signature mismatch");
|
|
64
|
+
}
|
|
65
|
+
let claims;
|
|
66
|
+
try {
|
|
67
|
+
claims = decodeB64(payloadB64);
|
|
68
|
+
} catch {
|
|
69
|
+
throw new Error("Invalid token: malformed payload");
|
|
70
|
+
}
|
|
71
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
72
|
+
const CLOCK_SKEW_SECONDS = 30;
|
|
73
|
+
if (typeof claims.exp !== "number" || claims.exp + CLOCK_SKEW_SECONDS <= now) {
|
|
74
|
+
throw new Error("Token has expired");
|
|
75
|
+
}
|
|
76
|
+
if (claims.iss !== "whatalo") {
|
|
77
|
+
throw new Error(`Invalid token: unexpected issuer "${claims.iss}"`);
|
|
78
|
+
}
|
|
79
|
+
const required = [
|
|
80
|
+
"aud",
|
|
81
|
+
"sub",
|
|
82
|
+
"iat",
|
|
83
|
+
"jti",
|
|
84
|
+
"storeId",
|
|
85
|
+
"appId",
|
|
86
|
+
"scopes",
|
|
87
|
+
"installationId"
|
|
88
|
+
];
|
|
89
|
+
for (const key of required) {
|
|
90
|
+
if (claims[key] === void 0 || claims[key] === null) {
|
|
91
|
+
throw new Error(`Invalid token: missing required claim "${key}"`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (!Array.isArray(claims.scopes)) {
|
|
95
|
+
throw new Error("Invalid token: scopes must be an array");
|
|
96
|
+
}
|
|
97
|
+
return claims;
|
|
98
|
+
}
|
|
99
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
100
|
+
0 && (module.exports = {
|
|
101
|
+
verifyWhataloSessionToken
|
|
102
|
+
});
|
|
103
|
+
//# sourceMappingURL=verify-session-token.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/verify-session-token.ts","../../src/session-token.ts"],"sourcesContent":["/**\n * Plugin backend helper — verifies a Whatalo session token.\n *\n * Plugin developers import this function in their OWN backend (Next.js API routes,\n * Express handlers, Hono routes, etc.) to authenticate requests from their frontend.\n *\n * Flow:\n * 1. Plugin frontend calls `bridge.sessionToken()` (App Bridge)\n * 2. Admin host issues a signed JWT and delivers it via postMessage\n * 3. Plugin frontend includes the token in `Authorization: Bearer <token>`\n * 4. Plugin backend calls verifyWhataloSessionToken(token, clientSecret)\n * 5. On success, the backend trusts the claims and calls api.whatalo.com\n * with its own WHATALO_API_KEY\n *\n * Usage:\n * ```typescript\n * import { verifyWhataloSessionToken } from \"@whatalo/plugin-sdk/server\";\n *\n * // In your backend handler:\n * const token = req.headers.authorization?.replace(\"Bearer \", \"\") ?? \"\";\n * const claims = verifyWhataloSessionToken(token, process.env.WHATALO_CLIENT_SECRET!);\n * // claims.storeId, claims.scopes, claims.installationId are now available\n * ```\n */\n\nexport { verifySessionToken as verifyWhataloSessionToken } from \"../session-token.js\";\nexport type { SessionTokenClaims } from \"../session-token.js\";\n","/**\n * Session Token — server-side only.\n *\n * Signs and verifies short-lived HMAC-SHA256 JWTs used by plugin iframes to\n * authenticate against their own backend. The token is issued by the Whatalo\n * admin host and forwarded by the plugin frontend to its server, which\n * validates the signature using the app's client_secret.\n *\n * Architecture:\n * Admin host ──issues──► iframe (via App Bridge)\n * iframe frontend ──sends──► plugin backend\n * plugin backend ──validates──► calls Whatalo public API with its own key\n *\n * This module uses ONLY Node.js built-in `node:crypto`. No external dependencies.\n * It must NEVER be imported in browser code (import from server entry points only).\n */\n\nimport { createHmac, timingSafeEqual, randomBytes } from \"node:crypto\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * JWT claims embedded in every session token.\n * All string fields are required so the verifier can validate them fully.\n */\nexport interface SessionTokenClaims {\n /** Issuer — always \"whatalo\" */\n iss: \"whatalo\";\n /** Audience — the app's client_id (marketplace_apps.slug) */\n aud: string;\n /** Subject — the store's public_id */\n sub: string;\n /** Expiration time (Unix seconds) */\n exp: number;\n /** Issued-at time (Unix seconds) */\n iat: number;\n /** JWT ID — unique nonce to prevent replay attacks */\n jti: string;\n /** Store public identifier (same as sub, kept explicit for SDK consumers) */\n storeId: string;\n /** App/plugin public identifier (slug) */\n appId: string;\n /** Permission scopes granted at install time */\n scopes: string[];\n /** app_installations.id for the specific install record */\n installationId: string;\n}\n\n/** Output of signSessionToken — includes the raw token and its expiry. */\nexport interface SignedSessionToken {\n /** Compact JWT string (header.payload.signature) */\n token: string;\n /** Expiration as Unix seconds (same as claims.exp) */\n expiresAt: number;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Token lifetime in seconds. Short by design — the bridge auto-refreshes. */\nconst TOKEN_TTL_SECONDS = 300; // 5 minutes\n\n/** Fixed header for all session tokens (alg=HS256, typ=JWT). */\nconst HEADER_B64 = Buffer.from(\n JSON.stringify({ alg: \"HS256\", typ: \"JWT\" }),\n).toString(\"base64url\");\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Encodes an object as a base64url JSON string (no padding). */\nfunction encodeB64(obj: unknown): string {\n return Buffer.from(JSON.stringify(obj)).toString(\"base64url\");\n}\n\n/** Decodes a base64url string to a plain object. Throws on malformed input. */\nfunction decodeB64<T>(encoded: string): T {\n const json = Buffer.from(encoded, \"base64url\").toString(\"utf8\");\n return JSON.parse(json) as T;\n}\n\n/**\n * Computes HMAC-SHA256 over `data` using `secret` and returns the hex digest.\n * Uses the app's client_secret as the key — never a platform-wide secret.\n */\nfunction computeHmac(data: string, secret: string): string {\n return createHmac(\"sha256\", secret).update(data, \"utf8\").digest(\"hex\");\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Signs a session token for the given claims using the app's client_secret.\n *\n * @param payload - Token claims (storeId, appId, scopes, installationId, aud).\n * exp/iat/jti/iss are injected automatically.\n * @param clientSecret - The app's client_secret from marketplace_apps table.\n * @returns Signed JWT and its expiry timestamp.\n */\nexport function signSessionToken(\n payload: Omit<SessionTokenClaims, \"iss\" | \"iat\" | \"exp\" | \"jti\">,\n clientSecret: string,\n): SignedSessionToken {\n if (!clientSecret || clientSecret.length === 0) {\n throw new Error(\"client_secret must be a non-empty string\");\n }\n\n const now = Math.floor(Date.now() / 1000);\n const exp = now + TOKEN_TTL_SECONDS;\n\n const claims: SessionTokenClaims = {\n iss: \"whatalo\",\n aud: payload.aud,\n sub: payload.sub,\n iat: now,\n exp,\n // 16 random bytes → 32-char hex string used as the JWT ID (jti)\n jti: randomBytes(16).toString(\"hex\"),\n storeId: payload.storeId,\n appId: payload.appId,\n scopes: payload.scopes,\n installationId: payload.installationId,\n };\n\n const payloadB64 = encodeB64(claims);\n const signingInput = `${HEADER_B64}.${payloadB64}`;\n const signature = computeHmac(signingInput, clientSecret);\n\n return {\n token: `${signingInput}.${signature}`,\n expiresAt: exp,\n };\n}\n\n/**\n * Verifies a session token previously issued by signSessionToken.\n *\n * Checks (in order):\n * 1. Token structure (3 parts separated by \".\")\n * 2. Header declares HS256 + JWT\n * 3. HMAC-SHA256 signature matches (constant-time comparison)\n * 4. Token has not expired\n * 5. Issuer is \"whatalo\"\n * 6. Required claims are present\n *\n * @param token - Compact JWT string from the plugin frontend.\n * @param clientSecret - The app's client_secret used to sign the original token.\n * @returns Decoded and validated SessionTokenClaims.\n * @throws Error with a descriptive message on any validation failure.\n */\nexport function verifySessionToken(\n token: string,\n clientSecret: string,\n): SessionTokenClaims {\n if (!token || !clientSecret) {\n throw new Error(\"Token and client_secret are required\");\n }\n\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\"Invalid token structure: expected header.payload.signature\");\n }\n\n const [headerB64, payloadB64, receivedSig] = parts as [string, string, string];\n\n // 1. Validate header\n let header: { alg?: string; typ?: string };\n try {\n header = decodeB64<{ alg?: string; typ?: string }>(headerB64);\n } catch {\n throw new Error(\"Invalid token: malformed header\");\n }\n if (header.alg !== \"HS256\" || header.typ !== \"JWT\") {\n throw new Error(\"Invalid token: unsupported algorithm or type\");\n }\n\n // 2. Verify HMAC signature (constant-time to prevent timing attacks)\n const signingInput = `${headerB64}.${payloadB64}`;\n const expectedSig = computeHmac(signingInput, clientSecret);\n\n // Re-HMAC both sides to normalize to a fixed-length buffer regardless of\n // the received signature's actual length. This eliminates the timing\n // side-channel that would otherwise leak whether lengths match.\n const normalizeKey = expectedSig; // use expected sig as HMAC key for normalization\n const receivedNorm = createHmac(\"sha256\", normalizeKey).update(receivedSig).digest();\n const expectedNorm = createHmac(\"sha256\", normalizeKey).update(expectedSig).digest();\n\n if (!timingSafeEqual(receivedNorm, expectedNorm)) {\n throw new Error(\"Invalid token: signature mismatch\");\n }\n\n // 3. Decode claims\n let claims: SessionTokenClaims;\n try {\n claims = decodeB64<SessionTokenClaims>(payloadB64);\n } catch {\n throw new Error(\"Invalid token: malformed payload\");\n }\n\n // 4. Check expiration (with 30-second clock skew tolerance for distributed systems)\n const now = Math.floor(Date.now() / 1000);\n const CLOCK_SKEW_SECONDS = 30;\n if (typeof claims.exp !== \"number\" || claims.exp + CLOCK_SKEW_SECONDS <= now) {\n throw new Error(\"Token has expired\");\n }\n\n // 5. Check issuer\n if (claims.iss !== \"whatalo\") {\n throw new Error(`Invalid token: unexpected issuer \"${claims.iss}\"`);\n }\n\n // 6. Validate required claims\n const required: Array<keyof SessionTokenClaims> = [\n \"aud\", \"sub\", \"iat\", \"jti\", \"storeId\", \"appId\", \"scopes\", \"installationId\",\n ];\n for (const key of required) {\n if (claims[key] === undefined || claims[key] === null) {\n throw new Error(`Invalid token: missing required claim \"${key}\"`);\n }\n }\n\n if (!Array.isArray(claims.scopes)) {\n throw new Error(\"Invalid token: scopes must be an array\");\n }\n\n return claims;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACiBA,yBAAyD;AAiDzD,IAAM,aAAa,OAAO;AAAA,EACxB,KAAK,UAAU,EAAE,KAAK,SAAS,KAAK,MAAM,CAAC;AAC7C,EAAE,SAAS,WAAW;AAYtB,SAAS,UAAa,SAAoB;AACxC,QAAM,OAAO,OAAO,KAAK,SAAS,WAAW,EAAE,SAAS,MAAM;AAC9D,SAAO,KAAK,MAAM,IAAI;AACxB;AAMA,SAAS,YAAY,MAAc,QAAwB;AACzD,aAAO,+BAAW,UAAU,MAAM,EAAE,OAAO,MAAM,MAAM,EAAE,OAAO,KAAK;AACvE;AAiEO,SAAS,mBACd,OACA,cACoB;AACpB,MAAI,CAAC,SAAS,CAAC,cAAc;AAC3B,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACxD;AAEA,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI,MAAM,4DAA4D;AAAA,EAC9E;AAEA,QAAM,CAAC,WAAW,YAAY,WAAW,IAAI;AAG7C,MAAI;AACJ,MAAI;AACF,aAAS,UAA0C,SAAS;AAAA,EAC9D,QAAQ;AACN,UAAM,IAAI,MAAM,iCAAiC;AAAA,EACnD;AACA,MAAI,OAAO,QAAQ,WAAW,OAAO,QAAQ,OAAO;AAClD,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AAGA,QAAM,eAAe,GAAG,SAAS,IAAI,UAAU;AAC/C,QAAM,cAAc,YAAY,cAAc,YAAY;AAK1D,QAAM,eAAe;AACrB,QAAM,mBAAe,+BAAW,UAAU,YAAY,EAAE,OAAO,WAAW,EAAE,OAAO;AACnF,QAAM,mBAAe,+BAAW,UAAU,YAAY,EAAE,OAAO,WAAW,EAAE,OAAO;AAEnF,MAAI,KAAC,oCAAgB,cAAc,YAAY,GAAG;AAChD,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AAGA,MAAI;AACJ,MAAI;AACF,aAAS,UAA8B,UAAU;AAAA,EACnD,QAAQ;AACN,UAAM,IAAI,MAAM,kCAAkC;AAAA,EACpD;AAGA,QAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,QAAM,qBAAqB;AAC3B,MAAI,OAAO,OAAO,QAAQ,YAAY,OAAO,MAAM,sBAAsB,KAAK;AAC5E,UAAM,IAAI,MAAM,mBAAmB;AAAA,EACrC;AAGA,MAAI,OAAO,QAAQ,WAAW;AAC5B,UAAM,IAAI,MAAM,qCAAqC,OAAO,GAAG,GAAG;AAAA,EACpE;AAGA,QAAM,WAA4C;AAAA,IAChD;AAAA,IAAO;AAAA,IAAO;AAAA,IAAO;AAAA,IAAO;AAAA,IAAW;AAAA,IAAS;AAAA,IAAU;AAAA,EAC5D;AACA,aAAW,OAAO,UAAU;AAC1B,QAAI,OAAO,GAAG,MAAM,UAAa,OAAO,GAAG,MAAM,MAAM;AACrD,YAAM,IAAI,MAAM,0CAA0C,GAAG,GAAG;AAAA,IAClE;AAAA,EACF;AAEA,MAAI,CAAC,MAAM,QAAQ,OAAO,MAAM,GAAG;AACjC,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AAEA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SessionTokenClaims, verifySessionToken as verifyWhataloSessionToken } from '../session-token.cjs';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SessionTokenClaims, verifySessionToken as verifyWhataloSessionToken } from '../session-token.js';
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/session-token.ts
|
|
2
|
+
import { createHmac, timingSafeEqual, randomBytes } from "crypto";
|
|
3
|
+
var HEADER_B64 = Buffer.from(
|
|
4
|
+
JSON.stringify({ alg: "HS256", typ: "JWT" })
|
|
5
|
+
).toString("base64url");
|
|
6
|
+
function decodeB64(encoded) {
|
|
7
|
+
const json = Buffer.from(encoded, "base64url").toString("utf8");
|
|
8
|
+
return JSON.parse(json);
|
|
9
|
+
}
|
|
10
|
+
function computeHmac(data, secret) {
|
|
11
|
+
return createHmac("sha256", secret).update(data, "utf8").digest("hex");
|
|
12
|
+
}
|
|
13
|
+
function verifySessionToken(token, clientSecret) {
|
|
14
|
+
if (!token || !clientSecret) {
|
|
15
|
+
throw new Error("Token and client_secret are required");
|
|
16
|
+
}
|
|
17
|
+
const parts = token.split(".");
|
|
18
|
+
if (parts.length !== 3) {
|
|
19
|
+
throw new Error("Invalid token structure: expected header.payload.signature");
|
|
20
|
+
}
|
|
21
|
+
const [headerB64, payloadB64, receivedSig] = parts;
|
|
22
|
+
let header;
|
|
23
|
+
try {
|
|
24
|
+
header = decodeB64(headerB64);
|
|
25
|
+
} catch {
|
|
26
|
+
throw new Error("Invalid token: malformed header");
|
|
27
|
+
}
|
|
28
|
+
if (header.alg !== "HS256" || header.typ !== "JWT") {
|
|
29
|
+
throw new Error("Invalid token: unsupported algorithm or type");
|
|
30
|
+
}
|
|
31
|
+
const signingInput = `${headerB64}.${payloadB64}`;
|
|
32
|
+
const expectedSig = computeHmac(signingInput, clientSecret);
|
|
33
|
+
const normalizeKey = expectedSig;
|
|
34
|
+
const receivedNorm = createHmac("sha256", normalizeKey).update(receivedSig).digest();
|
|
35
|
+
const expectedNorm = createHmac("sha256", normalizeKey).update(expectedSig).digest();
|
|
36
|
+
if (!timingSafeEqual(receivedNorm, expectedNorm)) {
|
|
37
|
+
throw new Error("Invalid token: signature mismatch");
|
|
38
|
+
}
|
|
39
|
+
let claims;
|
|
40
|
+
try {
|
|
41
|
+
claims = decodeB64(payloadB64);
|
|
42
|
+
} catch {
|
|
43
|
+
throw new Error("Invalid token: malformed payload");
|
|
44
|
+
}
|
|
45
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
46
|
+
const CLOCK_SKEW_SECONDS = 30;
|
|
47
|
+
if (typeof claims.exp !== "number" || claims.exp + CLOCK_SKEW_SECONDS <= now) {
|
|
48
|
+
throw new Error("Token has expired");
|
|
49
|
+
}
|
|
50
|
+
if (claims.iss !== "whatalo") {
|
|
51
|
+
throw new Error(`Invalid token: unexpected issuer "${claims.iss}"`);
|
|
52
|
+
}
|
|
53
|
+
const required = [
|
|
54
|
+
"aud",
|
|
55
|
+
"sub",
|
|
56
|
+
"iat",
|
|
57
|
+
"jti",
|
|
58
|
+
"storeId",
|
|
59
|
+
"appId",
|
|
60
|
+
"scopes",
|
|
61
|
+
"installationId"
|
|
62
|
+
];
|
|
63
|
+
for (const key of required) {
|
|
64
|
+
if (claims[key] === void 0 || claims[key] === null) {
|
|
65
|
+
throw new Error(`Invalid token: missing required claim "${key}"`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!Array.isArray(claims.scopes)) {
|
|
69
|
+
throw new Error("Invalid token: scopes must be an array");
|
|
70
|
+
}
|
|
71
|
+
return claims;
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
verifySessionToken as verifyWhataloSessionToken
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=verify-session-token.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/session-token.ts"],"sourcesContent":["/**\n * Session Token — server-side only.\n *\n * Signs and verifies short-lived HMAC-SHA256 JWTs used by plugin iframes to\n * authenticate against their own backend. The token is issued by the Whatalo\n * admin host and forwarded by the plugin frontend to its server, which\n * validates the signature using the app's client_secret.\n *\n * Architecture:\n * Admin host ──issues──► iframe (via App Bridge)\n * iframe frontend ──sends──► plugin backend\n * plugin backend ──validates──► calls Whatalo public API with its own key\n *\n * This module uses ONLY Node.js built-in `node:crypto`. No external dependencies.\n * It must NEVER be imported in browser code (import from server entry points only).\n */\n\nimport { createHmac, timingSafeEqual, randomBytes } from \"node:crypto\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * JWT claims embedded in every session token.\n * All string fields are required so the verifier can validate them fully.\n */\nexport interface SessionTokenClaims {\n /** Issuer — always \"whatalo\" */\n iss: \"whatalo\";\n /** Audience — the app's client_id (marketplace_apps.slug) */\n aud: string;\n /** Subject — the store's public_id */\n sub: string;\n /** Expiration time (Unix seconds) */\n exp: number;\n /** Issued-at time (Unix seconds) */\n iat: number;\n /** JWT ID — unique nonce to prevent replay attacks */\n jti: string;\n /** Store public identifier (same as sub, kept explicit for SDK consumers) */\n storeId: string;\n /** App/plugin public identifier (slug) */\n appId: string;\n /** Permission scopes granted at install time */\n scopes: string[];\n /** app_installations.id for the specific install record */\n installationId: string;\n}\n\n/** Output of signSessionToken — includes the raw token and its expiry. */\nexport interface SignedSessionToken {\n /** Compact JWT string (header.payload.signature) */\n token: string;\n /** Expiration as Unix seconds (same as claims.exp) */\n expiresAt: number;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Token lifetime in seconds. Short by design — the bridge auto-refreshes. */\nconst TOKEN_TTL_SECONDS = 300; // 5 minutes\n\n/** Fixed header for all session tokens (alg=HS256, typ=JWT). */\nconst HEADER_B64 = Buffer.from(\n JSON.stringify({ alg: \"HS256\", typ: \"JWT\" }),\n).toString(\"base64url\");\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Encodes an object as a base64url JSON string (no padding). */\nfunction encodeB64(obj: unknown): string {\n return Buffer.from(JSON.stringify(obj)).toString(\"base64url\");\n}\n\n/** Decodes a base64url string to a plain object. Throws on malformed input. */\nfunction decodeB64<T>(encoded: string): T {\n const json = Buffer.from(encoded, \"base64url\").toString(\"utf8\");\n return JSON.parse(json) as T;\n}\n\n/**\n * Computes HMAC-SHA256 over `data` using `secret` and returns the hex digest.\n * Uses the app's client_secret as the key — never a platform-wide secret.\n */\nfunction computeHmac(data: string, secret: string): string {\n return createHmac(\"sha256\", secret).update(data, \"utf8\").digest(\"hex\");\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Signs a session token for the given claims using the app's client_secret.\n *\n * @param payload - Token claims (storeId, appId, scopes, installationId, aud).\n * exp/iat/jti/iss are injected automatically.\n * @param clientSecret - The app's client_secret from marketplace_apps table.\n * @returns Signed JWT and its expiry timestamp.\n */\nexport function signSessionToken(\n payload: Omit<SessionTokenClaims, \"iss\" | \"iat\" | \"exp\" | \"jti\">,\n clientSecret: string,\n): SignedSessionToken {\n if (!clientSecret || clientSecret.length === 0) {\n throw new Error(\"client_secret must be a non-empty string\");\n }\n\n const now = Math.floor(Date.now() / 1000);\n const exp = now + TOKEN_TTL_SECONDS;\n\n const claims: SessionTokenClaims = {\n iss: \"whatalo\",\n aud: payload.aud,\n sub: payload.sub,\n iat: now,\n exp,\n // 16 random bytes → 32-char hex string used as the JWT ID (jti)\n jti: randomBytes(16).toString(\"hex\"),\n storeId: payload.storeId,\n appId: payload.appId,\n scopes: payload.scopes,\n installationId: payload.installationId,\n };\n\n const payloadB64 = encodeB64(claims);\n const signingInput = `${HEADER_B64}.${payloadB64}`;\n const signature = computeHmac(signingInput, clientSecret);\n\n return {\n token: `${signingInput}.${signature}`,\n expiresAt: exp,\n };\n}\n\n/**\n * Verifies a session token previously issued by signSessionToken.\n *\n * Checks (in order):\n * 1. Token structure (3 parts separated by \".\")\n * 2. Header declares HS256 + JWT\n * 3. HMAC-SHA256 signature matches (constant-time comparison)\n * 4. Token has not expired\n * 5. Issuer is \"whatalo\"\n * 6. Required claims are present\n *\n * @param token - Compact JWT string from the plugin frontend.\n * @param clientSecret - The app's client_secret used to sign the original token.\n * @returns Decoded and validated SessionTokenClaims.\n * @throws Error with a descriptive message on any validation failure.\n */\nexport function verifySessionToken(\n token: string,\n clientSecret: string,\n): SessionTokenClaims {\n if (!token || !clientSecret) {\n throw new Error(\"Token and client_secret are required\");\n }\n\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\"Invalid token structure: expected header.payload.signature\");\n }\n\n const [headerB64, payloadB64, receivedSig] = parts as [string, string, string];\n\n // 1. Validate header\n let header: { alg?: string; typ?: string };\n try {\n header = decodeB64<{ alg?: string; typ?: string }>(headerB64);\n } catch {\n throw new Error(\"Invalid token: malformed header\");\n }\n if (header.alg !== \"HS256\" || header.typ !== \"JWT\") {\n throw new Error(\"Invalid token: unsupported algorithm or type\");\n }\n\n // 2. Verify HMAC signature (constant-time to prevent timing attacks)\n const signingInput = `${headerB64}.${payloadB64}`;\n const expectedSig = computeHmac(signingInput, clientSecret);\n\n // Re-HMAC both sides to normalize to a fixed-length buffer regardless of\n // the received signature's actual length. This eliminates the timing\n // side-channel that would otherwise leak whether lengths match.\n const normalizeKey = expectedSig; // use expected sig as HMAC key for normalization\n const receivedNorm = createHmac(\"sha256\", normalizeKey).update(receivedSig).digest();\n const expectedNorm = createHmac(\"sha256\", normalizeKey).update(expectedSig).digest();\n\n if (!timingSafeEqual(receivedNorm, expectedNorm)) {\n throw new Error(\"Invalid token: signature mismatch\");\n }\n\n // 3. Decode claims\n let claims: SessionTokenClaims;\n try {\n claims = decodeB64<SessionTokenClaims>(payloadB64);\n } catch {\n throw new Error(\"Invalid token: malformed payload\");\n }\n\n // 4. Check expiration (with 30-second clock skew tolerance for distributed systems)\n const now = Math.floor(Date.now() / 1000);\n const CLOCK_SKEW_SECONDS = 30;\n if (typeof claims.exp !== \"number\" || claims.exp + CLOCK_SKEW_SECONDS <= now) {\n throw new Error(\"Token has expired\");\n }\n\n // 5. Check issuer\n if (claims.iss !== \"whatalo\") {\n throw new Error(`Invalid token: unexpected issuer \"${claims.iss}\"`);\n }\n\n // 6. Validate required claims\n const required: Array<keyof SessionTokenClaims> = [\n \"aud\", \"sub\", \"iat\", \"jti\", \"storeId\", \"appId\", \"scopes\", \"installationId\",\n ];\n for (const key of required) {\n if (claims[key] === undefined || claims[key] === null) {\n throw new Error(`Invalid token: missing required claim \"${key}\"`);\n }\n }\n\n if (!Array.isArray(claims.scopes)) {\n throw new Error(\"Invalid token: scopes must be an array\");\n }\n\n return claims;\n}\n"],"mappings":";AAiBA,SAAS,YAAY,iBAAiB,mBAAmB;AAiDzD,IAAM,aAAa,OAAO;AAAA,EACxB,KAAK,UAAU,EAAE,KAAK,SAAS,KAAK,MAAM,CAAC;AAC7C,EAAE,SAAS,WAAW;AAYtB,SAAS,UAAa,SAAoB;AACxC,QAAM,OAAO,OAAO,KAAK,SAAS,WAAW,EAAE,SAAS,MAAM;AAC9D,SAAO,KAAK,MAAM,IAAI;AACxB;AAMA,SAAS,YAAY,MAAc,QAAwB;AACzD,SAAO,WAAW,UAAU,MAAM,EAAE,OAAO,MAAM,MAAM,EAAE,OAAO,KAAK;AACvE;AAiEO,SAAS,mBACd,OACA,cACoB;AACpB,MAAI,CAAC,SAAS,CAAC,cAAc;AAC3B,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACxD;AAEA,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI,MAAM,4DAA4D;AAAA,EAC9E;AAEA,QAAM,CAAC,WAAW,YAAY,WAAW,IAAI;AAG7C,MAAI;AACJ,MAAI;AACF,aAAS,UAA0C,SAAS;AAAA,EAC9D,QAAQ;AACN,UAAM,IAAI,MAAM,iCAAiC;AAAA,EACnD;AACA,MAAI,OAAO,QAAQ,WAAW,OAAO,QAAQ,OAAO;AAClD,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AAGA,QAAM,eAAe,GAAG,SAAS,IAAI,UAAU;AAC/C,QAAM,cAAc,YAAY,cAAc,YAAY;AAK1D,QAAM,eAAe;AACrB,QAAM,eAAe,WAAW,UAAU,YAAY,EAAE,OAAO,WAAW,EAAE,OAAO;AACnF,QAAM,eAAe,WAAW,UAAU,YAAY,EAAE,OAAO,WAAW,EAAE,OAAO;AAEnF,MAAI,CAAC,gBAAgB,cAAc,YAAY,GAAG;AAChD,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AAGA,MAAI;AACJ,MAAI;AACF,aAAS,UAA8B,UAAU;AAAA,EACnD,QAAQ;AACN,UAAM,IAAI,MAAM,kCAAkC;AAAA,EACpD;AAGA,QAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,QAAM,qBAAqB;AAC3B,MAAI,OAAO,OAAO,QAAQ,YAAY,OAAO,MAAM,sBAAsB,KAAK;AAC5E,UAAM,IAAI,MAAM,mBAAmB;AAAA,EACrC;AAGA,MAAI,OAAO,QAAQ,WAAW;AAC5B,UAAM,IAAI,MAAM,qCAAqC,OAAO,GAAG,GAAG;AAAA,EACpE;AAGA,QAAM,WAA4C;AAAA,IAChD;AAAA,IAAO;AAAA,IAAO;AAAA,IAAO;AAAA,IAAO;AAAA,IAAW;AAAA,IAAS;AAAA,IAAU;AAAA,EAC5D;AACA,aAAW,OAAO,UAAU;AAC1B,QAAI,OAAO,GAAG,MAAM,UAAa,OAAO,GAAG,MAAM,MAAM;AACrD,YAAM,IAAI,MAAM,0CAA0C,GAAG,GAAG;AAAA,IAClE;AAAA,EACF;AAEA,MAAI,CAAC,MAAM,QAAQ,OAAO,MAAM,GAAG;AACjC,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AAEA,SAAO;AACT;","names":[]}
|