@arsel.sa/web-sdk 1.0.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 ADDED
@@ -0,0 +1,56 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@arsel.sa/web-sdk`. Format follows [Keep a Changelog](https://keepachangelog.com/),
4
+ versioning follows [Semantic Versioning](https://semver.org/).
5
+
6
+ Breaking changes to the public surface wait for a major release, and are listed here explicitly.
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.0.0] — 2026-08-17
11
+
12
+ ### Added
13
+
14
+ - **Events channel.** `track(name, properties)` with a durable IndexedDB queue: events are persisted
15
+ before they are sent, drain oldest-first in batches of up to 50 per request, stop at the first
16
+ retryable failure so history is never reordered, and discard permanent failures rather than
17
+ wedging the queue behind them — logging the response body when a drop carried identifiers.
18
+ Retries dedupe via persisted per-event idempotency keys and an `Idempotency-Key` request header.
19
+ Drains also trigger on `online` and on the tab becoming visible. Event properties pass through as
20
+ arbitrary nested JSON (`Date` → ISO string, non-JSON values dropped, 64 KB cap). `track()` calls
21
+ made before `init()` are buffered (up to 100) and enqueued, in order, when `init()` runs.
22
+ - **Identity.** `identify({ externalId, email, phoneNumber })` and `reset()`. `reset()` rotates the
23
+ anonymous id so a shared computer does not hand the next person the previous one's history.
24
+ `phoneNumber` is validated as E.164 and `email` for shape at the door — invalid values are
25
+ rejected with a console error instead of stored, so one bad identifier cannot poison every
26
+ subsequent event.
27
+ - **`optOut()`** — durable, server-side, non-resurrectable push opt-out for this browser, distinct
28
+ from `reset()`, which never touches push.
29
+ - **Sessions.** `arsel.session_start` / `arsel.session_end` from visibility transitions, with the
30
+ industry-standard 30-minute background gap and no timers. The end event is emitted on the next
31
+ visit, backdated to when the page actually went away. Sessions only open for pages actually seen —
32
+ background tabs and prerendered pages wait for their first visible transition.
33
+ - **Web push.** `promptForPush()`, opt-in service-worker registration (`serviceWorkerPath`, or
34
+ `serviceWorker: 'external'` for PWAs whose own worker imports arsel-sw.js — the SDK never
35
+ clobbers an existing worker), subscription registration carrying the `anonymousId` that binds the
36
+ subscription to the contact, and engagements (`delivered`, `displayed`, `suppressed`,
37
+ `opened`, `clicked`, `dismissed`) — exactly one per tap, with the post-tap programmatic close
38
+ never double-reported as a dismissal, and the `delivered` engagement raced against a short budget so
39
+ it can never delay display. Worker readiness is verified with bounded, actionable failures before
40
+ the permission prompt, never via a `.ready` that can hang forever.
41
+ - **Silent VAPID reconciliation** on `init()`: a browser subscribed under a rotated keypair
42
+ re-subscribes without prompting, instead of going quietly unreachable.
43
+ - **`pushsubscriptionchange` handling** in the service worker, for browser-initiated endpoint
44
+ rotation.
45
+ - `diagnostics()` — a support-ticket-safe snapshot containing no keys and no device secret.
46
+ - `flushNow()`, `getAnonymousId()`, `debug` mode.
47
+ - Every request carries `X-Arsel-SDK: web/<version>`.
48
+ - `baseUrl` may be plain `http://` for `localhost`/`127.0.0.1`, for local backends.
49
+ - ESM + UMD builds. The UMD build exposes named exports on `window.Arsel`.
50
+
51
+ ### Notes
52
+
53
+ - The events API never depends on push. `track()` and `identify()` work with notifications denied,
54
+ blocked, or never requested.
55
+ - The publishable `pub_…` client key is page-readable by design. Origin allowlisting bounds the
56
+ residual risk. A secret API key must never appear in page source.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BasicsEngage (Arsel)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # Arsel Web SDK
2
+
3
+ Events, identity and web push for the browser. ~4 kB gzipped, no dependencies, ESM + UMD.
4
+
5
+ ```js
6
+ import Arsel from '@arsel.sa/web-sdk';
7
+
8
+ await Arsel.init({ clientKey: 'pub_…', baseUrl: 'https://api.arsel.sa' });
9
+
10
+ Arsel.track('product.viewed', { sku: 'A-1023', price: 149.99 });
11
+ Arsel.identify({ externalId: user.id });
12
+ ```
13
+
14
+ **Two channels, and only one of them needs push.** `track()` and `identify()` work with notifications
15
+ denied, blocked, or never asked for — a visitor who declines still has a contact and a behavioural
16
+ history. Only delivery needs a subscription. Nothing in the events API waits on the push API.
17
+
18
+ ---
19
+
20
+ ## Documentation
21
+
22
+ | | |
23
+ | --- | --- |
24
+ | **[Quickstart](docs/quickstart.md)** | Install, initialize, and verify in about ten minutes. |
25
+ | **[Identity](docs/identity.md)** | Anonymous → identified, the identifier ladder, merges, `reset()`. |
26
+ | **[Events](docs/events.md)** | Custom events, properties, limits, reserved events, sessions, durability. |
27
+ | **[Web push](docs/web-push.md)** | Service worker, VAPID, permission UX, browser support, engagements. |
28
+ | **[API reference](docs/api-reference.md)** | Every method: signature, arguments, returns, when to call it. |
29
+ | **[Troubleshooting](docs/troubleshooting.md)** | Symptom → cause → fix, and the things that only look like bugs. |
30
+ | **[Data collection](docs/data-collection.md)** | What the SDK stores and sends, and what it never does. GDPR/erasure. |
31
+ | **[Migrating from CleverTap](docs/migrating-from-clevertap.md)** | API mapping, identity mapping, and the traps. |
32
+ | **[Changelog](CHANGELOG.md)** | Release notes. |
33
+
34
+ ## Requirements
35
+
36
+ | | |
37
+ | --- | --- |
38
+ | Browsers | Chrome/Edge 79+, Firefox 72+, Safari 16.4+, and their mobile equivalents |
39
+ | Transport | Your **page** must be served over HTTPS for push (`http://localhost` is the browser's one exemption). The **API `baseUrl`** must be HTTPS too, except `http://localhost` / `http://127.0.0.1` for a local backend. |
40
+ | Bundlers | Any, or none — the UMD build works from a script tag |
41
+ | Runtime deps | None |
42
+
43
+ Push has a narrower support surface than events; see the matrix in
44
+ [docs/web-push.md](docs/web-push.md#browser-support).
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ npm install @arsel.sa/web-sdk
50
+ ```
51
+
52
+ Or from a script tag, which exposes the same surface on `window.Arsel`. unpkg and jsDelivr serve the
53
+ UMD build straight from npm — **pin the version**, and add an
54
+ [SRI](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) hash so a
55
+ compromised CDN cannot run arbitrary script on your pages:
56
+
57
+ ```html
58
+ <script src="https://unpkg.com/@arsel.sa/web-sdk@1.0.0/dist/arsel.umd.cjs"
59
+ integrity="sha384-…" crossorigin="anonymous"></script>
60
+ <script>Arsel.init({ clientKey: 'pub_…', baseUrl: 'https://api.arsel.sa' })</script>
61
+ ```
62
+
63
+ Or skip the third party altogether: copy `node_modules/@arsel.sa/web-sdk/dist/arsel.umd.cjs` into your
64
+ static assets and serve it from your own origin.
65
+
66
+ For push, also add **one file** to your site — see
67
+ [the service worker](docs/quickstart.md#3-add-the-service-worker).
68
+
69
+ ## `clientKey` is publishable, and that is the design
70
+
71
+ It authenticates the event and push channels and grants nothing else — no reads, no contact list, none
72
+ of what a secret API key can do. Every vendor in this category does the same: Klaviyo's site ID,
73
+ CleverTap's Account ID, Braze's SDK key, Mixpanel's project token.
74
+
75
+ **Never put a secret API key in page source.** The residual risk on a publishable key is data
76
+ *integrity*, not confidentiality: someone who lifts it could post junk events. Origin allowlisting on
77
+ your org is what bounds that — a browser cannot forge its `Origin` header.
78
+
79
+ ## Contributing
80
+
81
+ ```bash
82
+ npm install
83
+ npm run typecheck # tsc --noEmit — the gate; there is no lint step
84
+ npm test # Vitest
85
+ npm run build # dist/arsel.js (ESM) + dist/arsel.umd.cjs
86
+ ```
87
+
88
+ ## License
89
+
90
+ MIT.