UNPKG

@whop/iframe

Version:

Powers communication between Whop and your embedded app

417 lines (408 loc) 13 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/host.ts var host_exports = {}; __export(host_exports, { createSdkHost: () => createSdkHost }); module.exports = __toCommonJS(host_exports); // src/sdk/apps-server.ts var import_zod2 = require("zod"); // src/sdk/utils.ts var import_zod = require("zod"); var withError = (schema, error) => { return import_zod.z.discriminatedUnion("status", [ import_zod.z.object({ status: import_zod.z.literal("ok"), data: schema }), import_zod.z.object({ status: import_zod.z.literal("error"), error }) ]); }; var frostedV2Theme = import_zod.z.object({ appearance: import_zod.z.enum(["light", "dark"]), accentColor: import_zod.z.string(), dangerColor: import_zod.z.string(), grayColor: import_zod.z.string(), infoColor: import_zod.z.string(), successColor: import_zod.z.string(), warningColor: import_zod.z.string() }).partial(); // src/sdk/apps-server.ts var appsServerSchema = import_zod2.z.discriminatedUnion("event", [ import_zod2.z.object({ event: import_zod2.z.literal("appPing"), request: import_zod2.z.literal("app_ping"), response: import_zod2.z.literal("app_pong") }), import_zod2.z.object({ event: import_zod2.z.literal("onColorThemeChange"), request: frostedV2Theme, response: import_zod2.z.void() }) ]); // src/sdk/transport/utils.ts var TimeoutError = class extends Error { constructor() { super("Timeout"); } }; function randomId(length) { const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; let str = ""; for (let i = 0; i < length; i++) { str += alphabet[Math.floor(Math.random() * alphabet.length)]; } return str; } // src/sdk/transport/sdk.ts function createSDK({ clientSchema, serverSchema, serverComplete, transport, timeout = 1e3, timeouts, localAppId, remoteAppId, serverImplementation = {}, serverMiddleware }) { const callbacks = []; const keys = clientSchema?.options.map( (option) => option._def.shape().event._def.value ) ?? []; const client = Object.fromEntries( keys.map((key) => [ key, async (req) => { const eventId = `${localAppId}:${key}:${randomId(8)}`; console.debug("[typed-transport] app. Created eventId", eventId); const responseData = new Promise((resolve, reject) => { const customTimeout = timeouts?.[key]; const timeoutId = setTimeout(() => { const index = callbacks.findIndex((cb) => cb.id === eventId); if (index !== -1) callbacks.splice(index, 1); if (serverComplete) { console.debug("[typed-transport] app. Timeout error"); reject(new TimeoutError()); } else resolve(void 0); }, customTimeout ?? timeout); if (customTimeout && customTimeout > timeout && !serverComplete) { const timeoutId2 = setTimeout(() => { const index = callbacks.findIndex((cb) => cb.id === eventId); if (index !== -1) callbacks.splice(index, 1); resolve(void 0); }, timeout); callbacks.push({ id: `${eventId}:processing`, resolve: () => clearTimeout(timeoutId2) }); } callbacks.push({ id: eventId, resolve: (data2) => { clearTimeout(timeoutId); resolve(data2); } }); }); console.debug("[typed-transport] app sending event", { eventId, localAppId, remoteAppId }); await transport.send?.(eventId, req, { localAppId, remoteAppId }); const data = await responseData; console.debug("[typed-transport] received response", data); return data; } ]) ); const cleanupRecv = transport.recv( async (event, dataAny) => { const [app, key, _randomId, type] = event.split(":"); if (app === localAppId) { const idx = callbacks.findIndex((cb2) => cb2.id === event); if (idx === -1) return; const dataSchema = clientSchema?.optionsMap.get(key); if (!dataSchema) return; const cb = callbacks[idx]; if (type === "processing") { cb.resolve(void 0); } else { const data = dataSchema.shape.response.parse(dataAny); callbacks.splice(idx, 1); cb.resolve(data); } } else if (app === remoteAppId) { if (serverImplementation === void 0) return; let handler = serverImplementation[key]; if (serverMiddleware) { for (let i = serverMiddleware.length - 1; i >= 0; i--) { const middlewareDef = serverMiddleware[i]; const middleware = middlewareDef[key]; if (!middleware) continue; const ref = handler; handler = (data2) => middleware(data2, ref); } } if (!handler) return; const dataSchema = serverSchema?.optionsMap.get(key); if (!dataSchema) return; const data = dataSchema.shape.request.parse(dataAny); const timeoutId = setTimeout(async () => { await transport.send( `${event}:processing`, {}, { localAppId, remoteAppId } ); }, 50); const response = await handler(data); clearTimeout(timeoutId); await transport.send(event, response, { localAppId, remoteAppId }); return response; } }, { localAppId, remoteAppId } ); const cleanupFunctions = []; if (transport.cleanup) cleanupFunctions.push(transport.cleanup); if (cleanupRecv) cleanupFunctions.push(cleanupRecv); client._cleanupTransport = () => { for (const fn of cleanupFunctions) fn(); }; return client; } // src/sdk/transport/postmessage.ts var MESSAGE_TAG = "typed-transport"; function postmessageTransport({ remoteWindow, targetOrigins }) { return { send(event, data, { remoteAppId, localAppId }) { if (!remoteWindow) { throw new Error( "No remote window. Is the SDK running on a server without a global window object?" ); } console.debug( "[typed-transport] postmessagetransport. Sending event", event, data ); console.debug( "[typed-transport] postmessagetransport. target origins =", targetOrigins ); for (const targetOrigin of targetOrigins) { console.debug("[typed-transport] remoteWindow.postMessage", { event, libId: MESSAGE_TAG, receiverAppId: remoteAppId, senderAppId: localAppId }); console.debug( "[typed-transport] remoteWindow.postMessage.data", data, JSON.stringify(data) ); remoteWindow.postMessage( { event, data, libId: MESSAGE_TAG, receiverAppId: remoteAppId, senderAppId: localAppId }, { targetOrigin } ); } if (targetOrigins.length === 0) { remoteWindow.postMessage({ event, data, libId: MESSAGE_TAG, receiverAppId: remoteAppId, senderAppId: localAppId }); } }, recv(handler, { localAppId, remoteAppId }) { const listener = (event) => { console.debug( "[typed-transport] postmessagetransport. Receiving event", event ); if (event.source !== remoteWindow || !targetOrigins.includes(event.origin) && targetOrigins.length > 0 || !event.data || !event.data.event || event.data.libId !== MESSAGE_TAG || event.data.receiverAppId !== localAppId || event.data.senderAppId !== remoteAppId) { return; } handler(event.data.event, event.data.data); }; if (typeof window === "undefined") { return; } window.addEventListener("message", listener); return () => { window.removeEventListener("message", listener); }; } }; } // src/sdk/whop-server.ts var import_zod3 = require("zod"); var whopServerSchema = import_zod3.z.discriminatedUnion("event", [ import_zod3.z.object({ event: import_zod3.z.literal("ping"), request: import_zod3.z.literal("ping"), response: import_zod3.z.literal("pong") }), import_zod3.z.object({ event: import_zod3.z.literal("getTopLevelUrlData"), request: import_zod3.z.object({}).optional(), response: import_zod3.z.object({ companyRoute: import_zod3.z.string(), experienceRoute: import_zod3.z.string(), experienceId: import_zod3.z.string(), viewType: import_zod3.z.enum(["app", "admin", "analytics", "preview"]), baseHref: import_zod3.z.string(), fullHref: import_zod3.z.string() }) }), import_zod3.z.object({ event: import_zod3.z.literal("openExternalUrl"), request: import_zod3.z.object({ newTab: import_zod3.z.boolean().optional(), url: import_zod3.z.string() }), response: import_zod3.z.literal("ok") }), import_zod3.z.object({ event: import_zod3.z.literal("onHrefChange"), request: import_zod3.z.object({ href: import_zod3.z.string() }), response: import_zod3.z.literal("ok") }), import_zod3.z.object({ event: import_zod3.z.literal("inAppPurchase"), request: import_zod3.z.object({ /** * ID returned from the `chargeUser` API call. * @example "ch_1234567890" */ id: import_zod3.z.string().optional(), /** * ID of the plan returned from the `chargeUser` API call. * @example "plan_1234567890" */ planId: import_zod3.z.string() }), response: withError( import_zod3.z.object({ sessionId: import_zod3.z.string(), /** * The receipt ID can be used to verify the purchase. * * NOTE: When receiving payments you should always listen to webhooks as a fallback * to process the payment. Do not solely rely on the client to process payments. The receipt ID * can be used to deduplicate payment events. */ receiptId: import_zod3.z.string() }), import_zod3.z.string() ) }), import_zod3.z.object({ event: import_zod3.z.literal("closeApp"), request: import_zod3.z.null(), response: import_zod3.z.literal("ok") }), import_zod3.z.object({ event: import_zod3.z.literal("openHelpChat"), request: import_zod3.z.null(), response: import_zod3.z.literal("ok") }), import_zod3.z.object({ event: import_zod3.z.literal("getColorTheme"), request: import_zod3.z.void(), response: frostedV2Theme }), import_zod3.z.object({ event: import_zod3.z.literal("earliestUnreadNotification"), request: import_zod3.z.object({ experienceId: import_zod3.z.string() }), response: import_zod3.z.object({ externalId: import_zod3.z.string() }).nullable() }), import_zod3.z.object({ event: import_zod3.z.literal("markExperienceRead"), request: import_zod3.z.object({ experienceId: import_zod3.z.string(), notificationExternalId: import_zod3.z.string().optional() }), response: import_zod3.z.literal("ok") }), import_zod3.z.object({ event: import_zod3.z.literal("performHaptic"), request: import_zod3.z.object({ type: import_zod3.z.enum(["selection", "impact", "notification"]), style: import_zod3.z.enum(["light", "medium", "heavy"]) }), response: import_zod3.z.literal("ok") }) ]); // src/host.ts function createSdkHost({ appId, onMessage, remoteWindow, remoteOrigin }) { return createSDK({ clientSchema: appsServerSchema, serverSchema: whopServerSchema, forceCompleteness: true, serverImplementation: onMessage, localAppId: "app_whop", remoteAppId: appId, transport: postmessageTransport({ remoteWindow, targetOrigins: [remoteOrigin] }), serverComplete: false, timeout: 200 }); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { createSdkHost }); //# sourceMappingURL=host.js.map