@whatalo/plugin-sdk 1.0.0 → 1.2.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/adapters/express.d.cts +3 -3
- package/dist/adapters/express.d.ts +3 -3
- package/dist/adapters/hono.d.cts +3 -3
- package/dist/adapters/hono.d.ts +3 -3
- package/dist/adapters/nextjs.d.cts +3 -3
- package/dist/adapters/nextjs.d.ts +3 -3
- 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 +8 -0
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +3 -4
- package/dist/client/index.d.ts +3 -4
- package/dist/client/index.mjs +8 -0
- package/dist/client/index.mjs.map +1 -1
- package/dist/index.cjs +197 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.mjs +193 -3
- package/dist/index.mjs.map +1 -1
- package/dist/manifest/index.d.cts +2 -2
- package/dist/manifest/index.d.ts +2 -2
- 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/dist/{types-D2Efg3EG.d.ts → types-C9mg4aQg.d.ts} +1 -1
- package/dist/{types-Db_BeRCj.d.cts → types-DMuHSL_a.d.cts} +1 -1
- package/dist/{types-DZ659i6f.d.ts → types-DTjA3FHe.d.ts} +1 -1
- package/dist/{types-M1eLMz6w.d.cts → types-DcmArIC2.d.cts} +16 -3
- package/dist/{types-M1eLMz6w.d.ts → types-DcmArIC2.d.ts} +16 -3
- package/dist/{types-DdqKKyqX.d.cts → types-tHUO9u-c.d.cts} +1 -1
- package/dist/webhooks/index.d.cts +2 -2
- package/dist/webhooks/index.d.ts +2 -2
- package/package.json +11 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { W as WebhookHandlerOptions } from '../types-
|
|
2
|
-
import '../types-
|
|
3
|
-
import '../types-
|
|
1
|
+
import { W as WebhookHandlerOptions } from '../types-DMuHSL_a.cjs';
|
|
2
|
+
import '../types-tHUO9u-c.cjs';
|
|
3
|
+
import '../types-DcmArIC2.cjs';
|
|
4
4
|
|
|
5
5
|
type ExpressLikeRequest = {
|
|
6
6
|
body: Buffer | string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { W as WebhookHandlerOptions } from '../types-
|
|
2
|
-
import '../types-
|
|
3
|
-
import '../types-
|
|
1
|
+
import { W as WebhookHandlerOptions } from '../types-C9mg4aQg.js';
|
|
2
|
+
import '../types-DTjA3FHe.js';
|
|
3
|
+
import '../types-DcmArIC2.js';
|
|
4
4
|
|
|
5
5
|
type ExpressLikeRequest = {
|
|
6
6
|
body: Buffer | string;
|
package/dist/adapters/hono.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { W as WebhookHandlerOptions } from '../types-
|
|
2
|
-
import '../types-
|
|
3
|
-
import '../types-
|
|
1
|
+
import { W as WebhookHandlerOptions } from '../types-DMuHSL_a.cjs';
|
|
2
|
+
import '../types-tHUO9u-c.cjs';
|
|
3
|
+
import '../types-DcmArIC2.cjs';
|
|
4
4
|
|
|
5
5
|
type HonoContextLike = {
|
|
6
6
|
req: {
|
package/dist/adapters/hono.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { W as WebhookHandlerOptions } from '../types-
|
|
2
|
-
import '../types-
|
|
3
|
-
import '../types-
|
|
1
|
+
import { W as WebhookHandlerOptions } from '../types-C9mg4aQg.js';
|
|
2
|
+
import '../types-DTjA3FHe.js';
|
|
3
|
+
import '../types-DcmArIC2.js';
|
|
4
4
|
|
|
5
5
|
type HonoContextLike = {
|
|
6
6
|
req: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { W as WebhookHandlerOptions } from '../types-
|
|
2
|
-
import '../types-
|
|
3
|
-
import '../types-
|
|
1
|
+
import { W as WebhookHandlerOptions } from '../types-DMuHSL_a.cjs';
|
|
2
|
+
import '../types-tHUO9u-c.cjs';
|
|
3
|
+
import '../types-DcmArIC2.cjs';
|
|
4
4
|
|
|
5
5
|
declare function createWebhookHandler(options: WebhookHandlerOptions): (request: Request) => Promise<Response>;
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { W as WebhookHandlerOptions } from '../types-
|
|
2
|
-
import '../types-
|
|
3
|
-
import '../types-
|
|
1
|
+
import { W as WebhookHandlerOptions } from '../types-C9mg4aQg.js';
|
|
2
|
+
import '../types-DTjA3FHe.js';
|
|
3
|
+
import '../types-DcmArIC2.js';
|
|
4
4
|
|
|
5
5
|
declare function createWebhookHandler(options: WebhookHandlerOptions): (request: Request) => Promise<Response>;
|
|
6
6
|
|
package/dist/bridge/index.cjs
CHANGED
|
@@ -21,9 +21,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var bridge_exports = {};
|
|
22
22
|
__export(bridge_exports, {
|
|
23
23
|
BILLING_OPERATION: () => BILLING_OPERATION,
|
|
24
|
+
DATA_RESOURCE_SCOPE: () => DATA_RESOURCE_SCOPE,
|
|
25
|
+
clearSessionTokenCache: () => clearSessionTokenCache,
|
|
26
|
+
sessionToken: () => sessionToken,
|
|
24
27
|
useAppBridge: () => useAppBridge,
|
|
25
28
|
useWhataloAction: () => useWhataloAction,
|
|
26
|
-
useWhataloContext: () => useWhataloContext
|
|
29
|
+
useWhataloContext: () => useWhataloContext,
|
|
30
|
+
useWhataloData: () => useWhataloData
|
|
27
31
|
});
|
|
28
32
|
module.exports = __toCommonJS(bridge_exports);
|
|
29
33
|
|
|
@@ -34,6 +38,13 @@ var import_react2 = require("react");
|
|
|
34
38
|
var import_react = require("react");
|
|
35
39
|
|
|
36
40
|
// src/bridge/types.ts
|
|
41
|
+
var DATA_RESOURCE_SCOPE = {
|
|
42
|
+
products: "read:products",
|
|
43
|
+
orders: "read:orders",
|
|
44
|
+
customers: "read:customers",
|
|
45
|
+
categories: "read:products"
|
|
46
|
+
// categories share the products scope
|
|
47
|
+
};
|
|
37
48
|
var BILLING_OPERATION = {
|
|
38
49
|
GET_PLANS: "getPlans",
|
|
39
50
|
GET_SUBSCRIPTION: "getSubscription",
|
|
@@ -64,8 +75,9 @@ function attachInitListener() {
|
|
|
64
75
|
if (event.source !== window.parent) return;
|
|
65
76
|
if (!event.data || typeof event.data !== "object") return;
|
|
66
77
|
if (event.data.type !== "whatalo:init") return;
|
|
67
|
-
const origin = event.
|
|
68
|
-
if (typeof origin !== "string" || !origin) return;
|
|
78
|
+
const origin = event.origin;
|
|
79
|
+
if (typeof origin !== "string" || !origin || origin === "null") return;
|
|
80
|
+
if (parentOrigin !== null) return;
|
|
69
81
|
parentOrigin = origin;
|
|
70
82
|
for (const resolve of originResolvers) {
|
|
71
83
|
resolve(origin);
|
|
@@ -73,6 +85,9 @@ function attachInitListener() {
|
|
|
73
85
|
originResolvers.length = 0;
|
|
74
86
|
});
|
|
75
87
|
}
|
|
88
|
+
if (typeof window !== "undefined") {
|
|
89
|
+
attachInitListener();
|
|
90
|
+
}
|
|
76
91
|
function useWhataloAction() {
|
|
77
92
|
const pendingAcks = (0, import_react.useRef)(
|
|
78
93
|
/* @__PURE__ */ new Map()
|
|
@@ -80,6 +95,7 @@ function useWhataloAction() {
|
|
|
80
95
|
(0, import_react.useEffect)(() => {
|
|
81
96
|
attachInitListener();
|
|
82
97
|
const handleMessage = (event) => {
|
|
98
|
+
if (parentOrigin && event.origin !== parentOrigin) return;
|
|
83
99
|
if (!event.data || typeof event.data !== "object") return;
|
|
84
100
|
const msg = event.data;
|
|
85
101
|
if (msg.type !== "whatalo:ack") return;
|
|
@@ -280,11 +296,181 @@ function useAppBridge() {
|
|
|
280
296
|
billing: actions.billing
|
|
281
297
|
};
|
|
282
298
|
}
|
|
299
|
+
|
|
300
|
+
// src/bridge/session-token.ts
|
|
301
|
+
var cachedToken = null;
|
|
302
|
+
var inFlightRequest = null;
|
|
303
|
+
var REFRESH_BUFFER_SECONDS = 60;
|
|
304
|
+
var REQUEST_TIMEOUT_MS = 1e4;
|
|
305
|
+
function isCacheValid(cached) {
|
|
306
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
307
|
+
return cached.expiresAt - nowSeconds > REFRESH_BUFFER_SECONDS;
|
|
308
|
+
}
|
|
309
|
+
async function requestFromHost() {
|
|
310
|
+
const requestId = crypto.randomUUID();
|
|
311
|
+
const parentOrigin2 = await waitForParentOrigin();
|
|
312
|
+
return new Promise((resolve, reject) => {
|
|
313
|
+
const timeout = setTimeout(() => {
|
|
314
|
+
window.removeEventListener("message", handler);
|
|
315
|
+
reject(new Error("whatalo.sessionToken() timed out: no response from host"));
|
|
316
|
+
}, REQUEST_TIMEOUT_MS);
|
|
317
|
+
function handler(event) {
|
|
318
|
+
if (event.origin !== parentOrigin2) return;
|
|
319
|
+
if (!event.data || typeof event.data !== "object") return;
|
|
320
|
+
const msg = event.data;
|
|
321
|
+
if (msg.type !== "whatalo:session_token_response" || msg.requestId !== requestId) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
clearTimeout(timeout);
|
|
325
|
+
window.removeEventListener("message", handler);
|
|
326
|
+
if (msg.error) {
|
|
327
|
+
reject(new Error(`Session token error: ${msg.error}`));
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (typeof msg.token !== "string" || typeof msg.expiresAt !== "number") {
|
|
331
|
+
reject(new Error("Session token response missing token or expiresAt"));
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
335
|
+
if (msg.expiresAt <= nowSeconds) {
|
|
336
|
+
reject(new Error("Host returned an already-expired session token"));
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
resolve({ token: msg.token, expiresAt: msg.expiresAt });
|
|
340
|
+
}
|
|
341
|
+
window.addEventListener("message", handler);
|
|
342
|
+
window.parent.postMessage(
|
|
343
|
+
{ type: "whatalo:session_token_request", requestId },
|
|
344
|
+
parentOrigin2
|
|
345
|
+
);
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
async function sessionToken() {
|
|
349
|
+
if (cachedToken && isCacheValid(cachedToken)) {
|
|
350
|
+
return cachedToken;
|
|
351
|
+
}
|
|
352
|
+
if (inFlightRequest) {
|
|
353
|
+
return inFlightRequest;
|
|
354
|
+
}
|
|
355
|
+
inFlightRequest = requestFromHost().then((result) => {
|
|
356
|
+
cachedToken = result;
|
|
357
|
+
return result;
|
|
358
|
+
}).finally(() => {
|
|
359
|
+
inFlightRequest = null;
|
|
360
|
+
});
|
|
361
|
+
return inFlightRequest;
|
|
362
|
+
}
|
|
363
|
+
function clearSessionTokenCache() {
|
|
364
|
+
cachedToken = null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// src/bridge/use-whatalo-data.ts
|
|
368
|
+
var import_react3 = require("react");
|
|
369
|
+
function extractAckData(ack) {
|
|
370
|
+
if (!ack.success) {
|
|
371
|
+
throw new Error(ack.error ?? "Data request failed");
|
|
372
|
+
}
|
|
373
|
+
return ack.data;
|
|
374
|
+
}
|
|
375
|
+
function useWhataloData() {
|
|
376
|
+
const { sendAction } = useWhataloAction();
|
|
377
|
+
const fetchResource = (0, import_react3.useCallback)(
|
|
378
|
+
(resource, operation, params, id) => sendAction("data", {
|
|
379
|
+
resource,
|
|
380
|
+
operation,
|
|
381
|
+
...id ? { id } : {},
|
|
382
|
+
...params && Object.keys(params).length > 0 ? { params } : {}
|
|
383
|
+
}),
|
|
384
|
+
[sendAction]
|
|
385
|
+
);
|
|
386
|
+
const productsList = (0, import_react3.useCallback)(
|
|
387
|
+
async (params) => {
|
|
388
|
+
const ack = await fetchResource(
|
|
389
|
+
"products",
|
|
390
|
+
"list",
|
|
391
|
+
params
|
|
392
|
+
);
|
|
393
|
+
return extractAckData(ack);
|
|
394
|
+
},
|
|
395
|
+
[fetchResource]
|
|
396
|
+
);
|
|
397
|
+
const productsGet = (0, import_react3.useCallback)(
|
|
398
|
+
async (id) => {
|
|
399
|
+
const ack = await fetchResource("products", "get", void 0, id);
|
|
400
|
+
return extractAckData(ack);
|
|
401
|
+
},
|
|
402
|
+
[fetchResource]
|
|
403
|
+
);
|
|
404
|
+
const ordersList = (0, import_react3.useCallback)(
|
|
405
|
+
async (params) => {
|
|
406
|
+
const ack = await fetchResource(
|
|
407
|
+
"orders",
|
|
408
|
+
"list",
|
|
409
|
+
params
|
|
410
|
+
);
|
|
411
|
+
return extractAckData(ack);
|
|
412
|
+
},
|
|
413
|
+
[fetchResource]
|
|
414
|
+
);
|
|
415
|
+
const ordersGet = (0, import_react3.useCallback)(
|
|
416
|
+
async (id) => {
|
|
417
|
+
const ack = await fetchResource("orders", "get", void 0, id);
|
|
418
|
+
return extractAckData(ack);
|
|
419
|
+
},
|
|
420
|
+
[fetchResource]
|
|
421
|
+
);
|
|
422
|
+
const customersList = (0, import_react3.useCallback)(
|
|
423
|
+
async (params) => {
|
|
424
|
+
const ack = await fetchResource(
|
|
425
|
+
"customers",
|
|
426
|
+
"list",
|
|
427
|
+
params
|
|
428
|
+
);
|
|
429
|
+
return extractAckData(ack);
|
|
430
|
+
},
|
|
431
|
+
[fetchResource]
|
|
432
|
+
);
|
|
433
|
+
const customersGet = (0, import_react3.useCallback)(
|
|
434
|
+
async (id) => {
|
|
435
|
+
const ack = await fetchResource("customers", "get", void 0, id);
|
|
436
|
+
return extractAckData(ack);
|
|
437
|
+
},
|
|
438
|
+
[fetchResource]
|
|
439
|
+
);
|
|
440
|
+
const categoriesList = (0, import_react3.useCallback)(
|
|
441
|
+
async (params) => {
|
|
442
|
+
const ack = await fetchResource(
|
|
443
|
+
"categories",
|
|
444
|
+
"list",
|
|
445
|
+
params
|
|
446
|
+
);
|
|
447
|
+
return extractAckData(ack);
|
|
448
|
+
},
|
|
449
|
+
[fetchResource]
|
|
450
|
+
);
|
|
451
|
+
const categoriesGet = (0, import_react3.useCallback)(
|
|
452
|
+
async (id) => {
|
|
453
|
+
const ack = await fetchResource("categories", "get", void 0, id);
|
|
454
|
+
return extractAckData(ack);
|
|
455
|
+
},
|
|
456
|
+
[fetchResource]
|
|
457
|
+
);
|
|
458
|
+
return {
|
|
459
|
+
products: { list: productsList, get: productsGet },
|
|
460
|
+
orders: { list: ordersList, get: ordersGet },
|
|
461
|
+
customers: { list: customersList, get: customersGet },
|
|
462
|
+
categories: { list: categoriesList, get: categoriesGet }
|
|
463
|
+
};
|
|
464
|
+
}
|
|
283
465
|
// Annotate the CommonJS export names for ESM import in node:
|
|
284
466
|
0 && (module.exports = {
|
|
285
467
|
BILLING_OPERATION,
|
|
468
|
+
DATA_RESOURCE_SCOPE,
|
|
469
|
+
clearSessionTokenCache,
|
|
470
|
+
sessionToken,
|
|
286
471
|
useAppBridge,
|
|
287
472
|
useWhataloAction,
|
|
288
|
-
useWhataloContext
|
|
473
|
+
useWhataloContext,
|
|
474
|
+
useWhataloData
|
|
289
475
|
});
|
|
290
476
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/bridge/index.ts","../../src/bridge/use-whatalo-context.ts","../../src/bridge/use-whatalo-action.ts","../../src/bridge/types.ts","../../src/bridge/use-app-bridge.ts"],"sourcesContent":["// Bridge — React hooks for plugin ↔ admin communication via postMessage\nexport { useAppBridge } from \"./use-app-bridge.js\";\nexport type { AppBridge, AppBridgeToast, AppBridgeModal } from \"./use-app-bridge.js\";\n\nexport { useWhataloContext } from \"./use-whatalo-context.js\";\nexport type { UseWhataloContextReturn } from \"./use-whatalo-context.js\";\n\nexport { useWhataloAction } from \"./use-whatalo-action.js\";\nexport type {\n UseWhataloActionReturn,\n BillingActions,\n} from \"./use-whatalo-action.js\";\n\nexport { BILLING_OPERATION } from \"./types.js\";\nexport type {\n WhataloContext,\n WhataloUser,\n BridgeAction,\n ContextMessage,\n ActionMessage,\n ActionAck,\n ToastPayload,\n NavigatePayload,\n ModalPayload,\n ResizePayload,\n // Billing types\n BillingOperation,\n BillingPayload,\n BillingPlanResponse,\n BillingSubscriptionResponse,\n} from \"./types.js\";\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"],"mappings":"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","names":["import_react"]}
|
|
1
|
+
{"version":3,"sources":["../../src/bridge/index.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"],"sourcesContent":["// Bridge — React hooks for plugin ↔ admin communication via postMessage\nexport { useAppBridge } from \"./use-app-bridge.js\";\nexport type { AppBridge, AppBridgeToast, AppBridgeModal } from \"./use-app-bridge.js\";\n\n// Session token (browser-side — requires App Bridge / postMessage)\nexport { sessionToken, clearSessionTokenCache } from \"./session-token.js\";\nexport type { SessionTokenResult } from \"./session-token.js\";\n\nexport { useWhataloContext } from \"./use-whatalo-context.js\";\nexport type { UseWhataloContextReturn } from \"./use-whatalo-context.js\";\n\nexport { useWhataloAction } from \"./use-whatalo-action.js\";\nexport type {\n UseWhataloActionReturn,\n BillingActions,\n} from \"./use-whatalo-action.js\";\n\nexport { useWhataloData } from \"./use-whatalo-data.js\";\nexport type {\n WhataloDataReturn,\n ResourceAccessor,\n} from \"./use-whatalo-data.js\";\n\nexport { BILLING_OPERATION, DATA_RESOURCE_SCOPE } from \"./types.js\";\nexport type {\n WhataloContext,\n WhataloUser,\n BridgeAction,\n ContextMessage,\n ActionMessage,\n ActionAck,\n ToastPayload,\n NavigatePayload,\n ModalPayload,\n ResizePayload,\n // Billing types\n BillingOperation,\n BillingPayload,\n BillingPlanResponse,\n BillingSubscriptionResponse,\n // Data types (frontend-only plugins)\n DataResource,\n DataOperation,\n DataPayload,\n DataListResponse,\n DataSingleResponse,\n} from \"./types.js\";\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"],"mappings":"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","names":["import_react","parentOrigin","import_react"]}
|
package/dist/bridge/index.d.cts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { a as Product, L as ListProductsParams, O as Order, b as ListOrdersParams, f as Customer, e as ListCustomersParams, h as Category, g as ListCategoriesParams } from '../types-DcmArIC2.cjs';
|
|
2
|
+
|
|
1
3
|
/** Context data sent from the admin host to a plugin iframe */
|
|
2
4
|
interface WhataloContext {
|
|
3
5
|
/** Store UUID */
|
|
@@ -37,7 +39,36 @@ interface WhataloUser {
|
|
|
37
39
|
role: "owner" | "admin" | "editor" | "staff" | "viewer";
|
|
38
40
|
}
|
|
39
41
|
/** Available bridge actions the plugin can dispatch */
|
|
40
|
-
type BridgeAction = "navigate" | "toast" | "modal" | "resize" | "ready" | "billing";
|
|
42
|
+
type BridgeAction = "navigate" | "toast" | "modal" | "resize" | "ready" | "billing" | "data";
|
|
43
|
+
/** Resources available through the data bridge. */
|
|
44
|
+
type DataResource = "products" | "orders" | "customers" | "categories";
|
|
45
|
+
/** Operations available on data resources. */
|
|
46
|
+
type DataOperation = "list" | "get";
|
|
47
|
+
/** Scope required to read each resource type. */
|
|
48
|
+
declare const DATA_RESOURCE_SCOPE: Record<DataResource, string>;
|
|
49
|
+
/** Payload sent from the plugin to the host for data actions. */
|
|
50
|
+
interface DataPayload {
|
|
51
|
+
resource: DataResource;
|
|
52
|
+
operation: DataOperation;
|
|
53
|
+
/** Resource ID — required for "get" operations. */
|
|
54
|
+
id?: string;
|
|
55
|
+
/** Pagination and filtering params for "list" operations. */
|
|
56
|
+
params?: Record<string, unknown>;
|
|
57
|
+
}
|
|
58
|
+
/** Paginated list response returned by the data bridge. */
|
|
59
|
+
interface DataListResponse<T = Record<string, unknown>> {
|
|
60
|
+
data: T[];
|
|
61
|
+
pagination: {
|
|
62
|
+
page: number;
|
|
63
|
+
per_page: number;
|
|
64
|
+
total: number;
|
|
65
|
+
total_pages: number;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Single resource response returned by the data bridge. */
|
|
69
|
+
interface DataSingleResponse<T = Record<string, unknown>> {
|
|
70
|
+
data: T;
|
|
71
|
+
}
|
|
41
72
|
/** All billing operations supported by the bridge. createUsageRecord is reserved for v2. */
|
|
42
73
|
declare const BILLING_OPERATION: {
|
|
43
74
|
readonly GET_PLANS: "getPlans";
|
|
@@ -223,6 +254,49 @@ interface AppBridge {
|
|
|
223
254
|
*/
|
|
224
255
|
declare function useAppBridge(): AppBridge;
|
|
225
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Session Token Bridge — browser-side.
|
|
259
|
+
*
|
|
260
|
+
* Provides the sessionToken() function that plugin frontends use to obtain a
|
|
261
|
+
* short-lived JWT from the admin host via postMessage. The token is cached and
|
|
262
|
+
* automatically refreshed when it is within 60 seconds of expiry.
|
|
263
|
+
*
|
|
264
|
+
* Protocol (postMessage):
|
|
265
|
+
* Plugin → Host: { type: "whatalo:session_token_request", requestId: string }
|
|
266
|
+
* Host → Plugin: { type: "whatalo:session_token_response", requestId: string,
|
|
267
|
+
* token: string, expiresAt: number }
|
|
268
|
+
* OR: { type: "whatalo:session_token_response", requestId: string,
|
|
269
|
+
* error: string }
|
|
270
|
+
*/
|
|
271
|
+
/** Resolved value from sessionToken() */
|
|
272
|
+
interface SessionTokenResult {
|
|
273
|
+
/** Compact JWT string (header.payload.signature) */
|
|
274
|
+
token: string;
|
|
275
|
+
/** Token expiry as Unix seconds */
|
|
276
|
+
expiresAt: number;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Returns a valid Whatalo session token, fetching a new one from the admin host
|
|
280
|
+
* when the cache is empty or within REFRESH_BUFFER_SECONDS of expiry.
|
|
281
|
+
*
|
|
282
|
+
* Multiple concurrent calls share a single in-flight request to avoid hammering
|
|
283
|
+
* the host with duplicate postMessage round-trips.
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* ```typescript
|
|
287
|
+
* const { token } = await sessionToken();
|
|
288
|
+
* const res = await fetch("https://my-plugin.com/api/orders", {
|
|
289
|
+
* headers: { Authorization: `Bearer ${token}` },
|
|
290
|
+
* });
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
declare function sessionToken(): Promise<SessionTokenResult>;
|
|
294
|
+
/**
|
|
295
|
+
* Clears the cached session token, forcing the next call to sessionToken()
|
|
296
|
+
* to fetch a fresh one from the host. Useful after authentication errors.
|
|
297
|
+
*/
|
|
298
|
+
declare function clearSessionTokenCache(): void;
|
|
299
|
+
|
|
226
300
|
interface UseWhataloContextReturn extends WhataloContext {
|
|
227
301
|
/** Whether the plugin has received context from the admin host */
|
|
228
302
|
isReady: boolean;
|
|
@@ -233,4 +307,37 @@ interface UseWhataloContextReturn extends WhataloContext {
|
|
|
233
307
|
*/
|
|
234
308
|
declare function useWhataloContext(): UseWhataloContextReturn;
|
|
235
309
|
|
|
236
|
-
|
|
310
|
+
/**
|
|
311
|
+
* Provides typed, scope-enforced read access to store data through the
|
|
312
|
+
* App Bridge. Designed for frontend-only plugins that don't have their own
|
|
313
|
+
* backend server.
|
|
314
|
+
*
|
|
315
|
+
* Every request is proxied through the admin host (PluginFrame), which
|
|
316
|
+
* checks `granted_scopes` on the installation before executing the query.
|
|
317
|
+
*
|
|
318
|
+
* All methods are read-only. Write operations require the plugin to have
|
|
319
|
+
* its own backend and use the WhataloClient with an API key.
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* ```tsx
|
|
323
|
+
* const { products, orders } = useWhataloData();
|
|
324
|
+
*
|
|
325
|
+
* const allProducts = await products.list({ page: 1, per_page: 20 });
|
|
326
|
+
* const single = await products.get("prod_abc123");
|
|
327
|
+
* ```
|
|
328
|
+
*/
|
|
329
|
+
declare function useWhataloData(): WhataloDataReturn;
|
|
330
|
+
/** Resource accessor with list and get methods. */
|
|
331
|
+
interface ResourceAccessor<T, P = Record<string, unknown>> {
|
|
332
|
+
list(params?: P): Promise<DataListResponse<T>>;
|
|
333
|
+
get(id: string): Promise<DataSingleResponse<T>>;
|
|
334
|
+
}
|
|
335
|
+
/** Return type of useWhataloData. */
|
|
336
|
+
interface WhataloDataReturn {
|
|
337
|
+
products: ResourceAccessor<Product, ListProductsParams>;
|
|
338
|
+
orders: ResourceAccessor<Order, ListOrdersParams>;
|
|
339
|
+
customers: ResourceAccessor<Customer, ListCustomersParams>;
|
|
340
|
+
categories: ResourceAccessor<Category, ListCategoriesParams>;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export { type ActionAck, type ActionMessage, type AppBridge, type AppBridgeModal, type AppBridgeToast, BILLING_OPERATION, type BillingActions, type BillingOperation, type BillingPayload, type BillingPlanResponse, type BillingSubscriptionResponse, type BridgeAction, type ContextMessage, DATA_RESOURCE_SCOPE, type DataListResponse, type DataOperation, type DataPayload, type DataResource, type DataSingleResponse, type ModalPayload, type NavigatePayload, type ResizePayload, type ResourceAccessor, type SessionTokenResult, type ToastPayload, type UseWhataloActionReturn, type UseWhataloContextReturn, type WhataloContext, type WhataloDataReturn, type WhataloUser, clearSessionTokenCache, sessionToken, useAppBridge, useWhataloAction, useWhataloContext, useWhataloData };
|