@joryio/web-sdk 1.3.1-beta.1 → 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 +5 -1
- package/dist/core/inapp.d.ts +15 -0
- package/dist/core/inapp.d.ts.map +1 -1
- package/dist/index.d.mts +1 -0
- package/dist/joryio-sw.js +1 -1
- package/dist/joryio.cjs.js +74 -8
- package/dist/joryio.min.js +2 -2
- package/dist/joryio.min.js.map +1 -1
- package/dist/joryio.mjs +74 -8
- package/dist/joryio.umd.js +74 -8
- package/package.json +1 -1
- package/src/core/inapp.ts +136 -16
package/dist/joryio.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Joryio Web SDK v1.3.1-beta.
|
|
2
|
+
* Joryio Web SDK v1.3.1-beta.2
|
|
3
3
|
* (c) 2026 Joryio. All rights reserved.
|
|
4
4
|
* @license Commercial - see https://github.com/joryio/joryio-web-sdk/blob/main/LICENSE
|
|
5
5
|
*/
|
|
@@ -2261,6 +2261,21 @@ class UTMManager {
|
|
|
2261
2261
|
function escapeStyleText(css) {
|
|
2262
2262
|
return typeof css === 'string' ? css.replace(/<\//g, '<\\/') : '';
|
|
2263
2263
|
}
|
|
2264
|
+
const IN_APP_DEFAULT_MAX_WIDTH = { modal: 600, slideup: 400 };
|
|
2265
|
+
const IN_APP_VIEWPORT_MARGIN = 16;
|
|
2266
|
+
function inAppMaxWidth(campaign) {
|
|
2267
|
+
var _a;
|
|
2268
|
+
if (campaign.type !== 'modal' && campaign.type !== 'slideup')
|
|
2269
|
+
return null;
|
|
2270
|
+
if (((_a = campaign.content) === null || _a === void 0 ? void 0 : _a.kind) !== 'html')
|
|
2271
|
+
return null;
|
|
2272
|
+
const mw = campaign.content.maxWidth;
|
|
2273
|
+
return typeof mw === 'number' && Number.isFinite(mw) && mw > 0 ? Math.floor(mw) : IN_APP_DEFAULT_MAX_WIDTH[campaign.type];
|
|
2274
|
+
}
|
|
2275
|
+
function inAppFrameWidth(maxWidth, viewportWidth, margin = IN_APP_VIEWPORT_MARGIN) {
|
|
2276
|
+
const available = Math.max(0, Math.floor(viewportWidth) - 2 * margin);
|
|
2277
|
+
return Math.max(0, Math.min(Math.floor(maxWidth), available));
|
|
2278
|
+
}
|
|
2264
2279
|
const HOST_BRIDGE_KEY = '__joryioInApp';
|
|
2265
2280
|
const IMPRESSIONS_STORAGE_KEY = 'inapp_impressions';
|
|
2266
2281
|
const MAX_DISPLAYED_MESSAGES = 500;
|
|
@@ -2376,6 +2391,7 @@ class InAppMessagingManager {
|
|
|
2376
2391
|
this.syncAgain = false;
|
|
2377
2392
|
this.claimed = new Set();
|
|
2378
2393
|
this.iframes = new Map();
|
|
2394
|
+
this.frameMaxWidths = new Map();
|
|
2379
2395
|
this.allowHtmlJsInAppMessages = false;
|
|
2380
2396
|
this.bridge = null;
|
|
2381
2397
|
this.auth = null;
|
|
@@ -2495,16 +2511,13 @@ class InAppMessagingManager {
|
|
|
2495
2511
|
this.trackImpression(campaignId, 'clicked').catch(() => { });
|
|
2496
2512
|
return this.submitForm(campaignId, values);
|
|
2497
2513
|
},
|
|
2498
|
-
setSize: (campaignId,
|
|
2514
|
+
setSize: (campaignId, _width, height) => {
|
|
2499
2515
|
var _a;
|
|
2500
2516
|
const iframe = this.iframes.get(campaignId);
|
|
2501
2517
|
if (!iframe || !Number.isFinite(height) || height <= 0)
|
|
2502
2518
|
return;
|
|
2503
2519
|
const type = (_a = this.displayedMessages.get(campaignId)) === null || _a === void 0 ? void 0 : _a.campaign.type;
|
|
2504
2520
|
iframe.style.height = type === 'fullscreen' ? '100%' : `${Math.ceil(height)}px`;
|
|
2505
|
-
if ((type === 'modal' || type === 'slideup') && Number.isFinite(width) && width > 0) {
|
|
2506
|
-
iframe.style.width = `${Math.ceil(width)}px`;
|
|
2507
|
-
}
|
|
2508
2521
|
},
|
|
2509
2522
|
};
|
|
2510
2523
|
}
|
|
@@ -3202,6 +3215,13 @@ class InAppMessagingManager {
|
|
|
3202
3215
|
if (campaign.type === 'banner' || campaign.type === 'fullscreen') {
|
|
3203
3216
|
iframe.style.width = '100%';
|
|
3204
3217
|
}
|
|
3218
|
+
const maxWidth = inAppMaxWidth(campaign);
|
|
3219
|
+
if (maxWidth !== null) {
|
|
3220
|
+
container.style.maxWidth = 'none';
|
|
3221
|
+
this.frameMaxWidths.set(campaign.id, maxWidth);
|
|
3222
|
+
iframe.style.width = `${inAppFrameWidth(maxWidth, this.hostViewportWidth())}px`;
|
|
3223
|
+
this.watchViewport();
|
|
3224
|
+
}
|
|
3205
3225
|
iframe.srcdoc = this.buildSrcdoc(campaign.id, renderedHtml, ((_d = campaign.content) === null || _d === void 0 ? void 0 : _d.kind) === 'html' ? campaign.content.css : '', campaign.type);
|
|
3206
3226
|
container.appendChild(iframe);
|
|
3207
3227
|
this.iframes.set(campaign.id, iframe);
|
|
@@ -3255,13 +3275,53 @@ class InAppMessagingManager {
|
|
|
3255
3275
|
this.callHost('onMessageDisplay', () => { var _a, _b; return (_b = (_a = this.displayOptions).onDisplay) === null || _b === void 0 ? void 0 : _b.call(_a, message); });
|
|
3256
3276
|
this.logger.info(`Displayed campaign: ${campaign.name} (${campaign.id})`);
|
|
3257
3277
|
}
|
|
3278
|
+
hostViewportWidth() {
|
|
3279
|
+
const doc = typeof document !== 'undefined' ? document.documentElement : null;
|
|
3280
|
+
const cw = doc && typeof doc.clientWidth === 'number' ? doc.clientWidth : 0;
|
|
3281
|
+
if (cw > 0)
|
|
3282
|
+
return cw;
|
|
3283
|
+
return typeof window !== 'undefined' && typeof window.innerWidth === 'number' ? window.innerWidth : 0;
|
|
3284
|
+
}
|
|
3285
|
+
sizeContentFrame(campaignId) {
|
|
3286
|
+
const iframe = this.iframes.get(campaignId);
|
|
3287
|
+
const maxWidth = this.frameMaxWidths.get(campaignId);
|
|
3288
|
+
if (!iframe || maxWidth === undefined)
|
|
3289
|
+
return;
|
|
3290
|
+
iframe.style.width = `${inAppFrameWidth(maxWidth, this.hostViewportWidth())}px`;
|
|
3291
|
+
}
|
|
3292
|
+
watchViewport() {
|
|
3293
|
+
if (this.viewportListener || typeof window === 'undefined' || typeof window.addEventListener !== 'function')
|
|
3294
|
+
return;
|
|
3295
|
+
this.viewportListener = () => {
|
|
3296
|
+
if (this.viewportTimer)
|
|
3297
|
+
clearTimeout(this.viewportTimer);
|
|
3298
|
+
this.viewportTimer = setTimeout(() => {
|
|
3299
|
+
this.viewportTimer = undefined;
|
|
3300
|
+
for (const id of this.frameMaxWidths.keys())
|
|
3301
|
+
this.sizeContentFrame(id);
|
|
3302
|
+
}, 100);
|
|
3303
|
+
};
|
|
3304
|
+
window.addEventListener('resize', this.viewportListener);
|
|
3305
|
+
}
|
|
3306
|
+
unwatchViewportIfIdle() {
|
|
3307
|
+
if (this.frameMaxWidths.size > 0 || !this.viewportListener)
|
|
3308
|
+
return;
|
|
3309
|
+
if (typeof window !== 'undefined' && typeof window.removeEventListener === 'function') {
|
|
3310
|
+
window.removeEventListener('resize', this.viewportListener);
|
|
3311
|
+
}
|
|
3312
|
+
this.viewportListener = undefined;
|
|
3313
|
+
if (this.viewportTimer) {
|
|
3314
|
+
clearTimeout(this.viewportTimer);
|
|
3315
|
+
this.viewportTimer = undefined;
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3258
3318
|
buildSrcdoc(campaignId, html, css, type) {
|
|
3259
3319
|
const cid = JSON.stringify(campaignId);
|
|
3260
3320
|
const key = JSON.stringify(HOST_BRIDGE_KEY);
|
|
3261
3321
|
const contentSized = type === 'modal' || type === 'slideup';
|
|
3262
|
-
const bodySizing = contentSized ? 'body{display:
|
|
3322
|
+
const bodySizing = contentSized ? 'body{display:flow-root;}' : '';
|
|
3263
3323
|
const bootstrap = `(function(){
|
|
3264
|
-
var CID=${cid},KEY=${key};
|
|
3324
|
+
var CID=${cid},KEY=${key},SIZED=${contentSized ? 'true' : 'false'};
|
|
3265
3325
|
// Per-device visibility: tag the root with the REAL device class from the
|
|
3266
3326
|
// HOST page viewport (the iframe is message-sized, so its own innerWidth is
|
|
3267
3327
|
// useless here). The jr-vp-* → jr-hide-* rules in <style> then hide elements
|
|
@@ -3310,7 +3370,7 @@ class InAppMessagingManager {
|
|
|
3310
3370
|
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}
|
|
3311
3371
|
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});
|
|
3312
3372
|
},true);
|
|
3313
|
-
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){}}
|
|
3373
|
+
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){}}
|
|
3314
3374
|
if(document.readyState!=='loading')reportSize();else document.addEventListener('DOMContentLoaded',reportSize);
|
|
3315
3375
|
window.addEventListener('load',reportSize);
|
|
3316
3376
|
try{if(window.ResizeObserver&&document.body){new ResizeObserver(reportSize).observe(document.body);}}catch(e){}
|
|
@@ -3372,6 +3432,8 @@ class InAppMessagingManager {
|
|
|
3372
3432
|
container.remove();
|
|
3373
3433
|
}
|
|
3374
3434
|
this.iframes.delete(campaignId);
|
|
3435
|
+
this.frameMaxWidths.delete(campaignId);
|
|
3436
|
+
this.unwatchViewportIfIdle();
|
|
3375
3437
|
if (backdrop) {
|
|
3376
3438
|
backdrop.remove();
|
|
3377
3439
|
}
|
|
@@ -3599,6 +3661,8 @@ class InAppMessagingManager {
|
|
|
3599
3661
|
this.displayedMessages.clear();
|
|
3600
3662
|
this.watchedAttributes.clear();
|
|
3601
3663
|
this.iframes.clear();
|
|
3664
|
+
this.frameMaxWidths.clear();
|
|
3665
|
+
this.unwatchViewportIfIdle();
|
|
3602
3666
|
this.logger.info('In-app messaging state cleared');
|
|
3603
3667
|
}
|
|
3604
3668
|
forgetImpressions() {
|
|
@@ -3639,6 +3703,8 @@ class InAppMessagingManager {
|
|
|
3639
3703
|
this.impressionTimestamps.clear();
|
|
3640
3704
|
this.watchedAttributes.clear();
|
|
3641
3705
|
this.iframes.clear();
|
|
3706
|
+
this.frameMaxWidths.clear();
|
|
3707
|
+
this.unwatchViewportIfIdle();
|
|
3642
3708
|
if (typeof window !== 'undefined') {
|
|
3643
3709
|
delete window[HOST_BRIDGE_KEY];
|
|
3644
3710
|
}
|
package/dist/joryio.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Joryio Web SDK v1.3.1-beta.
|
|
2
|
+
* Joryio Web SDK v1.3.1-beta.2
|
|
3
3
|
* (c) 2026 Joryio. All rights reserved.
|
|
4
4
|
* @license Commercial - see https://github.com/joryio/joryio-web-sdk/blob/main/LICENSE
|
|
5
5
|
*/
|
|
@@ -2267,6 +2267,21 @@
|
|
|
2267
2267
|
function escapeStyleText(css) {
|
|
2268
2268
|
return typeof css === 'string' ? css.replace(/<\//g, '<\\/') : '';
|
|
2269
2269
|
}
|
|
2270
|
+
const IN_APP_DEFAULT_MAX_WIDTH = { modal: 600, slideup: 400 };
|
|
2271
|
+
const IN_APP_VIEWPORT_MARGIN = 16;
|
|
2272
|
+
function inAppMaxWidth(campaign) {
|
|
2273
|
+
var _a;
|
|
2274
|
+
if (campaign.type !== 'modal' && campaign.type !== 'slideup')
|
|
2275
|
+
return null;
|
|
2276
|
+
if (((_a = campaign.content) === null || _a === void 0 ? void 0 : _a.kind) !== 'html')
|
|
2277
|
+
return null;
|
|
2278
|
+
const mw = campaign.content.maxWidth;
|
|
2279
|
+
return typeof mw === 'number' && Number.isFinite(mw) && mw > 0 ? Math.floor(mw) : IN_APP_DEFAULT_MAX_WIDTH[campaign.type];
|
|
2280
|
+
}
|
|
2281
|
+
function inAppFrameWidth(maxWidth, viewportWidth, margin = IN_APP_VIEWPORT_MARGIN) {
|
|
2282
|
+
const available = Math.max(0, Math.floor(viewportWidth) - 2 * margin);
|
|
2283
|
+
return Math.max(0, Math.min(Math.floor(maxWidth), available));
|
|
2284
|
+
}
|
|
2270
2285
|
const HOST_BRIDGE_KEY = '__joryioInApp';
|
|
2271
2286
|
const IMPRESSIONS_STORAGE_KEY = 'inapp_impressions';
|
|
2272
2287
|
const MAX_DISPLAYED_MESSAGES = 500;
|
|
@@ -2382,6 +2397,7 @@
|
|
|
2382
2397
|
this.syncAgain = false;
|
|
2383
2398
|
this.claimed = new Set();
|
|
2384
2399
|
this.iframes = new Map();
|
|
2400
|
+
this.frameMaxWidths = new Map();
|
|
2385
2401
|
this.allowHtmlJsInAppMessages = false;
|
|
2386
2402
|
this.bridge = null;
|
|
2387
2403
|
this.auth = null;
|
|
@@ -2501,16 +2517,13 @@
|
|
|
2501
2517
|
this.trackImpression(campaignId, 'clicked').catch(() => { });
|
|
2502
2518
|
return this.submitForm(campaignId, values);
|
|
2503
2519
|
},
|
|
2504
|
-
setSize: (campaignId,
|
|
2520
|
+
setSize: (campaignId, _width, height) => {
|
|
2505
2521
|
var _a;
|
|
2506
2522
|
const iframe = this.iframes.get(campaignId);
|
|
2507
2523
|
if (!iframe || !Number.isFinite(height) || height <= 0)
|
|
2508
2524
|
return;
|
|
2509
2525
|
const type = (_a = this.displayedMessages.get(campaignId)) === null || _a === void 0 ? void 0 : _a.campaign.type;
|
|
2510
2526
|
iframe.style.height = type === 'fullscreen' ? '100%' : `${Math.ceil(height)}px`;
|
|
2511
|
-
if ((type === 'modal' || type === 'slideup') && Number.isFinite(width) && width > 0) {
|
|
2512
|
-
iframe.style.width = `${Math.ceil(width)}px`;
|
|
2513
|
-
}
|
|
2514
2527
|
},
|
|
2515
2528
|
};
|
|
2516
2529
|
}
|
|
@@ -3208,6 +3221,13 @@
|
|
|
3208
3221
|
if (campaign.type === 'banner' || campaign.type === 'fullscreen') {
|
|
3209
3222
|
iframe.style.width = '100%';
|
|
3210
3223
|
}
|
|
3224
|
+
const maxWidth = inAppMaxWidth(campaign);
|
|
3225
|
+
if (maxWidth !== null) {
|
|
3226
|
+
container.style.maxWidth = 'none';
|
|
3227
|
+
this.frameMaxWidths.set(campaign.id, maxWidth);
|
|
3228
|
+
iframe.style.width = `${inAppFrameWidth(maxWidth, this.hostViewportWidth())}px`;
|
|
3229
|
+
this.watchViewport();
|
|
3230
|
+
}
|
|
3211
3231
|
iframe.srcdoc = this.buildSrcdoc(campaign.id, renderedHtml, ((_d = campaign.content) === null || _d === void 0 ? void 0 : _d.kind) === 'html' ? campaign.content.css : '', campaign.type);
|
|
3212
3232
|
container.appendChild(iframe);
|
|
3213
3233
|
this.iframes.set(campaign.id, iframe);
|
|
@@ -3261,13 +3281,53 @@
|
|
|
3261
3281
|
this.callHost('onMessageDisplay', () => { var _a, _b; return (_b = (_a = this.displayOptions).onDisplay) === null || _b === void 0 ? void 0 : _b.call(_a, message); });
|
|
3262
3282
|
this.logger.info(`Displayed campaign: ${campaign.name} (${campaign.id})`);
|
|
3263
3283
|
}
|
|
3284
|
+
hostViewportWidth() {
|
|
3285
|
+
const doc = typeof document !== 'undefined' ? document.documentElement : null;
|
|
3286
|
+
const cw = doc && typeof doc.clientWidth === 'number' ? doc.clientWidth : 0;
|
|
3287
|
+
if (cw > 0)
|
|
3288
|
+
return cw;
|
|
3289
|
+
return typeof window !== 'undefined' && typeof window.innerWidth === 'number' ? window.innerWidth : 0;
|
|
3290
|
+
}
|
|
3291
|
+
sizeContentFrame(campaignId) {
|
|
3292
|
+
const iframe = this.iframes.get(campaignId);
|
|
3293
|
+
const maxWidth = this.frameMaxWidths.get(campaignId);
|
|
3294
|
+
if (!iframe || maxWidth === undefined)
|
|
3295
|
+
return;
|
|
3296
|
+
iframe.style.width = `${inAppFrameWidth(maxWidth, this.hostViewportWidth())}px`;
|
|
3297
|
+
}
|
|
3298
|
+
watchViewport() {
|
|
3299
|
+
if (this.viewportListener || typeof window === 'undefined' || typeof window.addEventListener !== 'function')
|
|
3300
|
+
return;
|
|
3301
|
+
this.viewportListener = () => {
|
|
3302
|
+
if (this.viewportTimer)
|
|
3303
|
+
clearTimeout(this.viewportTimer);
|
|
3304
|
+
this.viewportTimer = setTimeout(() => {
|
|
3305
|
+
this.viewportTimer = undefined;
|
|
3306
|
+
for (const id of this.frameMaxWidths.keys())
|
|
3307
|
+
this.sizeContentFrame(id);
|
|
3308
|
+
}, 100);
|
|
3309
|
+
};
|
|
3310
|
+
window.addEventListener('resize', this.viewportListener);
|
|
3311
|
+
}
|
|
3312
|
+
unwatchViewportIfIdle() {
|
|
3313
|
+
if (this.frameMaxWidths.size > 0 || !this.viewportListener)
|
|
3314
|
+
return;
|
|
3315
|
+
if (typeof window !== 'undefined' && typeof window.removeEventListener === 'function') {
|
|
3316
|
+
window.removeEventListener('resize', this.viewportListener);
|
|
3317
|
+
}
|
|
3318
|
+
this.viewportListener = undefined;
|
|
3319
|
+
if (this.viewportTimer) {
|
|
3320
|
+
clearTimeout(this.viewportTimer);
|
|
3321
|
+
this.viewportTimer = undefined;
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3264
3324
|
buildSrcdoc(campaignId, html, css, type) {
|
|
3265
3325
|
const cid = JSON.stringify(campaignId);
|
|
3266
3326
|
const key = JSON.stringify(HOST_BRIDGE_KEY);
|
|
3267
3327
|
const contentSized = type === 'modal' || type === 'slideup';
|
|
3268
|
-
const bodySizing = contentSized ? 'body{display:
|
|
3328
|
+
const bodySizing = contentSized ? 'body{display:flow-root;}' : '';
|
|
3269
3329
|
const bootstrap = `(function(){
|
|
3270
|
-
var CID=${cid},KEY=${key};
|
|
3330
|
+
var CID=${cid},KEY=${key},SIZED=${contentSized ? 'true' : 'false'};
|
|
3271
3331
|
// Per-device visibility: tag the root with the REAL device class from the
|
|
3272
3332
|
// HOST page viewport (the iframe is message-sized, so its own innerWidth is
|
|
3273
3333
|
// useless here). The jr-vp-* → jr-hide-* rules in <style> then hide elements
|
|
@@ -3316,7 +3376,7 @@
|
|
|
3316
3376
|
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}
|
|
3317
3377
|
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});
|
|
3318
3378
|
},true);
|
|
3319
|
-
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){}}
|
|
3379
|
+
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){}}
|
|
3320
3380
|
if(document.readyState!=='loading')reportSize();else document.addEventListener('DOMContentLoaded',reportSize);
|
|
3321
3381
|
window.addEventListener('load',reportSize);
|
|
3322
3382
|
try{if(window.ResizeObserver&&document.body){new ResizeObserver(reportSize).observe(document.body);}}catch(e){}
|
|
@@ -3378,6 +3438,8 @@
|
|
|
3378
3438
|
container.remove();
|
|
3379
3439
|
}
|
|
3380
3440
|
this.iframes.delete(campaignId);
|
|
3441
|
+
this.frameMaxWidths.delete(campaignId);
|
|
3442
|
+
this.unwatchViewportIfIdle();
|
|
3381
3443
|
if (backdrop) {
|
|
3382
3444
|
backdrop.remove();
|
|
3383
3445
|
}
|
|
@@ -3605,6 +3667,8 @@
|
|
|
3605
3667
|
this.displayedMessages.clear();
|
|
3606
3668
|
this.watchedAttributes.clear();
|
|
3607
3669
|
this.iframes.clear();
|
|
3670
|
+
this.frameMaxWidths.clear();
|
|
3671
|
+
this.unwatchViewportIfIdle();
|
|
3608
3672
|
this.logger.info('In-app messaging state cleared');
|
|
3609
3673
|
}
|
|
3610
3674
|
forgetImpressions() {
|
|
@@ -3645,6 +3709,8 @@
|
|
|
3645
3709
|
this.impressionTimestamps.clear();
|
|
3646
3710
|
this.watchedAttributes.clear();
|
|
3647
3711
|
this.iframes.clear();
|
|
3712
|
+
this.frameMaxWidths.clear();
|
|
3713
|
+
this.unwatchViewportIfIdle();
|
|
3648
3714
|
if (typeof window !== 'undefined') {
|
|
3649
3715
|
delete window[HOST_BRIDGE_KEY];
|
|
3650
3716
|
}
|
package/package.json
CHANGED
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,6 +422,14 @@ 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
435
|
* Whether HTML in-app messages are displayed. Off by default. The markup
|
|
@@ -582,10 +633,15 @@ export class InAppMessagingManager {
|
|
|
582
633
|
this.trackImpression(campaignId, 'clicked').catch(() => {});
|
|
583
634
|
return this.submitForm(campaignId, values);
|
|
584
635
|
},
|
|
585
|
-
// Size the content iframe from the message's reported dimensions.
|
|
586
|
-
//
|
|
587
|
-
//
|
|
588
|
-
|
|
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) => {
|
|
589
645
|
// Guard on the iframe (the thing being sized), set synchronously in
|
|
590
646
|
// display() — not displayedMessages, so a setSize on iframe-load is never
|
|
591
647
|
// rejected by a timing gap.
|
|
@@ -593,9 +649,6 @@ export class InAppMessagingManager {
|
|
|
593
649
|
if (!iframe || !Number.isFinite(height) || height <= 0) return;
|
|
594
650
|
const type = this.displayedMessages.get(campaignId)?.campaign.type;
|
|
595
651
|
iframe.style.height = type === 'fullscreen' ? '100%' : `${Math.ceil(height)}px`;
|
|
596
|
-
if ((type === 'modal' || type === 'slideup') && Number.isFinite(width) && width > 0) {
|
|
597
|
-
iframe.style.width = `${Math.ceil(width)}px`;
|
|
598
|
-
}
|
|
599
652
|
},
|
|
600
653
|
};
|
|
601
654
|
}
|
|
@@ -1696,12 +1749,22 @@ export class InAppMessagingManager {
|
|
|
1696
1749
|
});
|
|
1697
1750
|
iframe.setAttribute('title', campaign.name || 'Message');
|
|
1698
1751
|
// Start at 0 height (avoids the ~150px default-iframe flash); the bootstrap
|
|
1699
|
-
// reports real
|
|
1700
|
-
// 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.
|
|
1701
1755
|
iframe.style.height = '0';
|
|
1702
1756
|
if (campaign.type === 'banner' || campaign.type === 'fullscreen') {
|
|
1703
1757
|
iframe.style.width = '100%';
|
|
1704
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
|
+
}
|
|
1705
1768
|
iframe.srcdoc = this.buildSrcdoc(
|
|
1706
1769
|
campaign.id,
|
|
1707
1770
|
renderedHtml,
|
|
@@ -1796,6 +1859,51 @@ export class InAppMessagingManager {
|
|
|
1796
1859
|
this.logger.info(`Displayed campaign: ${campaign.name} (${campaign.id})`);
|
|
1797
1860
|
}
|
|
1798
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
|
+
|
|
1799
1907
|
/**
|
|
1800
1908
|
* Build the iframe srcdoc: the message HTML + its CSS + a bootstrap that
|
|
1801
1909
|
* (a) exposes `window.joryioBridge` (proxying same-origin to the host bridge),
|
|
@@ -1821,13 +1929,19 @@ export class InAppMessagingManager {
|
|
|
1821
1929
|
): string {
|
|
1822
1930
|
const cid = JSON.stringify(campaignId);
|
|
1823
1931
|
const key = JSON.stringify(HOST_BRIDGE_KEY);
|
|
1824
|
-
// Content-sized types
|
|
1825
|
-
//
|
|
1826
|
-
//
|
|
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.
|
|
1827
1941
|
const contentSized = type === 'modal' || type === 'slideup';
|
|
1828
|
-
const bodySizing = contentSized ? 'body{display:
|
|
1942
|
+
const bodySizing = contentSized ? 'body{display:flow-root;}' : '';
|
|
1829
1943
|
const bootstrap = `(function(){
|
|
1830
|
-
var CID=${cid},KEY=${key};
|
|
1944
|
+
var CID=${cid},KEY=${key},SIZED=${contentSized ? 'true' : 'false'};
|
|
1831
1945
|
// Per-device visibility: tag the root with the REAL device class from the
|
|
1832
1946
|
// HOST page viewport (the iframe is message-sized, so its own innerWidth is
|
|
1833
1947
|
// useless here). The jr-vp-* → jr-hide-* rules in <style> then hide elements
|
|
@@ -1876,7 +1990,7 @@ export class InAppMessagingManager {
|
|
|
1876
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}
|
|
1877
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});
|
|
1878
1992
|
},true);
|
|
1879
|
-
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){}}
|
|
1880
1994
|
if(document.readyState!=='loading')reportSize();else document.addEventListener('DOMContentLoaded',reportSize);
|
|
1881
1995
|
window.addEventListener('load',reportSize);
|
|
1882
1996
|
try{if(window.ResizeObserver&&document.body){new ResizeObserver(reportSize).observe(document.body);}}catch(e){}
|
|
@@ -1991,6 +2105,8 @@ export class InAppMessagingManager {
|
|
|
1991
2105
|
container.remove(); // also removes the child content iframe
|
|
1992
2106
|
}
|
|
1993
2107
|
this.iframes.delete(campaignId);
|
|
2108
|
+
this.frameMaxWidths.delete(campaignId);
|
|
2109
|
+
this.unwatchViewportIfIdle();
|
|
1994
2110
|
if (backdrop) {
|
|
1995
2111
|
backdrop.remove();
|
|
1996
2112
|
}
|
|
@@ -2350,6 +2466,8 @@ export class InAppMessagingManager {
|
|
|
2350
2466
|
this.displayedMessages.clear();
|
|
2351
2467
|
this.watchedAttributes.clear();
|
|
2352
2468
|
this.iframes.clear();
|
|
2469
|
+
this.frameMaxWidths.clear();
|
|
2470
|
+
this.unwatchViewportIfIdle();
|
|
2353
2471
|
|
|
2354
2472
|
this.logger.info('In-app messaging state cleared');
|
|
2355
2473
|
}
|
|
@@ -2415,6 +2533,8 @@ export class InAppMessagingManager {
|
|
|
2415
2533
|
this.impressionTimestamps.clear();
|
|
2416
2534
|
this.watchedAttributes.clear();
|
|
2417
2535
|
this.iframes.clear();
|
|
2536
|
+
this.frameMaxWidths.clear();
|
|
2537
|
+
this.unwatchViewportIfIdle();
|
|
2418
2538
|
|
|
2419
2539
|
// Tear down the host bridge so a later new JoryioSDK() rebinds a fresh one
|
|
2420
2540
|
// (installHostBridge overwrites, but clearing here avoids a dangling closure
|