@joryio/web-sdk 1.3.0 → 1.3.1-beta.2
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 +8 -0
- package/README.md +1 -1
- package/dist/core/config.d.ts +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/inapp.d.ts +15 -0
- package/dist/core/inapp.d.ts.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/joryio-sw.js +1 -1
- package/dist/joryio.cjs.js +75 -9
- package/dist/joryio.min.js +2 -2
- package/dist/joryio.min.js.map +1 -1
- package/dist/joryio.mjs +75 -9
- package/dist/joryio.umd.js +75 -9
- package/package.json +1 -1
- package/src/core/config.ts +13 -10
- package/src/core/inapp.ts +171 -44
package/src/core/inapp.ts
CHANGED
|
@@ -96,7 +96,17 @@ export interface InAppCampaign {
|
|
|
96
96
|
* verbatim on web. Server-rendered HTML is now the only thing sent.
|
|
97
97
|
*/
|
|
98
98
|
content?:
|
|
99
|
-
| {
|
|
99
|
+
| {
|
|
100
|
+
kind: 'html';
|
|
101
|
+
html: string;
|
|
102
|
+
css: string;
|
|
103
|
+
assets?: unknown[];
|
|
104
|
+
/**
|
|
105
|
+
* Widest the message may be, in CSS px. Sent for `modal` and `slideup`,
|
|
106
|
+
* already resolved by the server; see inAppMaxWidth for the fallback.
|
|
107
|
+
*/
|
|
108
|
+
maxWidth?: number;
|
|
109
|
+
}
|
|
100
110
|
| {
|
|
101
111
|
kind: 'native';
|
|
102
112
|
title?: string;
|
|
@@ -202,6 +212,39 @@ export function escapeStyleText(css: string): string {
|
|
|
202
212
|
return typeof css === 'string' ? css.replace(/<\//g, '<\\/') : '';
|
|
203
213
|
}
|
|
204
214
|
|
|
215
|
+
/**
|
|
216
|
+
* The width a content-sized HTML message gets when its payload carries no
|
|
217
|
+
* `maxWidth` (a server that predates the field). The same two numbers as the
|
|
218
|
+
* backend's IN_APP_DEFAULT_MAX_WIDTH and every other SDK; the server normally
|
|
219
|
+
* sends the resolved value, so this is only ever a fallback.
|
|
220
|
+
*/
|
|
221
|
+
export const IN_APP_DEFAULT_MAX_WIDTH = { modal: 600, slideup: 400 } as const;
|
|
222
|
+
|
|
223
|
+
/** Space kept between a content-sized message and each side of the viewport, in px. */
|
|
224
|
+
export const IN_APP_VIEWPORT_MARGIN = 16;
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* The maxWidth for this campaign, or null when its type is not content-sized
|
|
228
|
+
* (banner and fullscreen span the page; custom is the host's to place) or its
|
|
229
|
+
* content is native (the card has its own width).
|
|
230
|
+
*/
|
|
231
|
+
export function inAppMaxWidth(campaign: Pick<InAppCampaign, 'type' | 'content'>): number | null {
|
|
232
|
+
if (campaign.type !== 'modal' && campaign.type !== 'slideup') return null;
|
|
233
|
+
if (campaign.content?.kind !== 'html') return null;
|
|
234
|
+
const mw = campaign.content.maxWidth;
|
|
235
|
+
return typeof mw === 'number' && Number.isFinite(mw) && mw > 0 ? Math.floor(mw) : IN_APP_DEFAULT_MAX_WIDTH[campaign.type];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* The width the message frame is given: min(maxWidth, viewport - margin on
|
|
240
|
+
* each side). Set BEFORE the content lays out, so the author's media queries
|
|
241
|
+
* see the width the message really has; only the height follows the content.
|
|
242
|
+
*/
|
|
243
|
+
export function inAppFrameWidth(maxWidth: number, viewportWidth: number, margin: number = IN_APP_VIEWPORT_MARGIN): number {
|
|
244
|
+
const available = Math.max(0, Math.floor(viewportWidth) - 2 * margin);
|
|
245
|
+
return Math.max(0, Math.min(Math.floor(maxWidth), available));
|
|
246
|
+
}
|
|
247
|
+
|
|
205
248
|
// Host-window key the in-app iframes call into (same-origin). Internal.
|
|
206
249
|
const HOST_BRIDGE_KEY = '__joryioInApp';
|
|
207
250
|
|
|
@@ -379,12 +422,23 @@ export class InAppMessagingManager {
|
|
|
379
422
|
private claimed = new Set<string>();
|
|
380
423
|
// Per-campaign content iframe (so we can auto-size + clean it up).
|
|
381
424
|
private iframes: Map<string, HTMLIFrameElement> = new Map();
|
|
425
|
+
/**
|
|
426
|
+
* maxWidth of each displayed content-sized (modal / slideup) HTML message.
|
|
427
|
+
* Their frame width is derived from it and the host viewport, and re-derived
|
|
428
|
+
* when the viewport changes - see sizeContentFrame.
|
|
429
|
+
*/
|
|
430
|
+
private frameMaxWidths: Map<string, number> = new Map();
|
|
431
|
+
private viewportListener?: () => void;
|
|
432
|
+
private viewportTimer?: ReturnType<typeof setTimeout>;
|
|
382
433
|
|
|
383
434
|
/**
|
|
384
|
-
* Whether HTML in-app messages
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
435
|
+
* Whether HTML in-app messages are displayed. Off by default. The markup
|
|
436
|
+
* is kept as written by the server, and author scripts in it do NOT run:
|
|
437
|
+
* the frame's per-message nonce CSP (buildSrcdoc) lets only the SDK's own
|
|
438
|
+
* bridge bootstrap execute. But that frame can execute script at the host
|
|
439
|
+
* page's origin, so a gap in the CSP would be XSS - hence opt-in. Native
|
|
440
|
+
* messages are unaffected: they are data rendered as DOM, with no
|
|
441
|
+
* interpreter.
|
|
388
442
|
*/
|
|
389
443
|
private allowHtmlJsInAppMessages = false;
|
|
390
444
|
|
|
@@ -579,10 +633,15 @@ export class InAppMessagingManager {
|
|
|
579
633
|
this.trackImpression(campaignId, 'clicked').catch(() => {});
|
|
580
634
|
return this.submitForm(campaignId, values);
|
|
581
635
|
},
|
|
582
|
-
// Size the content iframe from the message's reported dimensions.
|
|
583
|
-
//
|
|
584
|
-
//
|
|
585
|
-
|
|
636
|
+
// Size the content iframe from the message's reported dimensions. Only
|
|
637
|
+
// the HEIGHT follows the content. The width is the host's decision:
|
|
638
|
+
// modal/slideup get min(maxWidth, viewport - margin) before the content
|
|
639
|
+
// lays out (sizeContentFrame), banner/fullscreen are 100%. Following the
|
|
640
|
+
// reported width is what made responsive messages impossible - the frame
|
|
641
|
+
// started narrow, the author's media queries fired, the content measured
|
|
642
|
+
// narrow, and the frame stayed narrow. `width` stays in the signature so
|
|
643
|
+
// the bridge every message calls is unchanged.
|
|
644
|
+
setSize: (campaignId: string, _width: number, height: number) => {
|
|
586
645
|
// Guard on the iframe (the thing being sized), set synchronously in
|
|
587
646
|
// display() — not displayedMessages, so a setSize on iframe-load is never
|
|
588
647
|
// rejected by a timing gap.
|
|
@@ -590,9 +649,6 @@ export class InAppMessagingManager {
|
|
|
590
649
|
if (!iframe || !Number.isFinite(height) || height <= 0) return;
|
|
591
650
|
const type = this.displayedMessages.get(campaignId)?.campaign.type;
|
|
592
651
|
iframe.style.height = type === 'fullscreen' ? '100%' : `${Math.ceil(height)}px`;
|
|
593
|
-
if ((type === 'modal' || type === 'slideup') && Number.isFinite(width) && width > 0) {
|
|
594
|
-
iframe.style.width = `${Math.ceil(width)}px`;
|
|
595
|
-
}
|
|
596
652
|
},
|
|
597
653
|
};
|
|
598
654
|
}
|
|
@@ -1203,8 +1259,9 @@ export class InAppMessagingManager {
|
|
|
1203
1259
|
|
|
1204
1260
|
if (kind === 'html') {
|
|
1205
1261
|
if (!renderedHtml.trim()) return 'its `content.html` is empty';
|
|
1206
|
-
// A SKIP, not a failure: HTML
|
|
1207
|
-
//
|
|
1262
|
+
// A SKIP, not a failure: HTML is shown in a frame that can execute script
|
|
1263
|
+
// at the host page's origin (only the SDK bridge, under the nonce CSP),
|
|
1264
|
+
// so an integration that has not opted in never creates the iframe.
|
|
1208
1265
|
if (!this.allowHtmlJsInAppMessages) {
|
|
1209
1266
|
return 'it is an HTML message and allowHtmlJsInAppMessages is false';
|
|
1210
1267
|
}
|
|
@@ -1459,11 +1516,14 @@ export class InAppMessagingManager {
|
|
|
1459
1516
|
// Render the message HTML inside a SAME-ORIGIN SANDBOXED iframe. The sandbox
|
|
1460
1517
|
// (allow-scripts allow-same-origin) is the isolation/encapsulation boundary
|
|
1461
1518
|
// — message CSS can't leak to the host and host CSS can't bleed in — and it
|
|
1462
|
-
// lets the
|
|
1463
|
-
// required so `joryioBridge.requestPushPermission()` keeps the click's
|
|
1519
|
+
// lets the SDK's bridge bootstrap run AND reach `joryioBridge`. Same-origin
|
|
1520
|
+
// is required so `joryioBridge.requestPushPermission()` keeps the click's
|
|
1464
1521
|
// user-gesture activation (a cross-origin postMessage hop would lose it).
|
|
1465
|
-
//
|
|
1466
|
-
//
|
|
1522
|
+
// Author scripts and on* handlers do NOT run: the server keeps the markup
|
|
1523
|
+
// as written (sanitizeInAppHtml drops only <base> and meta refresh), and
|
|
1524
|
+
// the srcdoc's nonce CSP (buildSrcdoc) is what stops them. The sandbox and
|
|
1525
|
+
// the navigation rules contain the rest; there is no server sanitiser in
|
|
1526
|
+
// front of this.
|
|
1467
1527
|
const iframe = document.createElement('iframe');
|
|
1468
1528
|
iframe.id = `joryio-iframe-${campaign.id}`;
|
|
1469
1529
|
// ── Native content: build DOM directly, no iframe ───────────────────────
|
|
@@ -1649,9 +1709,9 @@ export class InAppMessagingManager {
|
|
|
1649
1709
|
let isDismissing = false;
|
|
1650
1710
|
|
|
1651
1711
|
if (campaign.content?.kind !== 'native') {
|
|
1652
|
-
// HTML
|
|
1653
|
-
//
|
|
1654
|
-
// one. This is a SKIP, not a failure: native messages still display,
|
|
1712
|
+
// HTML is shown in an iframe that can execute script at this page's
|
|
1713
|
+
// origin (only the SDK bridge - the nonce CSP blocks author script). An
|
|
1714
|
+
// integration that has not opted in never creates one. This is a SKIP, not a failure: native messages still display,
|
|
1655
1715
|
// because they are data rendered as DOM with no interpreter.
|
|
1656
1716
|
if (!this.allowHtmlJsInAppMessages) {
|
|
1657
1717
|
this.logger.warn(
|
|
@@ -1661,7 +1721,8 @@ export class InAppMessagingManager {
|
|
|
1661
1721
|
return;
|
|
1662
1722
|
}
|
|
1663
1723
|
|
|
1664
|
-
// allow-scripts/allow-same-origin: run the
|
|
1724
|
+
// allow-scripts/allow-same-origin: run the SDK bridge bootstrap (the
|
|
1725
|
+
// nonce CSP blocks every other script) + reach the host bridge.
|
|
1665
1726
|
// allow-popups: a CTA may open a new tab. NOT allow-top-navigation: every
|
|
1666
1727
|
// link and redirect goes through the bridge's `navigate`, which the HOST
|
|
1667
1728
|
// page carries out (rule 57) - the message document itself may never
|
|
@@ -1688,12 +1749,22 @@ export class InAppMessagingManager {
|
|
|
1688
1749
|
});
|
|
1689
1750
|
iframe.setAttribute('title', campaign.name || 'Message');
|
|
1690
1751
|
// Start at 0 height (avoids the ~150px default-iframe flash); the bootstrap
|
|
1691
|
-
// reports real
|
|
1692
|
-
// width; modal/slideup get
|
|
1752
|
+
// reports real height on load via the bridge's setSize. Banner/fullscreen
|
|
1753
|
+
// fill the width; modal/slideup get min(maxWidth, viewport - margin) NOW,
|
|
1754
|
+
// before the document loads, so its media queries see the real width.
|
|
1693
1755
|
iframe.style.height = '0';
|
|
1694
1756
|
if (campaign.type === 'banner' || campaign.type === 'fullscreen') {
|
|
1695
1757
|
iframe.style.width = '100%';
|
|
1696
1758
|
}
|
|
1759
|
+
const maxWidth = inAppMaxWidth(campaign);
|
|
1760
|
+
if (maxWidth !== null) {
|
|
1761
|
+
// The frame width already keeps the margin, so the container's own
|
|
1762
|
+
// caps (90% for modal, 400px for slide-up) would only clip it.
|
|
1763
|
+
container.style.maxWidth = 'none';
|
|
1764
|
+
this.frameMaxWidths.set(campaign.id, maxWidth);
|
|
1765
|
+
iframe.style.width = `${inAppFrameWidth(maxWidth, this.hostViewportWidth())}px`;
|
|
1766
|
+
this.watchViewport();
|
|
1767
|
+
}
|
|
1697
1768
|
iframe.srcdoc = this.buildSrcdoc(
|
|
1698
1769
|
campaign.id,
|
|
1699
1770
|
renderedHtml,
|
|
@@ -1788,6 +1859,51 @@ export class InAppMessagingManager {
|
|
|
1788
1859
|
this.logger.info(`Displayed campaign: ${campaign.name} (${campaign.id})`);
|
|
1789
1860
|
}
|
|
1790
1861
|
|
|
1862
|
+
/** The host page's layout width: without the scrollbar where the browser says so. */
|
|
1863
|
+
private hostViewportWidth(): number {
|
|
1864
|
+
const doc = typeof document !== 'undefined' ? document.documentElement : null;
|
|
1865
|
+
const cw = doc && typeof doc.clientWidth === 'number' ? doc.clientWidth : 0;
|
|
1866
|
+
if (cw > 0) return cw;
|
|
1867
|
+
return typeof window !== 'undefined' && typeof window.innerWidth === 'number' ? window.innerWidth : 0;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
/** Re-derive a content-sized frame's width from its maxWidth and the viewport. */
|
|
1871
|
+
private sizeContentFrame(campaignId: string): void {
|
|
1872
|
+
const iframe = this.iframes.get(campaignId);
|
|
1873
|
+
const maxWidth = this.frameMaxWidths.get(campaignId);
|
|
1874
|
+
if (!iframe || maxWidth === undefined) return;
|
|
1875
|
+
iframe.style.width = `${inAppFrameWidth(maxWidth, this.hostViewportWidth())}px`;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
/**
|
|
1879
|
+
* Follow the viewport while a content-sized message is open: rotate a phone
|
|
1880
|
+
* or narrow a window and the message is re-fitted (debounced). The content's
|
|
1881
|
+
* ResizeObserver then reports the height at the new width.
|
|
1882
|
+
*/
|
|
1883
|
+
private watchViewport(): void {
|
|
1884
|
+
if (this.viewportListener || typeof window === 'undefined' || typeof window.addEventListener !== 'function') return;
|
|
1885
|
+
this.viewportListener = () => {
|
|
1886
|
+
if (this.viewportTimer) clearTimeout(this.viewportTimer);
|
|
1887
|
+
this.viewportTimer = setTimeout(() => {
|
|
1888
|
+
this.viewportTimer = undefined;
|
|
1889
|
+
for (const id of this.frameMaxWidths.keys()) this.sizeContentFrame(id);
|
|
1890
|
+
}, 100);
|
|
1891
|
+
};
|
|
1892
|
+
window.addEventListener('resize', this.viewportListener);
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
private unwatchViewportIfIdle(): void {
|
|
1896
|
+
if (this.frameMaxWidths.size > 0 || !this.viewportListener) return;
|
|
1897
|
+
if (typeof window !== 'undefined' && typeof window.removeEventListener === 'function') {
|
|
1898
|
+
window.removeEventListener('resize', this.viewportListener);
|
|
1899
|
+
}
|
|
1900
|
+
this.viewportListener = undefined;
|
|
1901
|
+
if (this.viewportTimer) {
|
|
1902
|
+
clearTimeout(this.viewportTimer);
|
|
1903
|
+
this.viewportTimer = undefined;
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1791
1907
|
/**
|
|
1792
1908
|
* Build the iframe srcdoc: the message HTML + its CSS + a bootstrap that
|
|
1793
1909
|
* (a) exposes `window.joryioBridge` (proxying same-origin to the host bridge),
|
|
@@ -1800,8 +1916,9 @@ export class InAppMessagingManager {
|
|
|
1800
1916
|
* event name, received `undefined`, and the host guard dropped it - a control
|
|
1801
1917
|
* that looked like it worked and logged nothing. Verbs taking two arguments
|
|
1802
1918
|
* (`navigate`) still cannot be driven declaratively.
|
|
1803
|
-
* NOT sanitized here
|
|
1804
|
-
*
|
|
1919
|
+
* NOT sanitized here, nor by the server beyond <base> and meta refresh: the
|
|
1920
|
+
* nonce CSP below is what keeps author script from running, and the sandbox
|
|
1921
|
+
* and navigation rules contain the document. campaignId + the host
|
|
1805
1922
|
* key are injected as JSON literals (our own values).
|
|
1806
1923
|
*/
|
|
1807
1924
|
private buildSrcdoc(
|
|
@@ -1812,13 +1929,19 @@ export class InAppMessagingManager {
|
|
|
1812
1929
|
): string {
|
|
1813
1930
|
const cid = JSON.stringify(campaignId);
|
|
1814
1931
|
const key = JSON.stringify(HOST_BRIDGE_KEY);
|
|
1815
|
-
// Content-sized types
|
|
1816
|
-
//
|
|
1817
|
-
//
|
|
1932
|
+
// Content-sized types: the HOST picks the width (min(maxWidth, viewport -
|
|
1933
|
+
// margin)) and only the height follows the content. The body used to be
|
|
1934
|
+
// inline-block so its width could be measured and the frame set to it -
|
|
1935
|
+
// which let the frame start narrow and stay narrow. Now the body is a
|
|
1936
|
+
// normal block at the frame's width; flow-root keeps the first and last
|
|
1937
|
+
// child's margins inside it, so its height is the content's height.
|
|
1938
|
+
// That height is measured from the BODY: the root's scrollHeight is never
|
|
1939
|
+
// less than the frame itself, so a message that got shorter (wider
|
|
1940
|
+
// viewport, a form message replaced by a line) could never shrink back.
|
|
1818
1941
|
const contentSized = type === 'modal' || type === 'slideup';
|
|
1819
|
-
const bodySizing = contentSized ? 'body{display:
|
|
1942
|
+
const bodySizing = contentSized ? 'body{display:flow-root;}' : '';
|
|
1820
1943
|
const bootstrap = `(function(){
|
|
1821
|
-
var CID=${cid},KEY=${key};
|
|
1944
|
+
var CID=${cid},KEY=${key},SIZED=${contentSized ? 'true' : 'false'};
|
|
1822
1945
|
// Per-device visibility: tag the root with the REAL device class from the
|
|
1823
1946
|
// HOST page viewport (the iframe is message-sized, so its own innerWidth is
|
|
1824
1947
|
// useless here). The jr-vp-* → jr-hide-* rules in <style> then hide elements
|
|
@@ -1867,7 +1990,7 @@ export class InAppMessagingManager {
|
|
|
1867
1990
|
var p=window.joryioBridge.submitForm(d);if(!p||typeof p.then!=='function'){msg('This form cannot be sent here.',false);f.removeAttribute('data-jry-busy');if(btn)btn.disabled=false;return}
|
|
1868
1991
|
p.then(function(r){if(r&&r.ok){if(r.redirect){window.joryioBridge.navigate(r.redirect,'_top');return}var els2=f.querySelectorAll('input,select,textarea,button');for(var j=0;j<els2.length;j++){els2[j].disabled=true}msg(r.message||'Thank you.',true)}else{msg((r&&(r.message||(r.errors&&r.errors[0])))||'Something went wrong. Please try again.',false);f.removeAttribute('data-jry-busy');if(btn)btn.disabled=false}}).catch(function(){msg('Something went wrong. Please try again.',false);f.removeAttribute('data-jry-busy');if(btn)btn.disabled=false});
|
|
1869
1992
|
},true);
|
|
1870
|
-
function reportSize(){try{var b=document.body,de=document.documentElement;var w=Math.max(b?b.scrollWidth:0,de.scrollWidth);var h=Math.max(b?b.scrollHeight:0,de.scrollHeight);call('setSize',[w,h]);}catch(e){}}
|
|
1993
|
+
function reportSize(){try{var b=document.body,de=document.documentElement;var w=Math.max(b?b.scrollWidth:0,de.scrollWidth);var h=SIZED&&b?Math.ceil(Math.max(b.scrollHeight,b.getBoundingClientRect?b.getBoundingClientRect().height:0)):Math.max(b?b.scrollHeight:0,de.scrollHeight);call('setSize',[w,h]);}catch(e){}}
|
|
1871
1994
|
if(document.readyState!=='loading')reportSize();else document.addEventListener('DOMContentLoaded',reportSize);
|
|
1872
1995
|
window.addEventListener('load',reportSize);
|
|
1873
1996
|
try{if(window.ResizeObserver&&document.body){new ResizeObserver(reportSize).observe(document.body);}}catch(e){}
|
|
@@ -1880,22 +2003,20 @@ export class InAppMessagingManager {
|
|
|
1880
2003
|
/*
|
|
1881
2004
|
* A per-document CSP, and the nonce is the point.
|
|
1882
2005
|
*
|
|
1883
|
-
* The server
|
|
1884
|
-
*
|
|
1885
|
-
*
|
|
1886
|
-
*
|
|
1887
|
-
*
|
|
1888
|
-
* reachable from a compromised marketing account.
|
|
2006
|
+
* The server keeps the author's markup as written (sanitizeInAppHtml drops
|
|
2007
|
+
* only <base> and meta refresh), so author <script> blocks and on*
|
|
2008
|
+
* handlers ARE in this document. Because this iframe is `allow-scripts
|
|
2009
|
+
* allow-same-origin`, they would run at the HOST PAGE'S ORIGIN - XSS on a
|
|
2010
|
+
* customer's site, reachable from a compromised marketing account.
|
|
1889
2011
|
*
|
|
1890
|
-
* `script-src 'nonce-...'`
|
|
1891
|
-
* the nonce, so
|
|
1892
|
-
*
|
|
1893
|
-
* a parser fighting an adversary.
|
|
2012
|
+
* `script-src 'nonce-...'` is the control: only the bootstrap below carries
|
|
2013
|
+
* the nonce, so nothing the author wrote executes. There is no server
|
|
2014
|
+
* sanitiser in front of it; this CSP is the first line, not a backstop.
|
|
1894
2015
|
*
|
|
1895
2016
|
* The rest is least-privilege for content that is only ever a message:
|
|
1896
2017
|
* default-src 'none' nothing is allowed unless named below
|
|
1897
|
-
* style-src unsafe-inline
|
|
1898
|
-
*
|
|
2018
|
+
* style-src unsafe-inline author style="" attributes are kept, so this
|
|
2019
|
+
* must stay permissive; styles cannot execute
|
|
1899
2020
|
* img/media/font https: data: no http subresources - the mixed-content
|
|
1900
2021
|
* control Android enforced and iOS could not
|
|
1901
2022
|
* upgrade-insecure-requests an author's http image is upgraded rather
|
|
@@ -1984,6 +2105,8 @@ export class InAppMessagingManager {
|
|
|
1984
2105
|
container.remove(); // also removes the child content iframe
|
|
1985
2106
|
}
|
|
1986
2107
|
this.iframes.delete(campaignId);
|
|
2108
|
+
this.frameMaxWidths.delete(campaignId);
|
|
2109
|
+
this.unwatchViewportIfIdle();
|
|
1987
2110
|
if (backdrop) {
|
|
1988
2111
|
backdrop.remove();
|
|
1989
2112
|
}
|
|
@@ -2343,6 +2466,8 @@ export class InAppMessagingManager {
|
|
|
2343
2466
|
this.displayedMessages.clear();
|
|
2344
2467
|
this.watchedAttributes.clear();
|
|
2345
2468
|
this.iframes.clear();
|
|
2469
|
+
this.frameMaxWidths.clear();
|
|
2470
|
+
this.unwatchViewportIfIdle();
|
|
2346
2471
|
|
|
2347
2472
|
this.logger.info('In-app messaging state cleared');
|
|
2348
2473
|
}
|
|
@@ -2408,6 +2533,8 @@ export class InAppMessagingManager {
|
|
|
2408
2533
|
this.impressionTimestamps.clear();
|
|
2409
2534
|
this.watchedAttributes.clear();
|
|
2410
2535
|
this.iframes.clear();
|
|
2536
|
+
this.frameMaxWidths.clear();
|
|
2537
|
+
this.unwatchViewportIfIdle();
|
|
2411
2538
|
|
|
2412
2539
|
// Tear down the host bridge so a later new JoryioSDK() rebinds a fresh one
|
|
2413
2540
|
// (installHostBridge overwrites, but clearing here avoids a dangling closure
|