@whop/iframe
Version:
Powers communication between Whop and your embedded app
455 lines (445 loc) • 12.8 kB
JavaScript
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