@croct/plug
Version:
A fully-featured devkit for building natively personalized applications.
157 lines (156 loc) • 5.08 kB
JavaScript
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
};