@pexip-engage-public/plugin 1.1.26 → 1.1.27-canary-20250729085737
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/events/index.d.ts +11 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +2 -0
- package/dist/events/index.js.map +1 -1
- package/dist/instance/PexipEngagePluginFrame.d.ts +11 -1
- package/dist/instance/PexipEngagePluginFrame.d.ts.map +1 -1
- package/dist/instance/PexipEngagePluginFrame.js +46 -6
- package/dist/instance/PexipEngagePluginFrame.js.map +1 -1
- package/dist/instance/PluginInstance.d.ts.map +1 -1
- package/dist/instance/PluginInstance.js +34 -35
- package/dist/instance/PluginInstance.js.map +1 -1
- package/dist/resizer/child.d.ts +15 -0
- package/dist/resizer/child.d.ts.map +1 -0
- package/dist/resizer/child.js +94 -0
- package/dist/resizer/child.js.map +1 -0
- package/dist/resizer/common.d.ts +27 -0
- package/dist/resizer/common.d.ts.map +1 -0
- package/dist/resizer/common.js +4 -0
- package/dist/resizer/common.js.map +1 -0
- package/dist/resizer/parent.d.ts +3 -0
- package/dist/resizer/parent.d.ts.map +1 -0
- package/dist/resizer/parent.js +64 -0
- package/dist/resizer/parent.js.map +1 -0
- package/dist/state/PluginState.schema.d.ts +6 -6
- package/package.json +9 -8
- package/src/events/index.ts +14 -0
- package/src/instance/PexipEngagePluginFrame.ts +66 -8
- package/src/instance/PluginInstance.ts +36 -44
- package/src/resizer/child.ts +138 -0
- package/src/resizer/common.ts +28 -0
- package/src/resizer/parent.ts +102 -0
- package/src/@types/iframe-resizer.iframeResizer.d.ts +0 -211
|
@@ -675,15 +675,15 @@ export declare const PluginStateSchema: z.ZodObject<{
|
|
|
675
675
|
subject: z.ZodOptional<z.ZodLiteral<true>>;
|
|
676
676
|
}, "strip", z.ZodTypeAny, {
|
|
677
677
|
questions?: true | undefined;
|
|
678
|
+
office?: true | undefined;
|
|
678
679
|
meetingType?: true | undefined;
|
|
679
680
|
employee?: true | undefined;
|
|
680
|
-
office?: true | undefined;
|
|
681
681
|
subject?: true | undefined;
|
|
682
682
|
}, {
|
|
683
683
|
questions?: true | undefined;
|
|
684
|
+
office?: true | undefined;
|
|
684
685
|
meetingType?: true | undefined;
|
|
685
686
|
employee?: true | undefined;
|
|
686
|
-
office?: true | undefined;
|
|
687
687
|
subject?: true | undefined;
|
|
688
688
|
}>>;
|
|
689
689
|
subjectGroups: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
@@ -858,9 +858,9 @@ export declare const PluginStateSchema: z.ZodObject<{
|
|
|
858
858
|
schedulable: boolean;
|
|
859
859
|
skipped: {
|
|
860
860
|
questions?: true | undefined;
|
|
861
|
+
office?: true | undefined;
|
|
861
862
|
meetingType?: true | undefined;
|
|
862
863
|
employee?: true | undefined;
|
|
863
|
-
office?: true | undefined;
|
|
864
864
|
subject?: true | undefined;
|
|
865
865
|
};
|
|
866
866
|
customer?: {
|
|
@@ -896,6 +896,7 @@ export declare const PluginStateSchema: z.ZodObject<{
|
|
|
896
896
|
questions?: Record<string, string | string[]> | undefined;
|
|
897
897
|
subjectGroups?: string[] | undefined;
|
|
898
898
|
subjects?: string[] | undefined;
|
|
899
|
+
metadata?: Record<string, unknown> | undefined;
|
|
899
900
|
appointmentId?: string | undefined;
|
|
900
901
|
callbackRequestId?: string | undefined;
|
|
901
902
|
employeeId?: string | undefined;
|
|
@@ -907,7 +908,6 @@ export declare const PluginStateSchema: z.ZodObject<{
|
|
|
907
908
|
leadSegmentId?: string | undefined;
|
|
908
909
|
listingId?: string | undefined;
|
|
909
910
|
meetingType?: "VIDEO" | "PHONE" | "ON_LOCATION" | "OFFICE" | undefined;
|
|
910
|
-
metadata?: Record<string, unknown> | undefined;
|
|
911
911
|
officeId?: string | undefined;
|
|
912
912
|
subjectId?: string | undefined;
|
|
913
913
|
warning?: string | undefined;
|
|
@@ -1067,6 +1067,7 @@ export declare const PluginStateSchema: z.ZodObject<{
|
|
|
1067
1067
|
}) & {
|
|
1068
1068
|
timeZone?: string | undefined;
|
|
1069
1069
|
}) | undefined;
|
|
1070
|
+
metadata?: Record<string, unknown> | undefined;
|
|
1070
1071
|
appointmentId?: string | undefined;
|
|
1071
1072
|
callbackRequestId?: string | undefined;
|
|
1072
1073
|
employeeId?: string | undefined;
|
|
@@ -1081,14 +1082,13 @@ export declare const PluginStateSchema: z.ZodObject<{
|
|
|
1081
1082
|
leadSegmentId?: string | undefined;
|
|
1082
1083
|
listingId?: string | undefined;
|
|
1083
1084
|
meetingType?: "VIDEO" | "PHONE" | "ON_LOCATION" | "OFFICE" | undefined;
|
|
1084
|
-
metadata?: Record<string, unknown> | undefined;
|
|
1085
1085
|
officeId?: string | undefined;
|
|
1086
1086
|
schedulable?: boolean | undefined;
|
|
1087
1087
|
skipped?: {
|
|
1088
1088
|
questions?: true | undefined;
|
|
1089
|
+
office?: true | undefined;
|
|
1089
1090
|
meetingType?: true | undefined;
|
|
1090
1091
|
employee?: true | undefined;
|
|
1091
|
-
office?: true | undefined;
|
|
1092
1092
|
subject?: true | undefined;
|
|
1093
1093
|
} | undefined;
|
|
1094
1094
|
subjectId?: string | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pexip-engage-public/plugin",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.27-canary-20250729085737",
|
|
4
4
|
"homepage": "https://github.com/skedify/frontend-mono/tree/develop/apps/booking-plugin/packages/plugin-public#readme",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/skedify/frontend-mono/issues"
|
|
@@ -22,12 +22,14 @@
|
|
|
22
22
|
"./configuration": "./dist/configuration/index.js",
|
|
23
23
|
"./configuration-parser": "./dist/configuration-parser/index.js",
|
|
24
24
|
"./configuration-parser/migrate": "./dist/configuration-parser/migrate-legacy-configuration.js",
|
|
25
|
-
"./events": "./dist/events/index.js",
|
|
26
|
-
"./instance": "./dist/instance/index.js",
|
|
27
|
-
"./state": "./dist/state/index.js",
|
|
28
25
|
"./constants": "./dist/constants.js",
|
|
29
26
|
"./encoding": "./dist/encoding.js",
|
|
30
|
-
"./
|
|
27
|
+
"./events": "./dist/events/index.js",
|
|
28
|
+
"./instance": "./dist/instance/index.js",
|
|
29
|
+
"./logger": "./dist/logger.js",
|
|
30
|
+
"./resizer-child": "./dist/resizer/child.js",
|
|
31
|
+
"./resizer-parent": "./dist/resizer/parent.js",
|
|
32
|
+
"./state": "./dist/state/index.js"
|
|
31
33
|
},
|
|
32
34
|
"files": [
|
|
33
35
|
"dist",
|
|
@@ -35,7 +37,6 @@
|
|
|
35
37
|
"src"
|
|
36
38
|
],
|
|
37
39
|
"dependencies": {
|
|
38
|
-
"iframe-resizer": "4.3.11",
|
|
39
40
|
"skedify-uri-encoding": "^2.1.2",
|
|
40
41
|
"zod": "^3.25.76",
|
|
41
42
|
"@pexip-engage-public/graphql": "1.1.9",
|
|
@@ -45,8 +46,8 @@
|
|
|
45
46
|
"@total-typescript/ts-reset": "^0.6.1",
|
|
46
47
|
"happy-dom": "^18.0.1",
|
|
47
48
|
"vitest": "^3.2.4",
|
|
48
|
-
"
|
|
49
|
-
"
|
|
49
|
+
"@pexip-engage/tsconfig": "0.1.1",
|
|
50
|
+
"eslint-config-pexip-engage": "1.1.27"
|
|
50
51
|
},
|
|
51
52
|
"volta": {
|
|
52
53
|
"extends": "../../../../package.json"
|
package/src/events/index.ts
CHANGED
|
@@ -1,2 +1,16 @@
|
|
|
1
|
+
import type { IFrameMessage, IFrameObjectMessage } from "./event-types.js";
|
|
2
|
+
|
|
1
3
|
// biome-ignore lint/performance/noReExportAll: types only, ignore
|
|
2
4
|
export * from "./event-types.js";
|
|
5
|
+
export const IFRAME_CHILD_MESSAGE = "iframe-child-message";
|
|
6
|
+
export const IFRAME_PARENT_MESSAGE = "iframe-parent-message";
|
|
7
|
+
|
|
8
|
+
export type IframeChildMessageEventData = {
|
|
9
|
+
type: typeof IFRAME_CHILD_MESSAGE;
|
|
10
|
+
message: IFrameObjectMessage;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type IframeParentMessageEventData = {
|
|
14
|
+
type: typeof IFRAME_PARENT_MESSAGE;
|
|
15
|
+
message: IFrameMessage;
|
|
16
|
+
};
|
|
@@ -1,22 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IFRAME_CHILD_MESSAGE,
|
|
3
|
+
IFRAME_PARENT_MESSAGE,
|
|
4
|
+
type IFrameMessage,
|
|
5
|
+
type IFrameObjectMessage,
|
|
6
|
+
} from "../events/index.js";
|
|
7
|
+
import { initialize } from "../resizer/parent.js";
|
|
8
|
+
|
|
1
9
|
const SPINNER_STYLE =
|
|
2
10
|
"<style>@keyframes spin{to{transform: rotate(360deg);}}.container{display: flex; align-items: center; justify-content: center; height: calc(700px - 1rem);}svg{fill: rgb(10 33 54); color: rgb(229 231 235); animation: spin 1s linear infinite;}</style>";
|
|
3
11
|
const SPINNER_DOM = `<div class="container"><svg aria-hidden="true" fill="none" viewBox="0 0 100 101" width="24px" height="24px" xmlns="http://www.w3.org/2000/svg"> <path fill="currentColor" d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" ></path> <path fill="currentFill" d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"></path></svg></div>`;
|
|
4
12
|
|
|
5
13
|
const spinnerHtml = `<!DOCTYPE html><html><head>${SPINNER_STYLE}</head><body>${SPINNER_DOM}</body></html>`;
|
|
6
|
-
|
|
7
|
-
const spinnerTemplate = document.createElement("template");
|
|
8
|
-
spinnerTemplate.innerHTML = `${SPINNER_STYLE}${SPINNER_DOM}`;
|
|
9
|
-
|
|
10
14
|
class PexipEngagePluginFrame extends HTMLElement {
|
|
11
15
|
readonly #shadowRoot: ShadowRoot;
|
|
16
|
+
static observedAttributes = ["src"];
|
|
17
|
+
#cleanup: (() => void) | null = null;
|
|
18
|
+
#iframe: HTMLIFrameElement | null = null;
|
|
19
|
+
|
|
12
20
|
constructor() {
|
|
13
21
|
super();
|
|
22
|
+
|
|
14
23
|
this.#shadowRoot = this.attachShadow({ mode: "open" });
|
|
15
|
-
this.#shadowRoot.appendChild(spinnerTemplate.content.cloneNode(true));
|
|
16
24
|
}
|
|
17
25
|
|
|
18
|
-
|
|
26
|
+
connectedCallback() {
|
|
27
|
+
console.log("PexipEngagePluginFrame connected");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
disconnectedCallback() {
|
|
31
|
+
console.log("PexipEngagePluginFrame disconnected");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
connectedMoveCallback() {
|
|
35
|
+
console.log("PexipEngagePluginFrame moved");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
adoptedCallback() {
|
|
39
|
+
console.log("PexipEngagePluginFrame adopted");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
|
|
43
|
+
console.log(`Attribute ${name} has changed from ${oldValue} to ${newValue}.`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
postMessage(message: IFrameObjectMessage) {
|
|
47
|
+
this.#iframe?.contentWindow?.postMessage({ message, type: IFRAME_CHILD_MESSAGE }, "*");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
validateMessageEvent(event: MessageEvent) {
|
|
51
|
+
const isTargetIframe =
|
|
52
|
+
Boolean(this.#iframe?.contentWindow && this.#iframe.contentWindow === event.source) &&
|
|
53
|
+
event.data.type === IFRAME_PARENT_MESSAGE;
|
|
54
|
+
|
|
55
|
+
if (!isTargetIframe) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const message = event.data.message as IFrameMessage;
|
|
60
|
+
|
|
61
|
+
return message;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
createPexipPlugin() {
|
|
65
|
+
this.#cleanup?.();
|
|
66
|
+
const src = this.getAttribute("src");
|
|
67
|
+
if (!src) throw new Error("Source URL is required to create the Pexip Engage Plugin frame.");
|
|
68
|
+
|
|
19
69
|
const iframe = document.createElement("iframe");
|
|
70
|
+
this.#iframe = iframe;
|
|
20
71
|
iframe.src = src;
|
|
21
72
|
iframe.style.border = "0px";
|
|
22
73
|
iframe.style.overflow = "hidden";
|
|
@@ -28,14 +79,21 @@ class PexipEngagePluginFrame extends HTMLElement {
|
|
|
28
79
|
iframe.referrerPolicy = "strict-origin-when-cross-origin";
|
|
29
80
|
iframe.allow = "clipboard-write; geolocation";
|
|
30
81
|
|
|
31
|
-
// iframe.loading = "lazy";
|
|
32
82
|
iframe.onload = () => iframe.removeAttribute("srcdoc");
|
|
33
83
|
iframe.srcdoc = spinnerHtml;
|
|
34
84
|
|
|
35
85
|
this.#shadowRoot.innerHTML = "";
|
|
36
86
|
this.#shadowRoot.appendChild(iframe);
|
|
37
87
|
|
|
38
|
-
|
|
88
|
+
const result = initialize(iframe);
|
|
89
|
+
this.#cleanup = () => {
|
|
90
|
+
result.unsubscribe();
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
unsubscribe() {
|
|
95
|
+
this.#cleanup?.();
|
|
96
|
+
this.#cleanup = null;
|
|
39
97
|
}
|
|
40
98
|
}
|
|
41
99
|
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import { getCurrentPosition } from "@pexip-engage-public/utils/get-current-position";
|
|
2
|
-
import resizer, {
|
|
3
|
-
type IFrameComponent,
|
|
4
|
-
type IFrameMessageData,
|
|
5
|
-
} from "iframe-resizer/js/iframeResizer.js";
|
|
6
|
-
|
|
7
2
|
import { parsePluginConfiguration } from "../configuration-parser/index.js";
|
|
8
3
|
import { encodeURIParameters, pluginSearchParams } from "../encoding.js";
|
|
9
4
|
import type {
|
|
@@ -27,13 +22,12 @@ import { getPexipEngagePluginFrame } from "./PexipEngagePluginFrame.js";
|
|
|
27
22
|
|
|
28
23
|
type PluginEventListener = (event: PluginCustomEvent) => unknown;
|
|
29
24
|
|
|
30
|
-
const PexipEngagePluginFrame = getPexipEngagePluginFrame();
|
|
31
|
-
|
|
32
25
|
export class PluginInstance {
|
|
33
|
-
#instance: IFrameComponent;
|
|
34
26
|
readonly #target: HTMLElement;
|
|
27
|
+
#element: InstanceType<ReturnType<typeof getPexipEngagePluginFrame>> | null = null;
|
|
35
28
|
#state: StateUpdateMessage["payload"] | null = null;
|
|
36
29
|
#meta: Record<string, unknown> = {};
|
|
30
|
+
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: Not in use currently
|
|
37
31
|
#status: "pending" | "success" | "error" | "disposed" = "pending";
|
|
38
32
|
#fallbackTimeoutId: number | null = null;
|
|
39
33
|
readonly #fallbackHTML: string;
|
|
@@ -64,7 +58,7 @@ export class PluginInstance {
|
|
|
64
58
|
this.#fallbackHTML = existingInstance ? existingInstance.#fallbackHTML : this.#target.innerHTML;
|
|
65
59
|
existingInstance?.dispose();
|
|
66
60
|
|
|
67
|
-
this.#
|
|
61
|
+
this.#createInstance();
|
|
68
62
|
PluginInstance.#instances.push(this);
|
|
69
63
|
queueMacroTask(() => {
|
|
70
64
|
dispatchEvent({
|
|
@@ -76,7 +70,10 @@ export class PluginInstance {
|
|
|
76
70
|
});
|
|
77
71
|
}
|
|
78
72
|
|
|
79
|
-
#handleMessage = async (
|
|
73
|
+
#handleMessage = async (event: MessageEvent) => {
|
|
74
|
+
const message = this.#element?.validateMessageEvent(event);
|
|
75
|
+
if (!message) return;
|
|
76
|
+
|
|
80
77
|
if (message.type === "STATE_UPDATE") {
|
|
81
78
|
this.#state = message.payload;
|
|
82
79
|
|
|
@@ -114,7 +111,7 @@ export class PluginInstance {
|
|
|
114
111
|
});
|
|
115
112
|
}
|
|
116
113
|
|
|
117
|
-
this.#
|
|
114
|
+
this.#element?.postMessage({
|
|
118
115
|
payload: { meta: this.#meta },
|
|
119
116
|
type: "PRE_APPOINTMENT_REQUEST",
|
|
120
117
|
});
|
|
@@ -123,7 +120,7 @@ export class PluginInstance {
|
|
|
123
120
|
}
|
|
124
121
|
|
|
125
122
|
if (message.type === "REQUEST_ORIGIN_URL") {
|
|
126
|
-
this.#
|
|
123
|
+
this.#element?.postMessage({
|
|
127
124
|
payload: { href: window.location.href },
|
|
128
125
|
type: "REQUEST_ORIGIN_URL",
|
|
129
126
|
});
|
|
@@ -134,7 +131,7 @@ export class PluginInstance {
|
|
|
134
131
|
if (message.type === "REQUEST_GEOLOCATION") {
|
|
135
132
|
const geolocation = getCurrentPosition();
|
|
136
133
|
|
|
137
|
-
this.#
|
|
134
|
+
this.#element?.postMessage({
|
|
138
135
|
payload: await geolocation.catch((error) =>
|
|
139
136
|
typeof error === "string" ? { error } : { error: "Unknown error" },
|
|
140
137
|
),
|
|
@@ -174,44 +171,38 @@ export class PluginInstance {
|
|
|
174
171
|
};
|
|
175
172
|
|
|
176
173
|
#createInstance() {
|
|
174
|
+
const searchParams = pluginSearchParams.encode(this.#config.config);
|
|
175
|
+
const src = `${PluginInstance.#url}/plugin?${searchParams}`;
|
|
176
|
+
|
|
177
|
+
const PexipEngagePluginFrame = getPexipEngagePluginFrame();
|
|
177
178
|
const container = new PexipEngagePluginFrame();
|
|
179
|
+
container.setAttribute("src", src);
|
|
180
|
+
|
|
181
|
+
this.#element = container;
|
|
178
182
|
this.#target.innerHTML = "";
|
|
179
183
|
this.#target.appendChild(container);
|
|
180
184
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const iframe = container.createPexipPlugin(src);
|
|
185
|
-
const self = this;
|
|
186
|
-
const [instance] = resizer(
|
|
187
|
-
{
|
|
188
|
-
checkOrigin: false,
|
|
189
|
-
heightCalculationMethod: "taggedElement",
|
|
190
|
-
log: false,
|
|
191
|
-
onInit() {
|
|
192
|
-
self.#status = "success";
|
|
193
|
-
dispatchEvent({
|
|
194
|
-
bubbles: true,
|
|
195
|
-
cancelable: true,
|
|
196
|
-
detail: { instance: self, type: PluginInstance.EVENT_LOADED },
|
|
197
|
-
target: self.#target,
|
|
198
|
-
});
|
|
199
|
-
},
|
|
200
|
-
onMessage: this.#handleMessage,
|
|
201
|
-
resizeFrom: "child",
|
|
202
|
-
},
|
|
203
|
-
iframe,
|
|
204
|
-
);
|
|
185
|
+
try {
|
|
186
|
+
container.createPexipPlugin();
|
|
205
187
|
|
|
206
|
-
|
|
188
|
+
this.#status = "success";
|
|
189
|
+
dispatchEvent({
|
|
190
|
+
bubbles: true,
|
|
191
|
+
cancelable: true,
|
|
192
|
+
detail: { instance: this, type: PluginInstance.EVENT_LOADED },
|
|
193
|
+
target: this.#target,
|
|
194
|
+
});
|
|
207
195
|
|
|
208
|
-
|
|
196
|
+
window.addEventListener("message", this.#handleMessage);
|
|
197
|
+
} catch (err) {
|
|
198
|
+
throw new Error("Failed to create resizer instance");
|
|
199
|
+
}
|
|
209
200
|
}
|
|
210
201
|
|
|
211
202
|
#restart = () => {
|
|
203
|
+
window.removeEventListener("message", this.#handleMessage);
|
|
212
204
|
// leave event listeners intact.
|
|
213
|
-
this.#
|
|
214
|
-
this.#instance = this.#createInstance();
|
|
205
|
+
this.#createInstance();
|
|
215
206
|
};
|
|
216
207
|
|
|
217
208
|
/** Destroy the instance */
|
|
@@ -226,7 +217,8 @@ export class PluginInstance {
|
|
|
226
217
|
window.clearTimeout(this.#fallbackTimeoutId);
|
|
227
218
|
}
|
|
228
219
|
|
|
229
|
-
this.#
|
|
220
|
+
window.removeEventListener("message", this.#handleMessage);
|
|
221
|
+
this.#element?.unsubscribe();
|
|
230
222
|
const idx = PluginInstance.#instances.indexOf(this);
|
|
231
223
|
if (idx !== -1) {
|
|
232
224
|
PluginInstance.#instances.splice(idx, 1);
|
|
@@ -238,11 +230,11 @@ export class PluginInstance {
|
|
|
238
230
|
setCSSVariable = (name: string, value: string) => {
|
|
239
231
|
warnPrivate({ name: "setCSSVariable", type: "function" });
|
|
240
232
|
|
|
241
|
-
this.#
|
|
233
|
+
this.#element?.postMessage({ payload: { name, value }, type: "CSS_VAR_UPDATE" });
|
|
242
234
|
};
|
|
243
235
|
|
|
244
236
|
setCustomCSS = (css: string) => {
|
|
245
|
-
this.#
|
|
237
|
+
this.#element?.postMessage({ payload: { css }, type: "CUSTOM_CSS_UPDATE" });
|
|
246
238
|
};
|
|
247
239
|
|
|
248
240
|
#listeners = new Set<EventListener>();
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IFrameMessage,
|
|
3
|
+
IFrameObjectMessage,
|
|
4
|
+
IframeChildMessageEventData,
|
|
5
|
+
IframeParentMessageEventData,
|
|
6
|
+
} from "../events/index.js";
|
|
7
|
+
import {
|
|
8
|
+
type IframeChildInitEventData,
|
|
9
|
+
type IframeResizeEventData,
|
|
10
|
+
type IframeScrollData,
|
|
11
|
+
isBrowser,
|
|
12
|
+
} from "./common.js";
|
|
13
|
+
|
|
14
|
+
export class IframeChildInstance {
|
|
15
|
+
#queue: IFrameMessage[] = [];
|
|
16
|
+
#isQueueConsumed = false;
|
|
17
|
+
#initialized = false;
|
|
18
|
+
#_resizeObserver: ResizeObserver | null = null;
|
|
19
|
+
get #resizeObserver() {
|
|
20
|
+
if (!this.#_resizeObserver) {
|
|
21
|
+
this.#_resizeObserver = new ResizeObserver((entries) => {
|
|
22
|
+
if (!entries[0]?.target) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const clientRect = entries[0].target.getBoundingClientRect();
|
|
27
|
+
const height = Math.ceil(clientRect.height);
|
|
28
|
+
const width = Math.ceil(clientRect.width);
|
|
29
|
+
|
|
30
|
+
const data: IframeResizeEventData = {
|
|
31
|
+
height,
|
|
32
|
+
type: "iframe-resized",
|
|
33
|
+
width,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
window.parent.postMessage(data, "*");
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return this.#_resizeObserver;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#handleInitializeSignal = (event: MessageEvent<IframeChildInitEventData>) => {
|
|
44
|
+
const elementToObserve = document.documentElement;
|
|
45
|
+
|
|
46
|
+
if (this.#initialized || window.parent !== event.source) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.#resizeObserver.disconnect();
|
|
51
|
+
this.#resizeObserver.observe(elementToObserve);
|
|
52
|
+
this.#initialized = true;
|
|
53
|
+
this.#isQueueConsumed = true;
|
|
54
|
+
this.#queue.forEach((message) => this.sendMessage(message));
|
|
55
|
+
this.#queue.length = 0;
|
|
56
|
+
|
|
57
|
+
this.#onReady();
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
sendMessage = (message: IFrameMessage) => {
|
|
61
|
+
if (!this.#isQueueConsumed && isInIframe()) {
|
|
62
|
+
this.#queue.push(message);
|
|
63
|
+
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const data: IframeParentMessageEventData = {
|
|
68
|
+
message,
|
|
69
|
+
type: "iframe-parent-message",
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
this.#sendMessage(data);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
scrollToOffset = (offset: { top: number }) => {
|
|
76
|
+
const data: IframeScrollData = {
|
|
77
|
+
top: offset.top,
|
|
78
|
+
type: "iframe-scroll-to-offset",
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
this.#sendMessage(data);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
#sendMessage(data: IframeScrollData | IframeParentMessageEventData) {
|
|
85
|
+
if (!isBrowser() || !isInIframe()) return;
|
|
86
|
+
|
|
87
|
+
window.parent.postMessage(data, "*");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#handleMessage = (
|
|
91
|
+
event: MessageEvent<IframeChildInitEventData | IframeChildMessageEventData>,
|
|
92
|
+
) => {
|
|
93
|
+
if (event.data?.type === "iframe-child-init") {
|
|
94
|
+
return deferWhenWindowDocumentIsLoaded(() =>
|
|
95
|
+
this.#handleInitializeSignal(event as MessageEvent<IframeChildInitEventData>),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (event.data?.type === "iframe-child-message") {
|
|
100
|
+
this.#onMessage(event.data.message);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
readonly #onReady: () => void;
|
|
105
|
+
readonly #onMessage: (message: IFrameObjectMessage) => void;
|
|
106
|
+
|
|
107
|
+
constructor({
|
|
108
|
+
onReady,
|
|
109
|
+
onMessage,
|
|
110
|
+
}: {
|
|
111
|
+
onReady: () => void;
|
|
112
|
+
onMessage: (message: IFrameObjectMessage) => void;
|
|
113
|
+
}) {
|
|
114
|
+
this.#onReady = onReady;
|
|
115
|
+
this.#onMessage = onMessage;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
initialize = () => {
|
|
119
|
+
if (!isBrowser() || !isInIframe()) return;
|
|
120
|
+
|
|
121
|
+
window.addEventListener("message", this.#handleMessage);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
unsubscribe = () => {
|
|
125
|
+
window.removeEventListener("message", this.#handleMessage);
|
|
126
|
+
this.#resizeObserver.disconnect();
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function isInIframe() {
|
|
131
|
+
return window.self !== window.top;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function deferWhenWindowDocumentIsLoaded(executable: () => void) {
|
|
135
|
+
window.document.readyState === "complete"
|
|
136
|
+
? executable()
|
|
137
|
+
: window.addEventListener("load", executable);
|
|
138
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function isBrowser() {
|
|
2
|
+
return typeof window !== "undefined";
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export type InitializeResult = { unsubscribe: () => void };
|
|
6
|
+
|
|
7
|
+
export type IframeResizeEventData = {
|
|
8
|
+
type: "iframe-resized";
|
|
9
|
+
width: number;
|
|
10
|
+
height?: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type IframeScrollData = {
|
|
14
|
+
type: "iframe-scroll-to-offset";
|
|
15
|
+
top: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type IframeChildInitEventData = {
|
|
19
|
+
type: "iframe-child-init";
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type IframeResizeEvent = MessageEvent<IframeResizeEventData>;
|
|
23
|
+
export type IframeScrollEvent = MessageEvent<IframeScrollData>;
|
|
24
|
+
|
|
25
|
+
export interface RegisteredElement {
|
|
26
|
+
iframe: HTMLIFrameElement;
|
|
27
|
+
initContext: { isInitialized: boolean; retryAttempts: number; retryTimeoutId?: number };
|
|
28
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type IframeChildInitEventData,
|
|
3
|
+
type IframeResizeEvent,
|
|
4
|
+
type IframeScrollEvent,
|
|
5
|
+
type InitializeResult,
|
|
6
|
+
isBrowser,
|
|
7
|
+
type RegisteredElement,
|
|
8
|
+
} from "./common.js";
|
|
9
|
+
|
|
10
|
+
export function initialize(iframe: HTMLIFrameElement): InitializeResult {
|
|
11
|
+
if (!isBrowser()) {
|
|
12
|
+
return { unsubscribe() {} };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const registeredElement: RegisteredElement = {
|
|
16
|
+
iframe,
|
|
17
|
+
initContext: { isInitialized: false, retryAttempts: 0 },
|
|
18
|
+
};
|
|
19
|
+
const unsubscribe = addCrossOriginChildResizeListener(registeredElement);
|
|
20
|
+
|
|
21
|
+
return { unsubscribe };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function addCrossOriginChildResizeListener(registeredElement: RegisteredElement) {
|
|
25
|
+
const { iframe, initContext } = registeredElement;
|
|
26
|
+
|
|
27
|
+
function handleIframeResizedMessage(event: MessageEvent) {
|
|
28
|
+
const isIframeTarget = iframe.contentWindow === event.source;
|
|
29
|
+
|
|
30
|
+
if (!isIframeTarget) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (event.data?.type === "iframe-resized") {
|
|
35
|
+
const { height } = (event as IframeResizeEvent).data;
|
|
36
|
+
height && resizeIframe({ newHeight: height, registeredElement });
|
|
37
|
+
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (event.data?.type === "iframe-scroll-to-offset") {
|
|
42
|
+
const { top } = (event as IframeScrollEvent).data;
|
|
43
|
+
const iFramePosition = iframe.getBoundingClientRect();
|
|
44
|
+
|
|
45
|
+
window.scrollTo({ top: iFramePosition.top + window.scrollY + top });
|
|
46
|
+
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
window.addEventListener("message", handleIframeResizedMessage);
|
|
52
|
+
|
|
53
|
+
const initMessage: IframeChildInitEventData = { type: "iframe-child-init" };
|
|
54
|
+
|
|
55
|
+
function sendInitializationMessageToChild() {
|
|
56
|
+
postMessageSafelyToCrossOriginIframe(iframe, () =>
|
|
57
|
+
iframe.contentWindow?.postMessage(initMessage, "*"),
|
|
58
|
+
);
|
|
59
|
+
initContext.retryAttempts++;
|
|
60
|
+
initContext.retryTimeoutId = window.setTimeout(
|
|
61
|
+
sendInitializationMessageToChild,
|
|
62
|
+
getExponentialBackoffDelay(initContext.retryAttempts),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
sendInitializationMessageToChild();
|
|
66
|
+
|
|
67
|
+
return () => window.removeEventListener("message", handleIframeResizedMessage);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function resizeIframe({
|
|
71
|
+
registeredElement,
|
|
72
|
+
newHeight,
|
|
73
|
+
}: {
|
|
74
|
+
registeredElement: RegisteredElement;
|
|
75
|
+
newHeight: number;
|
|
76
|
+
}) {
|
|
77
|
+
const { iframe, initContext } = registeredElement;
|
|
78
|
+
if (!initContext.isInitialized) {
|
|
79
|
+
initContext.isInitialized = true;
|
|
80
|
+
clearTimeout(initContext.retryTimeoutId);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
iframe.style.height = `${newHeight}px`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Post the message twice, it assures the target to receive the message at least once */
|
|
87
|
+
function postMessageSafelyToCrossOriginIframe(iframe: HTMLIFrameElement, executable: () => void) {
|
|
88
|
+
executable();
|
|
89
|
+
iframe.addEventListener("load", executable);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getExponentialBackoffDelay(nthRetry: number) {
|
|
93
|
+
if (nthRetry <= 100) {
|
|
94
|
+
return 100; // for 10 seconds
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (nthRetry <= 120) {
|
|
98
|
+
return 1000; // for 20 seconds
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return 10000;
|
|
102
|
+
}
|