@arsel.sa/web-sdk 1.0.0 → 1.1.0

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 CHANGED
@@ -7,6 +7,87 @@ Breaking changes to the public surface wait for a major release, and are listed
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.1.0] — 2026-08-23
11
+
12
+ ### Added
13
+
14
+ - **In-app messaging.** Messages authored in Arsel now render in the page, with no notification
15
+ permission and no prompt of any kind. The server resolves audience, consent, campaign window,
16
+ grants and lifetime caps into a per-device bundle; the SDK evaluates the trigger and the
17
+ session-scoped caps locally, so drawing a message costs no network round-trip. Four layouts —
18
+ `MODAL`, `BANNER_TOP`, `BANNER_BOTTOM`, `IMAGE_ONLY` — rendered into a closed shadow root with
19
+ a constructed stylesheet (falling back to `<style>`), text set with `textContent` only, and
20
+ colours applied through CSSOM so a strict `style-src` cannot strip them.
21
+ - **`screen(name, properties?)`.** Records a screen or page view. One request, two consumers: the
22
+ event reaches segments and automations exactly as `track()` would, and it is the trigger source
23
+ for `SCREEN_VIEW` in-app messages.
24
+ - **`suppressInAppMessages(boolean)`.** Holds messages back for the moments a host app knows are
25
+ wrong — a checkout step, a video playing full-screen.
26
+ - **`inApp` config option.** `true` by default; pass `false` to disable, or `{ zIndex, closeLabel }`
27
+ to tune the layer. The default `zIndex` sits below the maximum so a host site's own top-most
28
+ modal still wins.
29
+ - **Device registration without a push subscription.** `init()` now registers the browser so the
30
+ bundle fetch can authenticate. It shows no prompt, creates no subscription, and never touches the
31
+ service worker — gating in-app behind `promptForPush()` would have restricted the channel to the
32
+ minority who accept notifications.
33
+ - **Accessibility.** Modals get `role="dialog"`, `aria-modal`, a focus trap reading `root.activeElement`
34
+ (a closed root makes `document.activeElement` resolve to the host), focus capture and restore, and
35
+ `Escape` to dismiss. Banners get `role="status"` with `aria-live="polite"` and never steal focus.
36
+ Direction is read from the host page, not assumed.
37
+ - **`arsel_iam_sync` service-worker handling.** A reserved silent push refreshes the bundle and
38
+ renders nothing. Ships inert — nothing emits it yet — so refresh is driven by `init()`, tab
39
+ visibility and the bundle's own TTL.
40
+ - Diagnostics gain `inAppMessages`, `inAppBundleVersion`, `inAppFetchedAtMs` and
41
+ `pendingInAppBeacons`.
42
+ - **`diagnostics().configError`.** Reports why initialization was refused, readable before
43
+ initialization — which is precisely the state it describes.
44
+
45
+ ### Changed
46
+
47
+ - **`getJson()` returns the full `{ result, code, body }` envelope**, matching `post()`. The bundle
48
+ fetch has to branch on `304` *before* `classify()` sees it — `classify` maps 304 to `permanent`,
49
+ which would discard the cache on every successful revalidation.
50
+ - **The store's queue helpers take a leading queue name.** In-app beacons live in their own object
51
+ store: the events drain stops at the first retryable failure to preserve history order, so a
52
+ single stuck beacon sharing that queue would wedge the entire analytics pipeline behind it.
53
+ - IndexedDB schema version 1 → 2, adding the beacon store. The upgrade is guarded by store name, so
54
+ a browser installed at v1 gains only what it is missing.
55
+ - The service worker now closes its database handle after reading. It opens without a version, and
56
+ a connection left open makes the page's upgrade fire `blocked` and hang until the worker is
57
+ terminated.
58
+ - **`init()` no longer rejects on invalid configuration.** It is routinely called un-awaited, so the
59
+ rejection reached the page as an unhandled rejection the host could not catch — and an analytics
60
+ SDK must not break a page over its own misconfiguration. It logs, declines to start, and the
61
+ collecting calls then genuinely no-op: a refused SDK that still queued would grow IndexedDB
62
+ behind a flush that can never succeed. All three Arsel SDKs now behave identically here.
63
+ - The four configuration rules match the Android and iOS SDKs exactly, including the `pub_` prefix
64
+ check — the one that catches a secret API key pasted into page-readable source.
65
+
66
+ ### Fixed
67
+
68
+ - **An unreachable push service no longer escapes `init()`.** `pushManager.subscribe()` rejects
69
+ whenever the browser cannot reach its push service — a firewalled network, a captive portal,
70
+ private browsing, a Chromium build without push support. None are the caller's mistake and none
71
+ are feature-detectable. Both call sites now resolve to `null` like every other failure here.
72
+ - **`init()` no longer leaks an unhandled rejection from its background work.** It kicks off the
73
+ stranded-events flush and the push reconcile fire-and-forget, and neither carried a catch — so
74
+ anything they threw (a queue row that will not parse, an IndexedDB failure) surfaced on the host
75
+ page as a rejection it could not intercept. The same failure mode `init()` itself was hardened
76
+ against in this release, through a different door. Both now match the catch every other
77
+ fire-and-forget call site here already used.
78
+ - **A revoked device no longer reports as subscribed.** A durable opt-out answers the register call
79
+ with `200` and deliberately leaves the row `REVOKED`; reading only the HTTP status, `subscribe()`
80
+ returned `true`, and `isSubscribed()` asked the browser, which keeps its `PushSubscription`
81
+ across an opt-out. So `promptForPush()` reported success and an app would show "notifications on"
82
+ to someone who can never receive another push. The backend's status is now persisted and
83
+ consulted as `subscriptionStatus`, matching the Android SDK.
84
+
85
+ ### Requires
86
+
87
+ - Backend support for the in-app endpoints, CORS on `/api/v1/orgs/*/in-app/*`, `If-None-Match` in
88
+ the client-API allowed headers, and the org's `allowedOrigins` containing the customer's origin —
89
+ an empty allowlist matches nothing and rejects every visitor.
90
+
10
91
  ## [1.0.0] — 2026-08-17
11
92
 
12
93
  ### Added