@joryio/web-sdk 1.3.0 → 1.3.1-beta.1
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 +4 -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.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/joryio-sw.js +1 -1
- package/dist/joryio.cjs.js +2 -2
- package/dist/joryio.min.js +2 -2
- package/dist/joryio.min.js.map +1 -1
- package/dist/joryio.mjs +2 -2
- package/dist/joryio.umd.js +2 -2
- package/package.json +1 -1
- package/src/core/config.ts +13 -10
- package/src/core/inapp.ts +35 -28
package/dist/joryio.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Joryio Web SDK v1.3.
|
|
2
|
+
* Joryio Web SDK v1.3.1-beta.1
|
|
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
|
*/
|
|
6
|
-
const SDK_VERSION = '1.3.
|
|
6
|
+
const SDK_VERSION = '1.3.1';
|
|
7
7
|
const DEFAULT_API_ENDPOINT = 'https://api-eu1.joryio.com/api';
|
|
8
8
|
function normalizeApiEndpoint(raw) {
|
|
9
9
|
const trimmed = (raw || '').trim();
|
package/dist/joryio.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Joryio Web SDK v1.3.
|
|
2
|
+
* Joryio Web SDK v1.3.1-beta.1
|
|
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
|
*/
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.JoryioSDK = {}));
|
|
10
10
|
})(this, (function (exports) { 'use strict';
|
|
11
11
|
|
|
12
|
-
const SDK_VERSION = '1.3.
|
|
12
|
+
const SDK_VERSION = '1.3.1';
|
|
13
13
|
const DEFAULT_API_ENDPOINT = 'https://api-eu1.joryio.com/api';
|
|
14
14
|
function normalizeApiEndpoint(raw) {
|
|
15
15
|
const trimmed = (raw || '').trim();
|
package/package.json
CHANGED
package/src/core/config.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* SDK Configuration Types and Defaults
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
export const SDK_VERSION = '1.3.
|
|
5
|
+
export const SDK_VERSION = '1.3.1'; // keep in sync with package.json — the backend serves this in X-SDK-Version headers
|
|
6
6
|
export const DEFAULT_API_ENDPOINT = 'https://api-eu1.joryio.com/api';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -88,17 +88,20 @@ export interface InAppOptions {
|
|
|
88
88
|
*
|
|
89
89
|
* What actually happens, because the name alone cannot carry it:
|
|
90
90
|
*
|
|
91
|
-
* - The server
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* -
|
|
95
|
-
*
|
|
91
|
+
* - The server keeps the authored HTML as written (only `<base>` and
|
|
92
|
+
* `<meta http-equiv="refresh">` are removed), so `<script>` and `on*`
|
|
93
|
+
* handlers reach the browser - and do NOT run: the frame carries a fresh
|
|
94
|
+
* nonce Content-Security-Policy per message, and only the SDK's bootstrap
|
|
95
|
+
* has the nonce. Buttons and forms work through that bootstrap
|
|
96
|
+
* (`data-joryio-action`, `data-jry-form`), not through author script.
|
|
97
|
+
* - The message is rendered in an iframe with `allow-scripts
|
|
98
|
+
* allow-same-origin`, because the bootstrap runs there (height
|
|
96
99
|
* reporting, click logging, the host bridge). That frame therefore CAN
|
|
97
100
|
* execute script, at your page's origin.
|
|
98
|
-
* - So the residual risk is a
|
|
99
|
-
*
|
|
100
|
-
* this is opt-in and why the name keeps `Js` -
|
|
101
|
-
* alone would read like a formatting preference.
|
|
101
|
+
* - So the residual risk is a gap in the CSP (a browser that does not
|
|
102
|
+
* enforce it): author script would then run with your origin's
|
|
103
|
+
* privileges. That is why this is opt-in and why the name keeps `Js` -
|
|
104
|
+
* `allowHtmlInAppMessages` alone would read like a formatting preference.
|
|
102
105
|
*
|
|
103
106
|
* Leaving it off does not disable in-app messaging: NATIVE messages still
|
|
104
107
|
* display, because they are data rendered into DOM text nodes with no
|
package/src/core/inapp.ts
CHANGED
|
@@ -381,10 +381,13 @@ export class InAppMessagingManager {
|
|
|
381
381
|
private iframes: Map<string, HTMLIFrameElement> = new Map();
|
|
382
382
|
|
|
383
383
|
/**
|
|
384
|
-
* Whether HTML in-app messages
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
384
|
+
* Whether HTML in-app messages are displayed. Off by default. The markup
|
|
385
|
+
* is kept as written by the server, and author scripts in it do NOT run:
|
|
386
|
+
* the frame's per-message nonce CSP (buildSrcdoc) lets only the SDK's own
|
|
387
|
+
* bridge bootstrap execute. But that frame can execute script at the host
|
|
388
|
+
* page's origin, so a gap in the CSP would be XSS - hence opt-in. Native
|
|
389
|
+
* messages are unaffected: they are data rendered as DOM, with no
|
|
390
|
+
* interpreter.
|
|
388
391
|
*/
|
|
389
392
|
private allowHtmlJsInAppMessages = false;
|
|
390
393
|
|
|
@@ -1203,8 +1206,9 @@ export class InAppMessagingManager {
|
|
|
1203
1206
|
|
|
1204
1207
|
if (kind === 'html') {
|
|
1205
1208
|
if (!renderedHtml.trim()) return 'its `content.html` is empty';
|
|
1206
|
-
// A SKIP, not a failure: HTML
|
|
1207
|
-
//
|
|
1209
|
+
// A SKIP, not a failure: HTML is shown in a frame that can execute script
|
|
1210
|
+
// at the host page's origin (only the SDK bridge, under the nonce CSP),
|
|
1211
|
+
// so an integration that has not opted in never creates the iframe.
|
|
1208
1212
|
if (!this.allowHtmlJsInAppMessages) {
|
|
1209
1213
|
return 'it is an HTML message and allowHtmlJsInAppMessages is false';
|
|
1210
1214
|
}
|
|
@@ -1459,11 +1463,14 @@ export class InAppMessagingManager {
|
|
|
1459
1463
|
// Render the message HTML inside a SAME-ORIGIN SANDBOXED iframe. The sandbox
|
|
1460
1464
|
// (allow-scripts allow-same-origin) is the isolation/encapsulation boundary
|
|
1461
1465
|
// — 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
|
|
1466
|
+
// lets the SDK's bridge bootstrap run AND reach `joryioBridge`. Same-origin
|
|
1467
|
+
// is required so `joryioBridge.requestPushPermission()` keeps the click's
|
|
1464
1468
|
// user-gesture activation (a cross-origin postMessage hop would lose it).
|
|
1465
|
-
//
|
|
1466
|
-
//
|
|
1469
|
+
// Author scripts and on* handlers do NOT run: the server keeps the markup
|
|
1470
|
+
// as written (sanitizeInAppHtml drops only <base> and meta refresh), and
|
|
1471
|
+
// the srcdoc's nonce CSP (buildSrcdoc) is what stops them. The sandbox and
|
|
1472
|
+
// the navigation rules contain the rest; there is no server sanitiser in
|
|
1473
|
+
// front of this.
|
|
1467
1474
|
const iframe = document.createElement('iframe');
|
|
1468
1475
|
iframe.id = `joryio-iframe-${campaign.id}`;
|
|
1469
1476
|
// ── Native content: build DOM directly, no iframe ───────────────────────
|
|
@@ -1649,9 +1656,9 @@ export class InAppMessagingManager {
|
|
|
1649
1656
|
let isDismissing = false;
|
|
1650
1657
|
|
|
1651
1658
|
if (campaign.content?.kind !== 'native') {
|
|
1652
|
-
// HTML
|
|
1653
|
-
//
|
|
1654
|
-
// one. This is a SKIP, not a failure: native messages still display,
|
|
1659
|
+
// HTML is shown in an iframe that can execute script at this page's
|
|
1660
|
+
// origin (only the SDK bridge - the nonce CSP blocks author script). An
|
|
1661
|
+
// integration that has not opted in never creates one. This is a SKIP, not a failure: native messages still display,
|
|
1655
1662
|
// because they are data rendered as DOM with no interpreter.
|
|
1656
1663
|
if (!this.allowHtmlJsInAppMessages) {
|
|
1657
1664
|
this.logger.warn(
|
|
@@ -1661,7 +1668,8 @@ export class InAppMessagingManager {
|
|
|
1661
1668
|
return;
|
|
1662
1669
|
}
|
|
1663
1670
|
|
|
1664
|
-
// allow-scripts/allow-same-origin: run the
|
|
1671
|
+
// allow-scripts/allow-same-origin: run the SDK bridge bootstrap (the
|
|
1672
|
+
// nonce CSP blocks every other script) + reach the host bridge.
|
|
1665
1673
|
// allow-popups: a CTA may open a new tab. NOT allow-top-navigation: every
|
|
1666
1674
|
// link and redirect goes through the bridge's `navigate`, which the HOST
|
|
1667
1675
|
// page carries out (rule 57) - the message document itself may never
|
|
@@ -1800,8 +1808,9 @@ export class InAppMessagingManager {
|
|
|
1800
1808
|
* event name, received `undefined`, and the host guard dropped it - a control
|
|
1801
1809
|
* that looked like it worked and logged nothing. Verbs taking two arguments
|
|
1802
1810
|
* (`navigate`) still cannot be driven declaratively.
|
|
1803
|
-
* NOT sanitized here
|
|
1804
|
-
*
|
|
1811
|
+
* NOT sanitized here, nor by the server beyond <base> and meta refresh: the
|
|
1812
|
+
* nonce CSP below is what keeps author script from running, and the sandbox
|
|
1813
|
+
* and navigation rules contain the document. campaignId + the host
|
|
1805
1814
|
* key are injected as JSON literals (our own values).
|
|
1806
1815
|
*/
|
|
1807
1816
|
private buildSrcdoc(
|
|
@@ -1880,22 +1889,20 @@ export class InAppMessagingManager {
|
|
|
1880
1889
|
/*
|
|
1881
1890
|
* A per-document CSP, and the nonce is the point.
|
|
1882
1891
|
*
|
|
1883
|
-
* The server
|
|
1884
|
-
*
|
|
1885
|
-
*
|
|
1886
|
-
*
|
|
1887
|
-
*
|
|
1888
|
-
* reachable from a compromised marketing account.
|
|
1892
|
+
* The server keeps the author's markup as written (sanitizeInAppHtml drops
|
|
1893
|
+
* only <base> and meta refresh), so author <script> blocks and on*
|
|
1894
|
+
* handlers ARE in this document. Because this iframe is `allow-scripts
|
|
1895
|
+
* allow-same-origin`, they would run at the HOST PAGE'S ORIGIN - XSS on a
|
|
1896
|
+
* customer's site, reachable from a compromised marketing account.
|
|
1889
1897
|
*
|
|
1890
|
-
* `script-src 'nonce-...'`
|
|
1891
|
-
* the nonce, so
|
|
1892
|
-
*
|
|
1893
|
-
* a parser fighting an adversary.
|
|
1898
|
+
* `script-src 'nonce-...'` is the control: only the bootstrap below carries
|
|
1899
|
+
* the nonce, so nothing the author wrote executes. There is no server
|
|
1900
|
+
* sanitiser in front of it; this CSP is the first line, not a backstop.
|
|
1894
1901
|
*
|
|
1895
1902
|
* The rest is least-privilege for content that is only ever a message:
|
|
1896
1903
|
* default-src 'none' nothing is allowed unless named below
|
|
1897
|
-
* style-src unsafe-inline
|
|
1898
|
-
*
|
|
1904
|
+
* style-src unsafe-inline author style="" attributes are kept, so this
|
|
1905
|
+
* must stay permissive; styles cannot execute
|
|
1899
1906
|
* img/media/font https: data: no http subresources - the mixed-content
|
|
1900
1907
|
* control Android enforced and iOS could not
|
|
1901
1908
|
* upgrade-insecure-requests an author's http image is upgraded rather
|