UNPKG

@croct/plug

Version:

A fully-featured devkit for building natively personalized applications.

157 lines (156 loc) 5.08 kB
import { formatCause } from "@croct/sdk/error"; import { Token } from "./sdk/token.js"; import { PREVIEW_WIDGET_ORIGIN, PREVIEW_WIDGET_URL } from "./constants.js"; const PREVIEW_PARAMETER = "croct-preview"; const PREVIEW_EXIT = "exit"; const _PreviewPlugin = class _PreviewPlugin { constructor(configuration) { this.cleanUp = []; this.tokenStore = configuration.tokenStore; this.logger = configuration.logger; } enable() { const url = new URL(window.location.href); const previewData = url.searchParams.get(PREVIEW_PARAMETER); if (previewData !== null) { this.updateToken(previewData); this.updateUrl(); setTimeout(this.updateUrl, 500); } const token = this.tokenStore.getToken(); if (token !== null) { this.insertWidget(this.getWidgetUrl(token)); } } disable() { const callbacks = this.cleanUp.splice(0); callbacks.forEach((cleanUp) => cleanUp()); } updateToken(data) { if (data === PREVIEW_EXIT) { this.logger.debug("Exiting preview mode."); this.tokenStore.setToken(null); return; } try { let token = Token.parse(data); const { exp } = token.getPayload(); if (exp !== void 0 && exp <= Date.now() / 1e3) { this.logger.debug("Preview token expired."); token = null; } this.logger.debug("Preview token updated."); this.tokenStore.setToken(token); } catch (error) { this.tokenStore.setToken(null); this.logger.warn(`Invalid preview token: ${formatCause(error)}`); } } getWidgetUrl(token) { const params = this.getWidgetParams(token); let queryString = params.toString(); if (queryString !== "") { queryString = `?${queryString}`; } return `${PREVIEW_WIDGET_URL}${queryString}`; } getWidgetParams(token) { const { metadata = {} } = token.getPayload(); const params = new URLSearchParams(); if (metadata === null || typeof metadata !== "object" || Array.isArray(metadata)) { return params; } for (const [key, param] of Object.entries(_PreviewPlugin.PREVIEW_PARAMS)) { const value = metadata[key]; if (typeof value === "string") { params.set(param, value); } } return params; } insertWidget(url) { const widget = this.createWidget(url); const onMessage = (event) => { if (event.origin !== PREVIEW_WIDGET_ORIGIN) { return; } switch (event.data.type) { case "croct:preview:leave": { this.tokenStore.setToken(null); const exitUrl = new URL(window.location.href); exitUrl.searchParams.set(PREVIEW_PARAMETER, PREVIEW_EXIT); window.location.replace(exitUrl.toString()); break; } case "croct:preview:resize": widget.style.width = `${event.data.width}px`; widget.style.height = `${event.data.height}px`; break; } }; window.addEventListener("message", onMessage); this.cleanUp.push(() => window.removeEventListener("message", onMessage)); const insert = () => { const container = document.body; container.prepend(widget); this.cleanUp.push(() => container.removeChild(widget)); this.logger.debug("Preview widget mounted."); const observer = new MutationObserver(() => { if (!container.contains(widget)) { container.prepend(widget); this.logger.debug("Preview widget removed from DOM, reinserting."); } }); observer.observe(container, { childList: true }); this.cleanUp.unshift(() => observer.disconnect()); }; if (document.readyState !== "loading") { insert(); } else { this.logger.debug("Waiting for document to be ready."); this.cleanUp.push(() => window.removeEventListener("DOMContentLoaded", insert)); window.addEventListener("DOMContentLoaded", insert); } } updateUrl() { const url = new URL(window.location.href); if (url.searchParams.has(PREVIEW_PARAMETER)) { url.searchParams.delete(PREVIEW_PARAMETER); window.history.replaceState({}, "", url.toString()); } } createWidget(url) { const widget = document.createElement("iframe"); widget.setAttribute("src", url); widget.setAttribute("sandbox", "allow-scripts allow-same-origin"); widget.style.position = "fixed"; widget.style.width = "0px"; widget.style.height = "0px"; widget.style.right = "0"; widget.style.bottom = "0"; widget.style.border = "0"; widget.style.overflow = "hidden"; widget.style.zIndex = "2147483647"; return widget; } }; _PreviewPlugin.PREVIEW_PARAMS = { previewMode: "previewMode", experienceName: "experience", experimentName: "experiment", audienceName: "audience", variantName: "variant", locale: "locale" }; let PreviewPlugin = _PreviewPlugin; const factory = (props) => { const { sdk } = props; return new PreviewPlugin({ tokenStore: props.sdk.previewTokenStore, logger: sdk.getLogger() }); }; export { PreviewPlugin, factory };