UNPKG

@whop/iframe

Version:

Powers communication between Whop and your embedded app

455 lines (445 loc) 12.8 kB
var __defProp = Object.defineProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/sdk/apps-server.ts import { z as z2 } from "zod"; // src/sdk/utils.ts import { z } from "zod"; var withError = (schema, error) => { return z.discriminatedUnion("status", [ z.object({ status: z.literal("ok"), data: schema }), z.object({ status: z.literal("error"), error }) ]); }; var frostedV2Theme = z.object({ appearance: z.enum(["light", "dark"]), accentColor: z.string(), dangerColor: z.string(), grayColor: z.string(), infoColor: z.string(), successColor: z.string(), warningColor: z.string() }).partial(); // src/sdk/apps-server.ts var appsServerSchema = z2.discriminatedUnion("event", [ z2.object({ event: z2.literal("appPing"), request: z2.literal("app_ping"), response: z2.literal("app_pong") }), z2.object({ event: z2.literal("onColorThemeChange"), request: frostedV2Theme, response: z2.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); }; } }; } function reactNativeClientTransport({ postMessage, targetOrigin }) { return { send(event, data, { remoteAppId, localAppId }) { postMessage( JSON.stringify({ event, data, libId: MESSAGE_TAG, receiverAppId: remoteAppId, senderAppId: localAppId }) ); }, recv(handler, { localAppId, remoteAppId }) { const listener = (event) => { const dataString = typeof event.data === "string" ? event.data : null; if (!dataString) return; const data = JSON.parse(dataString); if (event.origin !== targetOrigin || !data || !data.event || !data.data || data.libId !== MESSAGE_TAG || data.receiverAppId !== localAppId || data.senderAppId !== remoteAppId) { return; } handler(data.event, data.data); }; if (typeof window === "undefined") { console.warn( "No window. Is the SDK running on a server without a global window object?" ); return; } window.addEventListener("message", listener); return () => { window.removeEventListener("message", listener); }; } }; } // src/sdk/transport/index.ts var transport_exports = {}; __export(transport_exports, { MESSAGE_TAG: () => MESSAGE_TAG, TimeoutError: () => TimeoutError, createHandler: () => createHandler, createSDK: () => createSDK, postmessageTransport: () => postmessageTransport }); // src/sdk/transport/handler.ts function createHandler({ schema, forceCompleteness, handlers }) { let eventHandler; createSDK({ clientSchema: void 0, serverSchema: schema, localAppId: "client", remoteAppId: "server", forceCompleteness, serverImplementation: handlers, transport: { send() { }, recv(handler) { eventHandler = handler; } } }); return (event, data) => { return eventHandler(`server:${event}`, data); }; } // src/sdk/whop-server.ts import { z as z3 } from "zod"; var whopServerSchema = z3.discriminatedUnion("event", [ z3.object({ event: z3.literal("ping"), request: z3.literal("ping"), response: z3.literal("pong") }), z3.object({ event: z3.literal("getTopLevelUrlData"), request: z3.object({}).optional(), response: z3.object({ companyRoute: z3.string(), experienceRoute: z3.string(), experienceId: z3.string(), viewType: z3.enum(["app", "admin", "analytics", "preview"]), baseHref: z3.string(), fullHref: z3.string() }) }), z3.object({ event: z3.literal("openExternalUrl"), request: z3.object({ newTab: z3.boolean().optional(), url: z3.string() }), response: z3.literal("ok") }), z3.object({ event: z3.literal("onHrefChange"), request: z3.object({ href: z3.string() }), response: z3.literal("ok") }), z3.object({ event: z3.literal("inAppPurchase"), request: z3.object({ /** * ID returned from the `chargeUser` API call. * @example "ch_1234567890" */ id: z3.string().optional(), /** * ID of the plan returned from the `chargeUser` API call. * @example "plan_1234567890" */ planId: z3.string() }), response: withError( z3.object({ sessionId: z3.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: z3.string() }), z3.string() ) }), z3.object({ event: z3.literal("closeApp"), request: z3.null(), response: z3.literal("ok") }), z3.object({ event: z3.literal("openHelpChat"), request: z3.null(), response: z3.literal("ok") }), z3.object({ event: z3.literal("getColorTheme"), request: z3.void(), response: frostedV2Theme }), z3.object({ event: z3.literal("earliestUnreadNotification"), request: z3.object({ experienceId: z3.string() }), response: z3.object({ externalId: z3.string() }).nullable() }), z3.object({ event: z3.literal("markExperienceRead"), request: z3.object({ experienceId: z3.string(), notificationExternalId: z3.string().optional() }), response: z3.literal("ok") }), z3.object({ event: z3.literal("performHaptic"), request: z3.object({ type: z3.enum(["selection", "impact", "notification"]), style: z3.enum(["light", "medium", "heavy"]) }), response: z3.literal("ok") }) ]); export { appsServerSchema, createSDK, postmessageTransport, reactNativeClientTransport, transport_exports, whopServerSchema }; //# sourceMappingURL=chunk-DPDPUJJX.mjs.map