@pexip-engage-public/plugin 1.1.23 → 1.1.25-canary-20250708143730

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/events/index.d.ts +11 -0
  3. package/dist/events/index.d.ts.map +1 -1
  4. package/dist/events/index.js +2 -0
  5. package/dist/events/index.js.map +1 -1
  6. package/dist/instance/PluginInstance.d.ts +1 -1
  7. package/dist/instance/PluginInstance.d.ts.map +1 -1
  8. package/dist/instance/PluginInstance.js +30 -27
  9. package/dist/instance/PluginInstance.js.map +1 -1
  10. package/dist/resizer/api-resize-handlers.d.ts +9 -0
  11. package/dist/resizer/api-resize-handlers.d.ts.map +1 -0
  12. package/dist/resizer/api-resize-handlers.js +12 -0
  13. package/dist/resizer/api-resize-handlers.js.map +1 -0
  14. package/dist/resizer/child.d.ts +12 -0
  15. package/dist/resizer/child.d.ts.map +1 -0
  16. package/dist/resizer/child.js +80 -0
  17. package/dist/resizer/child.js.map +1 -0
  18. package/dist/resizer/common.d.ts +27 -0
  19. package/dist/resizer/common.d.ts.map +1 -0
  20. package/dist/resizer/common.js +82 -0
  21. package/dist/resizer/common.js.map +1 -0
  22. package/dist/resizer/index.d.ts +2 -0
  23. package/dist/resizer/index.d.ts.map +1 -0
  24. package/dist/resizer/index.js +2 -0
  25. package/dist/resizer/index.js.map +1 -0
  26. package/dist/resizer/parent.d.ts +4 -0
  27. package/dist/resizer/parent.d.ts.map +1 -0
  28. package/dist/resizer/parent.js +185 -0
  29. package/dist/resizer/parent.js.map +1 -0
  30. package/dist/resizer/types.d.ts +101 -0
  31. package/dist/resizer/types.d.ts.map +1 -0
  32. package/dist/resizer/types.js +2 -0
  33. package/dist/resizer/types.js.map +1 -0
  34. package/dist/state/PluginState.schema.d.ts +6 -6
  35. package/package.json +10 -9
  36. package/src/events/index.ts +14 -0
  37. package/src/instance/PluginInstance.ts +42 -40
  38. package/src/resizer/api-resize-handlers.ts +17 -0
  39. package/src/resizer/child.ts +128 -0
  40. package/src/resizer/common.ts +117 -0
  41. package/src/resizer/index.ts +0 -0
  42. package/src/resizer/parent.ts +277 -0
  43. package/src/resizer/types.ts +108 -0
  44. package/src/@types/iframe-resizer.iframeResizer.d.ts +0 -211
@@ -0,0 +1,185 @@
1
+ import { applyStyleSettings, deferWhenSameOriginIframeIsLoaded, extractIframeOrigin, getBoundingRectSize, getDefaultSettings, getExponentialBackoffDelay, isBrowser, isHtmlIframeElement, isIframeSameOrigin, postMessageSafelyToCrossOriginIframe, removeUndefinedProperties, resolveElementToObserve, } from "./common.js";
2
+ const getResizeObserverInstance = createResizerObserverLazyFactory();
3
+ let registeredElements = [];
4
+ const initialize = (clientSettings, selector) => {
5
+ if (!isBrowser()) {
6
+ return [];
7
+ }
8
+ const finalSettings = {
9
+ ...getDefaultSettings(),
10
+ ...removeUndefinedProperties(clientSettings ?? {}),
11
+ };
12
+ const iframes = resolveIframesToRegister(selector);
13
+ const allowedOrigins = registerIframesAllowOrigins(finalSettings, iframes);
14
+ return iframes.map((iframe) => {
15
+ const registeredElement = {
16
+ iframe,
17
+ initContext: { isInitialized: false, retryAttempts: 0 },
18
+ interactionState: { isHovered: false },
19
+ settings: finalSettings,
20
+ };
21
+ const unsubscribe = addChildResizeListener(registeredElement, allowedOrigins);
22
+ registeredElements.push(registeredElement);
23
+ return {
24
+ unsubscribe: () => {
25
+ unsubscribe();
26
+ registeredElements = registeredElements.filter((entry) => entry.iframe !== iframe);
27
+ },
28
+ };
29
+ });
30
+ };
31
+ function resolveIframesToRegister(selector) {
32
+ if (typeof selector === "string") {
33
+ return Array.from(document.querySelectorAll(selector)).filter(isHtmlIframeElement);
34
+ }
35
+ if (selector) {
36
+ return isHtmlIframeElement(selector) ? [selector] : [];
37
+ }
38
+ return Array.from(document.getElementsByTagName("iframe"));
39
+ }
40
+ function registerIframesAllowOrigins(settings, iframes) {
41
+ if (Array.isArray(settings.checkOrigin)) {
42
+ return settings.checkOrigin;
43
+ }
44
+ if (!settings.checkOrigin) {
45
+ return [];
46
+ }
47
+ const allowedOrigins = [];
48
+ for (const iframe of iframes) {
49
+ const origin = extractIframeOrigin(iframe);
50
+ if (origin) {
51
+ allowedOrigins.push(origin);
52
+ }
53
+ }
54
+ return allowedOrigins;
55
+ }
56
+ function addChildResizeListener(registeredElement, allowedOrigins) {
57
+ const removeResizeListener = isIframeSameOrigin(registeredElement.iframe)
58
+ ? addSameOriginChildResizeListener(registeredElement)
59
+ : addCrossOriginChildResizeListener(registeredElement, allowedOrigins);
60
+ const removeInteractionListeners = addInteractionListeners(registeredElement);
61
+ return () => {
62
+ removeResizeListener();
63
+ removeInteractionListeners();
64
+ };
65
+ }
66
+ function addCrossOriginChildResizeListener(registeredElement, allowedOrigins) {
67
+ const { iframe, initContext, settings: { checkOrigin, targetElementSelector, bodyPadding, bodyMargin }, } = registeredElement;
68
+ const handleIframeResizedMessage = (event) => {
69
+ const isOriginValid = !checkOrigin || allowedOrigins.includes(event.origin);
70
+ const isIframeTarget = iframe.contentWindow === event.source;
71
+ if (!isIframeTarget || !isOriginValid) {
72
+ return;
73
+ }
74
+ if (event.data?.type === "iframe-resized") {
75
+ const { height } = event.data;
76
+ height && resizeIframe({ newHeight: height, registeredElement });
77
+ return;
78
+ }
79
+ };
80
+ window.addEventListener("message", handleIframeResizedMessage);
81
+ const initMessage = {
82
+ bodyMargin,
83
+ bodyPadding,
84
+ targetElementSelector,
85
+ type: "iframe-child-init",
86
+ };
87
+ const sendInitializationMessageToChild = () => {
88
+ postMessageSafelyToCrossOriginIframe(iframe, () => iframe.contentWindow?.postMessage(initMessage, "*"));
89
+ initContext.retryAttempts++;
90
+ initContext.retryTimeoutId = window.setTimeout(sendInitializationMessageToChild, getExponentialBackoffDelay(initContext.retryAttempts));
91
+ };
92
+ sendInitializationMessageToChild();
93
+ return () => window.removeEventListener("message", handleIframeResizedMessage);
94
+ }
95
+ function addSameOriginChildResizeListener(registeredElement) {
96
+ const { iframe, settings } = registeredElement;
97
+ const { targetElementSelector } = settings;
98
+ let nthRetry = 0;
99
+ const initialize = () => {
100
+ const elementToObserve = resolveElementToObserve(iframe.contentDocument, targetElementSelector);
101
+ if (!iframe.contentDocument || !elementToObserve) {
102
+ nthRetry++;
103
+ return setTimeout(initialize, getExponentialBackoffDelay(nthRetry));
104
+ }
105
+ applyStyleSettings(iframe.contentDocument, settings);
106
+ getResizeObserverInstance().observe(elementToObserve);
107
+ };
108
+ deferWhenSameOriginIframeIsLoaded(iframe, initialize);
109
+ return () => {
110
+ const elementToObserve = resolveElementToObserve(iframe.contentDocument, targetElementSelector);
111
+ if (elementToObserve) {
112
+ getResizeObserverInstance().unobserve(elementToObserve);
113
+ }
114
+ iframe.removeEventListener("load", initialize);
115
+ };
116
+ }
117
+ function addInteractionListeners({ iframe, interactionState }) {
118
+ const onMouseEnter = () => {
119
+ interactionState.isHovered = true;
120
+ };
121
+ const onMouseLeave = () => {
122
+ interactionState.isHovered = false;
123
+ };
124
+ iframe.addEventListener("mouseenter", onMouseEnter);
125
+ iframe.addEventListener("mouseleave", onMouseLeave);
126
+ return () => {
127
+ iframe.removeEventListener("mouseenter", onMouseEnter);
128
+ iframe.removeEventListener("mouseleave", onMouseLeave);
129
+ };
130
+ }
131
+ function createResizerObserverLazyFactory() {
132
+ let resizeObserver = null;
133
+ return () => {
134
+ if (!resizeObserver) {
135
+ const handleEntry = ({ target }) => {
136
+ const matchingRegisteredElement = registeredElements.find(({ iframe }) => iframe.contentDocument === target.ownerDocument);
137
+ if (!matchingRegisteredElement) {
138
+ return;
139
+ }
140
+ const { iframe, settings } = matchingRegisteredElement;
141
+ const observedElement = resolveElementToObserve(iframe.contentDocument, settings.targetElementSelector);
142
+ if (!observedElement) {
143
+ return;
144
+ }
145
+ const { height } = getBoundingRectSize(observedElement);
146
+ if (!height) {
147
+ return;
148
+ }
149
+ resizeIframe({ newHeight: height, registeredElement: matchingRegisteredElement });
150
+ };
151
+ resizeObserver = new ResizeObserver((entries) => entries.forEach(handleEntry));
152
+ }
153
+ return resizeObserver;
154
+ };
155
+ }
156
+ function resizeIframe({ registeredElement, newHeight, }) {
157
+ const { iframe, settings, interactionState, initContext } = registeredElement;
158
+ if (!initContext.isInitialized) {
159
+ initContext.isInitialized = true;
160
+ clearTimeout(initContext.retryTimeoutId);
161
+ }
162
+ if (settings.onBeforeIframeResize?.({
163
+ iframe,
164
+ observedHeight: newHeight,
165
+ settings: { ...settings },
166
+ }) === false) {
167
+ return;
168
+ }
169
+ const previousBoundingRect = iframe.getBoundingClientRect();
170
+ const newCalculatedHeight = newHeight + settings.offsetSize;
171
+ iframe.style.height = `${newCalculatedHeight}px`;
172
+ if (!settings.onIframeResize) {
173
+ return;
174
+ }
175
+ const resizeContext = {
176
+ iframe,
177
+ interactionState: { ...interactionState },
178
+ nextRenderState: { rect: iframe.getBoundingClientRect() },
179
+ previousRenderState: { rect: previousBoundingRect },
180
+ settings: { ...settings },
181
+ };
182
+ settings.onIframeResize(resizeContext);
183
+ }
184
+ export { initialize };
185
+ //# sourceMappingURL=parent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parent.js","sourceRoot":"","sources":["../../src/resizer/parent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Automatically resize the selected iframes when their inner content grows.
3
+ * @param settings The settings for the selected iframes. The default settings properties are picked if empty.
4
+ * @param selector The selector for the iframe(s) or the HTMLIFrameElement to be resized. If empty, all document iframe elements will be selected.
5
+ * @returns A result array, which can be used to clean up the listeners if you often remove iframes from the document.
6
+ */
7
+ export type InitializeFunction = (settings?: Partial<Settings>, selector?: string | HTMLIFrameElement) => InitializeResult[];
8
+ export type InitializeResult = {
9
+ unsubscribe: () => void;
10
+ };
11
+ export type Settings = {
12
+ /**
13
+ * Offset added to the resize size (in pixels).
14
+ *
15
+ * Default: `0`
16
+ */
17
+ offsetSize: number;
18
+ /**
19
+ * Specifies whether to check the origin of incoming messages.
20
+ * Accepts an array of allowed origins or a boolean.
21
+ * If `true`, incoming messages are allowed from the origins of the registered iframes.
22
+ *
23
+ * Default: `true`
24
+ */
25
+ checkOrigin: string[] | boolean;
26
+ /**
27
+ * By default, the root element observed for resizing is the <html> document.
28
+ * In more complex layouts, the scrolling container may be elsewhere.
29
+ * This setting allows you to customize the root element that should be observed for resize events.
30
+ *
31
+ * Default: `undefined`
32
+ */
33
+ targetElementSelector?: string;
34
+ /**
35
+ * Customize the padding style of the iframe body.
36
+ *
37
+ * Default: `undefined`
38
+ */
39
+ bodyPadding?: string;
40
+ /**
41
+ * Customize the margin style of the iframe body.
42
+ *
43
+ * Default: `undefined`
44
+ */
45
+ bodyMargin?: string;
46
+ /**
47
+ * Called whenever the observed content size changes and the iframe is about to be resized.
48
+ * Return `false` to cancel the resize; returning `true` or nothing will allow it.
49
+ *
50
+ * Default: `undefined`
51
+ */
52
+ onBeforeIframeResize?: (context: BeforeResizeContext) => boolean | undefined;
53
+ /**
54
+ * Listener that is called after the iframe has been resized.
55
+ * You can use a predefined handler like `updateParentScrollOnResize` or create your own custom handler.
56
+ *
57
+ * Default: `undefined`
58
+ */
59
+ onIframeResize?: (context: ResizeContext) => void;
60
+ };
61
+ export type IframeResizeEventData = {
62
+ type: "iframe-resized";
63
+ width: number;
64
+ height?: number;
65
+ };
66
+ export type IframeChildInitEventData = {
67
+ type: "iframe-child-init";
68
+ targetElementSelector?: string;
69
+ bodyPadding?: string;
70
+ bodyMargin?: string;
71
+ };
72
+ export type IframeResizeEvent = MessageEvent<IframeResizeEventData>;
73
+ export type InteractionState = {
74
+ isHovered: boolean;
75
+ };
76
+ export type ResizeRenderState = {
77
+ rect: DOMRect;
78
+ };
79
+ export type ResizeContext = {
80
+ iframe: HTMLIFrameElement;
81
+ settings: Settings;
82
+ interactionState: InteractionState;
83
+ previousRenderState: ResizeRenderState;
84
+ nextRenderState: ResizeRenderState;
85
+ };
86
+ export type BeforeResizeContext = {
87
+ iframe: HTMLIFrameElement;
88
+ settings: Settings;
89
+ observedHeight: number;
90
+ };
91
+ export type RegisteredElement = {
92
+ iframe: HTMLIFrameElement;
93
+ settings: Settings;
94
+ interactionState: InteractionState;
95
+ initContext: {
96
+ isInitialized: boolean;
97
+ retryAttempts: number;
98
+ retryTimeoutId?: number;
99
+ };
100
+ };
101
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/resizer/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,EAC5B,QAAQ,CAAC,EAAE,MAAM,GAAG,iBAAiB,KAClC,gBAAgB,EAAE,CAAC;AACxB,MAAM,MAAM,gBAAgB,GAAG;IAAE,WAAW,EAAE,MAAM,IAAI,CAAA;CAAE,CAAC;AAE3D,MAAM,MAAM,QAAQ,GAAG;IACrB;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAChC;;;;;;OAMG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,GAAG,SAAS,CAAC;IAE7E;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,YAAY,CAAC,qBAAqB,CAAC,CAAC;AAEpE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAElD,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,QAAQ,CAAC;IACnB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,mBAAmB,EAAE,iBAAiB,CAAC;IACvC,eAAe,EAAE,iBAAiB,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,QAAQ,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,QAAQ,CAAC;IACnB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,WAAW,EAAE;QAAE,aAAa,EAAE,OAAO,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACzF,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/resizer/types.ts"],"names":[],"mappings":""}
@@ -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.23",
3
+ "version": "1.1.25-canary-20250708143730",
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
- "./logger": "./dist/logger.js"
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,10 +37,9 @@
35
37
  "src"
36
38
  ],
37
39
  "dependencies": {
38
- "iframe-resizer": "4.3.11",
39
40
  "skedify-uri-encoding": "^2.1.2",
40
- "zod": "^3.25.67",
41
- "@pexip-engage-public/graphql": "1.1.6",
41
+ "zod": "^3.25.75",
42
+ "@pexip-engage-public/graphql": "1.1.7",
42
43
  "@pexip-engage-public/utils": "0.1.46"
43
44
  },
44
45
  "devDependencies": {
@@ -46,7 +47,7 @@
46
47
  "happy-dom": "^18.0.1",
47
48
  "vitest": "^3.2.4",
48
49
  "@pexip-engage/tsconfig": "0.1.1",
49
- "eslint-config-pexip-engage": "1.1.24"
50
+ "eslint-config-pexip-engage": "1.1.25"
50
51
  },
51
52
  "volta": {
52
53
  "extends": "../../../../package.json"
@@ -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,18 +1,19 @@
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
- import type {
10
- PluginEventMessage,
11
- PressedExternalLinkMessage,
12
- StateUpdateMessage,
13
- StepShownMessage,
4
+ import {
5
+ IFRAME_CHILD_MESSAGE,
6
+ IFRAME_PARENT_MESSAGE,
7
+ type IFrameMessage,
8
+ type IFrameObjectMessage,
9
+ type PluginEventMessage,
10
+ type PressedExternalLinkMessage,
11
+ type StateUpdateMessage,
12
+ type StepShownMessage,
14
13
  } from "../events/index.js";
15
14
  import { logger } from "../logger.js";
15
+ import { initialize } from "../resizer/parent.js";
16
+ import type { InitializeResult } from "../resizer/types.js";
16
17
  import type { PluginIntent } from "../state/index.js";
17
18
  import {
18
19
  type CustomEventPayload,
@@ -30,8 +31,9 @@ type PluginEventListener = (event: PluginCustomEvent) => unknown;
30
31
  const PexipEngagePluginFrame = getPexipEngagePluginFrame();
31
32
 
32
33
  export class PluginInstance {
33
- #instance: IFrameComponent;
34
+ #instance: InitializeResult;
34
35
  readonly #target: HTMLElement;
36
+ #iframe: HTMLIFrameElement | null = null;
35
37
  #state: StateUpdateMessage["payload"] | null = null;
36
38
  #meta: Record<string, unknown> = {};
37
39
  #status: "pending" | "success" | "error" | "disposed" = "pending";
@@ -76,7 +78,16 @@ export class PluginInstance {
76
78
  });
77
79
  }
78
80
 
79
- #handleMessage = async ({ message }: IFrameMessageData) => {
81
+ #sendMessage = (message: IFrameObjectMessage) => {
82
+ this.#iframe?.contentWindow?.postMessage({ message, type: IFRAME_CHILD_MESSAGE }, "*");
83
+ };
84
+
85
+ #handleMessage = async (event: MessageEvent) => {
86
+ const isIframeTarget = this.#iframe && this.#iframe.contentWindow === event.source;
87
+ if (!isIframeTarget || event.data.type !== IFRAME_PARENT_MESSAGE) return;
88
+
89
+ const message = event.data.message as IFrameMessage;
90
+
80
91
  if (message.type === "STATE_UPDATE") {
81
92
  this.#state = message.payload;
82
93
 
@@ -114,7 +125,7 @@ export class PluginInstance {
114
125
  });
115
126
  }
116
127
 
117
- this.#instance.iFrameResizer.sendMessage({
128
+ this.#sendMessage({
118
129
  payload: { meta: this.#meta },
119
130
  type: "PRE_APPOINTMENT_REQUEST",
120
131
  });
@@ -123,7 +134,7 @@ export class PluginInstance {
123
134
  }
124
135
 
125
136
  if (message.type === "REQUEST_ORIGIN_URL") {
126
- this.#instance.iFrameResizer.sendMessage({
137
+ this.#sendMessage({
127
138
  payload: { href: window.location.href },
128
139
  type: "REQUEST_ORIGIN_URL",
129
140
  });
@@ -134,7 +145,7 @@ export class PluginInstance {
134
145
  if (message.type === "REQUEST_GEOLOCATION") {
135
146
  const geolocation = getCurrentPosition();
136
147
 
137
- this.#instance.iFrameResizer.sendMessage({
148
+ this.#sendMessage({
138
149
  payload: await geolocation.catch((error) =>
139
150
  typeof error === "string" ? { error } : { error: "Unknown error" },
140
151
  ),
@@ -181,36 +192,26 @@ export class PluginInstance {
181
192
  const searchParams = pluginSearchParams.encode(this.#config.config);
182
193
  const src = `${PluginInstance.#url}/plugin?${searchParams}`;
183
194
 
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
- );
195
+ this.#iframe = container.createPexipPlugin(src);
196
+ const [instance] = initialize({ checkOrigin: false }, this.#iframe);
197
+ this.#status = "success";
198
+ dispatchEvent({
199
+ bubbles: true,
200
+ cancelable: true,
201
+ detail: { instance: this, type: PluginInstance.EVENT_LOADED },
202
+ target: this.#target,
203
+ });
205
204
 
205
+ window.addEventListener("message", this.#handleMessage);
206
206
  if (!instance) throw new Error("Failed to create resizer instance");
207
207
 
208
208
  return instance;
209
209
  }
210
210
 
211
211
  #restart = () => {
212
+ window.removeEventListener("message", this.#handleMessage);
212
213
  // leave event listeners intact.
213
- this.#instance.iFrameResizer.close();
214
+ this.#instance.unsubscribe();
214
215
  this.#instance = this.#createInstance();
215
216
  };
216
217
 
@@ -226,7 +227,8 @@ export class PluginInstance {
226
227
  window.clearTimeout(this.#fallbackTimeoutId);
227
228
  }
228
229
 
229
- this.#instance.iFrameResizer.close();
230
+ window.addEventListener("message", this.#handleMessage);
231
+ this.#instance.unsubscribe();
230
232
  const idx = PluginInstance.#instances.indexOf(this);
231
233
  if (idx !== -1) {
232
234
  PluginInstance.#instances.splice(idx, 1);
@@ -238,11 +240,11 @@ export class PluginInstance {
238
240
  setCSSVariable = (name: string, value: string) => {
239
241
  warnPrivate({ name: "setCSSVariable", type: "function" });
240
242
 
241
- this.#instance.iFrameResizer.sendMessage({ payload: { name, value }, type: "CSS_VAR_UPDATE" });
243
+ this.#sendMessage({ payload: { name, value }, type: "CSS_VAR_UPDATE" });
242
244
  };
243
245
 
244
246
  setCustomCSS = (css: string) => {
245
- this.#instance.iFrameResizer.sendMessage({ payload: { css }, type: "CUSTOM_CSS_UPDATE" });
247
+ this.#sendMessage({ payload: { css }, type: "CUSTOM_CSS_UPDATE" });
246
248
  };
247
249
 
248
250
  #listeners = new Set<EventListener>();
@@ -0,0 +1,17 @@
1
+ import type { ResizeContext } from "./types.js";
2
+
3
+ /**
4
+ * Resize handler that scrolls to restore the iframe's position in the viewport as it was before the resize.
5
+ *
6
+ * *Note:* This behavior only triggers if the iframe currently has focus,
7
+ * in order to try to limit the number of scroll as it can affect the user experience.
8
+ */
9
+ export const updateParentScrollOnResize = ({
10
+ previousRenderState,
11
+ nextRenderState,
12
+ iframe,
13
+ }: ResizeContext) => {
14
+ if (document.activeElement === iframe) {
15
+ window.scrollBy(0, nextRenderState.rect.bottom - previousRenderState.rect.bottom);
16
+ }
17
+ };