@sesamy/sesamy-js 1.119.2 → 1.120.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/README.md CHANGED
@@ -1,22 +1,533 @@
1
1
  # sesamy-js
2
2
 
3
- The Sesamy browser javascript API
3
+ The Sesamy browser JavaScript API.
4
4
 
5
- The project handles analytics, authentication and communication with the Sesamy API
5
+ `@sesamy/sesamy-js` is the publisher-side runtime that powers paywalls,
6
+ authentication, content gating, and analytics on Sesamy-integrated sites. It
7
+ exposes a single `window.sesamy` object (the namespace is configurable) with
8
+ ~30 namespaced services backed by the Sesamy API, plus ready-made handlers for
9
+ URL/hash triggers, content selectors, DOM transforms, consent, and DCA
10
+ (Capsule) decryption.
6
11
 
7
- ## Analytics
12
+ # Installation
8
13
 
9
- The analytics module is used to track events and page views. It is using the [GetAnalytics](https://getanalytics.io/) library to send events to the Sesamy interaction endpoint.
14
+ ```bash
15
+ npm install @sesamy/sesamy-js
16
+ # or
17
+ pnpm add @sesamy/sesamy-js
18
+ ```
19
+
20
+ The package ships several entry points so you can pick the build that fits
21
+ your integration:
22
+
23
+ | Import | Purpose |
24
+ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
25
+ | `@sesamy/sesamy-js` | Core library — `init()` and the `SesamyAPI` surface (ESM/CJS). |
26
+ | `@sesamy/sesamy-js/auth0-plugin` | Auth0 SPA auth plugin. Required for SPA login unless using BFF cookies. |
27
+ | `@sesamy/sesamy-js/capsule-plugin` | Capsule (DCA) decryption plugin. Required when `capsule.enabled` is `true`. |
28
+ | `@sesamy/sesamy-js/bootstrap` | Tiny inline loader (`sesamyBootstrap` + `renderBootstrapScript`) that orchestrates the script chain and prefetches `/auth/userinfo`. |
29
+
30
+ Pre-built IIFE bundles for classic `<script>` tags are also published:
31
+
32
+ - `dist/sesamy-js.iife.js` — core, all-in-one (loadable directly without ESM).
33
+ - `dist/auth0-plugin.iife.js` — auth plugin as an IIFE that registers
34
+ `window.auth0Plugin.createAuth0Plugin`.
35
+ - `dist/capsule-plugin.iife.js` — capsule plugin as an IIFE that registers
36
+ `window.capsulePlugin.createCapsulePlugin`.
37
+ - `dist/bootstrap.iife.js` — bootstrap loader as an IIFE.
38
+
39
+ Publishers using Sesamy's hosted scripts host typically don't reference these
40
+ files directly; instead they let the bootstrap loader resolve them from
41
+ `https://scripts.sesamy.com/s/{clientId}/{name}/{version}.js`.
42
+
43
+ # Quick start
44
+
45
+ The recommended way to embed sesamy-js on a publisher site is via the
46
+ **bootstrap loader**. It downloads the auth/capsule plugins, the components
47
+ package, and the core bundle in parallel (in the right execution order) and
48
+ prefetches `/auth/userinfo` so the user's session is hydrated by the time the
49
+ core finishes loading.
50
+
51
+ Drop this in `<head>`:
52
+
53
+ ```html
54
+ <script type="application/json" id="sesamy-js">
55
+ {
56
+ "clientId": "your-client-id",
57
+ "environment": "prod"
58
+ }
59
+ </script>
60
+ <script src="https://scripts.sesamy.com/s/your-client-id/bootstrap/stable.js"></script>
61
+ ```
62
+
63
+ The bootstrap script auto-detects the `<script id="sesamy-js">` config element
64
+ and pulls forward `clientId`, `environment`, `version`, `src`, plugin-version
65
+ overrides, fallback options, and the plugin/component flags below. No extra
66
+ inline call is needed.
67
+
68
+ Once the chain finishes, sesamy-js is reachable as `window.sesamy` (or the
69
+ namespace you set in `api.namespace`) and the `sesamyJsReady` event fires.
70
+
71
+ ```javascript
72
+ window.addEventListener('sesamyJsReady', () => {
73
+ console.log('Authenticated?', window.sesamy.auth.isAuthenticated());
74
+ });
75
+ ```
76
+
77
+ # Integration modes
78
+
79
+ sesamy-js supports several distinct integration shapes. Pick the one that
80
+ matches your stack — they are not mutually exclusive (e.g. you can use the
81
+ bootstrap loader together with BFF cookie auth).
82
+
83
+ ## Bootstrap loader (recommended)
84
+
85
+ Use when: you embed sesamy-js on a server-rendered publisher site and want a
86
+ single `<script>` in `<head>` that handles everything. Sets
87
+ `window.__sesamyBoot` with a parallel userinfo prefetch promise that the core
88
+ reuses, eliminating a round-trip from the critical path.
89
+
90
+ The bootstrap loader (`@sesamy/sesamy-js/bootstrap`) ships a
91
+ `sesamyBootstrap(options)` function that:
92
+
93
+ 1. Optionally fetches `/auth/userinfo` in parallel with the bundle download
94
+ when the `sesamy_is_authenticated=true` hint cookie is present (or skips
95
+ if `skipAuthPrefetch` is `true`).
96
+ 2. Injects the auth0-plugin, capsule-plugin (if enabled), sesamy-components,
97
+ and the sesamy-js core as `<script async=false>` tags so they execute in
98
+ order while downloading in parallel.
99
+ 3. Falls back to `fallbackSrc` if the primary core never installs
100
+ `window[namespace]` within `fallbackTimeoutMs` (default 3000ms).
101
+
102
+ Auto-run from the config element:
103
+
104
+ ```html
105
+ <script type="application/json" id="sesamy-js">
106
+ { "clientId": "demo", "environment": "prod" }
107
+ </script>
108
+ <script src="https://scripts.sesamy.com/s/demo/bootstrap/stable.js"></script>
109
+ ```
110
+
111
+ Or call it manually (useful from SSR templates, before the config element is
112
+ in the DOM):
113
+
114
+ ```html
115
+ <script>
116
+ sesamyBootstrap({
117
+ clientId: 'demo',
118
+ environment: 'prod',
119
+ version: 'stable',
120
+ fallbackSrc: 'https://scripts-fallback.sesamy.com/.../sesamy-js.iife.js',
121
+ debug: false,
122
+ });
123
+ </script>
124
+ ```
125
+
126
+ For server-rendered pages, use `renderBootstrapScript()` to serialise the
127
+ function plus its options into an inline script body:
128
+
129
+ ```typescript
130
+ import { renderBootstrapScript } from '@sesamy/sesamy-js/bootstrap';
131
+
132
+ const inline = renderBootstrapScript({
133
+ clientId: 'demo',
134
+ environment: 'prod',
135
+ skipAuthPrefetch: false,
136
+ });
137
+ // emit `<script>${inline}</script>` in your HTML response
138
+ ```
139
+
140
+ ### `BootstrapOptions`
141
+
142
+ | Option | Type | Default | Purpose |
143
+ | ---------------------- | ----------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------- |
144
+ | `clientId` | `string` | — | Resolves script chain URLs to `https://scripts.sesamy.com/s/{clientId}/{name}/{version}.js`. Required unless `src` is set. |
145
+ | `version` | `string` | `'stable'` | Version channel served by scripts-host. |
146
+ | `componentsVersion` | `string` | `version` | Per-entry override for `sesamy-components`. |
147
+ | `auth0PluginVersion` | `string` | `version` | Per-entry override for `auth0-plugin`. |
148
+ | `capsulePluginVersion` | `string` | `version` | Per-entry override for `capsule-plugin`. |
149
+ | `src` | `string` | — | Override the core bundle URL. Disables plugin/component chaining (escape hatch for self-contained bundles). |
150
+ | `environment` | `'dev' \| 'prod'` | `'prod'` | Origin to resolve `clientId` against (`scripts.sesamy.com` vs `scripts.sesamy.dev`). |
151
+ | `fallbackSrc` | `string` | — | Secondary bundle URL loaded if the core fails or doesn't install `window[namespace]` in time. |
152
+ | `fallbackTimeoutMs` | `number` | `3000` | Timeout before the fallback fires. |
153
+ | `apiBaseUrl` | `string` | inferred | Base URL for the userinfo prefetch. When unset, falls back to `auth.baseUrl` from the page config (so the prefetch matches the runtime BFF plugin) and then to same-origin. |
154
+ | `namespace` | `string` | `'sesamy'` | Window namespace the core installs onto. |
155
+ | `skipAuthPrefetch` | `boolean` | `false` | Skip the `/auth/userinfo` prefetch (e.g. when injecting the id-token via SSR). |
156
+ | `skipComponents` | `boolean` | `false` | Skip loading `sesamy-components`. |
157
+ | `loadAuth0Plugin` | `boolean` | inferred | Force auth0-plugin loading on/off. Default: load unless `auth.useHttpCookies === true` in the config element. |
158
+ | `loadCapsulePlugin` | `boolean` | inferred | Force capsule-plugin loading on/off. Default: load when `capsule.enabled === true` in the config element. |
159
+ | `debug` | `boolean` | `false` | Emit `[sesamy-boot]` log lines for chain decisions, prefetch source, load/error events, and fallback triggers. |
160
+
161
+ ## Auto-init from `<script id="sesamy-js">`
162
+
163
+ Use when: you've already loaded the core bundle some other way (custom CDN,
164
+ self-host) and just want sesamy-js to bootstrap itself from a JSON config
165
+ block.
166
+
167
+ The core looks for a `<script type="application/json" id="sesamy-js">` element
168
+ and, if found, calls `init()` with the parsed config. Only `clientId` is
169
+ required.
170
+
171
+ ```html
172
+ <script type="application/json" id="sesamy-js">
173
+ {
174
+ "clientId": "demo",
175
+ "environment": "prod",
176
+ "auth": { "domain": "auth.example.com" },
177
+ "api": { "namespace": "sesamy" }
178
+ }
179
+ </script>
180
+ <script src="/path/to/sesamy-js.iife.js"></script>
181
+ ```
182
+
183
+ ## Programmatic `init(config)`
184
+
185
+ Use when: your app is an SPA that already manages its own bootstrapping (e.g.
186
+ you import sesamy-js from npm and want full control over when it initialises).
187
+
188
+ ```typescript
189
+ import { init } from '@sesamy/sesamy-js';
190
+
191
+ const sesamy = await init({
192
+ clientId: 'demo',
193
+ vendorId: 'demo',
194
+ environment: 'prod',
195
+ auth: { domain: 'auth.example.com' },
196
+ });
197
+
198
+ if (await sesamy.auth.isAuthenticated()) {
199
+ const profile = await sesamy.profile.get();
200
+ }
201
+ ```
202
+
203
+ `init()` returns the `SesamyAPI` object and also installs it on
204
+ `window[namespace]` for parity with the auto-init path. Pass `awaitAllServices:
205
+ true` in the config to make the returned promise wait for analytics, auth,
206
+ content, transforms, and capsule to finish initialising; the default is to
207
+ return early and run those services in the background.
208
+
209
+ You can also pass plugins explicitly instead of relying on
210
+ `window.auth0Plugin` / `window.capsulePlugin` auto-detection:
211
+
212
+ ```typescript
213
+ import { init } from '@sesamy/sesamy-js';
214
+ import { createAuth0Plugin } from '@sesamy/sesamy-js/auth0-plugin';
215
+ import { createCapsulePlugin } from '@sesamy/sesamy-js/capsule-plugin';
216
+
217
+ await init(config, {
218
+ authPlugin: createAuth0Plugin(),
219
+ capsulePlugin: createCapsulePlugin(),
220
+ });
221
+ ```
222
+
223
+ ## BFF / cookie-based authentication
224
+
225
+ Use when: your backend already terminates Sesamy auth (the API proxy's Token
226
+ Handler) and you want sesamy-js to use HttpOnly cookies instead of the SPA
227
+ Auth0 plugin. The auth0-plugin is **not** loaded; login/logout/refresh go
228
+ through `/auth/login`, `/auth/callback`, `/auth/logout`, and `/auth/userinfo`
229
+ on the same origin by default.
230
+
231
+ ```html
232
+ <script type="application/json" id="sesamy-js">
233
+ {
234
+ "clientId": "demo",
235
+ "vendorId": "demo",
236
+ "auth": { "useHttpCookies": true },
237
+ "api": { "endpoint": "" }
238
+ }
239
+ </script>
240
+ ```
241
+
242
+ To namespace auth under a sub-path (e.g. when running behind a first-party
243
+ proxy that reserves the top-level `/auth/*` path for something else), set
244
+ `auth.baseUrl`:
245
+
246
+ ```html
247
+ <script type="application/json" id="sesamy-js">
248
+ {
249
+ "clientId": "acme",
250
+ "auth": { "useHttpCookies": true, "baseUrl": "/sesamy" }
251
+ }
252
+ </script>
253
+ ```
254
+
255
+ The plugin then calls `/sesamy/auth/userinfo`, `/sesamy/auth/{vendor}/login`,
256
+ and `/sesamy/auth/logout`. `auth.baseUrl` is independent of `api.endpoint` —
257
+ the API and auth proxies may live on different sub-paths or hosts.
258
+
259
+ Notes:
260
+
261
+ - When `useHttpCookies` is `true` and no `api.endpoint` is set, sesamy-js
262
+ defaults the API endpoint to `/api` on the current origin (cross-origin
263
+ cookies can't reach `api2.sesamy.com`).
264
+ - For SSR, set `auth.idToken` to a server-injected id-token and sesamy-js
265
+ treats the user as authenticated immediately, skipping `/auth/userinfo`.
266
+ Alternatively include
267
+ `<script type="application/json" id="sesamy-server-state">{"idToken":"eyJ..."}</script>` —
268
+ the cookie-auth plugin reads it automatically.
269
+ - The bootstrap loader detects `useHttpCookies === true` in the config element
270
+ and skips the auth0-plugin entry in the chain.
271
+
272
+ ## Capsule (DCA) decryption
273
+
274
+ Use when: your articles ship as encrypted DCA payloads and you need
275
+ sesamy-js to fetch unlock tokens, decrypt the manifest, and inject the
276
+ plaintext into `data-dca-content-name="..."` placeholders.
277
+
278
+ ```html
279
+ <script type="application/json" id="sesamy-js">
280
+ {
281
+ "clientId": "demo",
282
+ "auth": { "useHttpCookies": true },
283
+ "capsule": { "enabled": true, "autoProcess": true }
284
+ }
285
+ </script>
286
+
287
+ <!-- Register the capsule plugin before sesamy-js auto-inits -->
288
+ <script type="module">
289
+ import { createCapsulePlugin } from '@sesamy/sesamy-js/capsule-plugin';
290
+ window.capsulePlugin = { createCapsulePlugin };
291
+ </script>
292
+ <script src="/path/to/sesamy-js.iife.js"></script>
293
+ ```
294
+
295
+ When `autoProcess` is `true` (the default), sesamy-js detects the DCA
296
+ manifest on page load, calls the unlock endpoint, decrypts every
297
+ `data-dca-content-name` block, and starts a `MutationObserver` for SPA
298
+ navigation. To process content manually call `sesamy.capsule.processPage()`,
299
+ or call `sesamy.content.unlock(selector)` for per-article control.
300
+
301
+ ## IIFE / classic `<script>` tag
302
+
303
+ Use when: the host site has no build step. Drop the IIFE bundle in directly:
304
+
305
+ ```html
306
+ <script type="application/json" id="sesamy-js">
307
+ { "clientId": "demo" }
308
+ </script>
309
+ <script src="https://scripts.sesamy.com/s/demo/sesamy-js/stable.js"></script>
310
+ ```
311
+
312
+ The IIFE bundle assumes the auth0-plugin / capsule-plugin IIFEs (if needed)
313
+ have already executed and registered their factories on `window`. The
314
+ bootstrap loader automates that ordering — using IIFEs without the bootstrap
315
+ means you're responsible for inserting plugin scripts before the core script.
316
+
317
+ # Configuration
318
+
319
+ `init()` and the auto-init JSON block accept the same `Config` shape:
320
+
321
+ ```typescript
322
+ interface Config {
323
+ clientId: string; // required
324
+ vendorId: string; // required
325
+ environment?: 'dev' | 'prod';
326
+ organization?: string; // Auth0 org id
327
+ api?: Partial<ApiConfig>;
328
+ analytics?: Partial<AnalyticsConfig>;
329
+ auth?: Partial<AuthConfig>;
330
+ content?: ContentNode[];
331
+ transform?: TransformConfig;
332
+ capsule?: CapsuleConfig;
333
+ consent?: ConsentConfig;
334
+ awaitAllServices?: boolean; // default: false
335
+ }
336
+ ```
337
+
338
+ ## `api`
339
+
340
+ | Field | Type | Default | Purpose |
341
+ | ------------- | ----------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------- |
342
+ | `namespace` | `string` | `'sesamy'` | Property on `window` where the API is installed. |
343
+ | `endpoint` | `string` | `https://api2.sesamy.com` | Absolute URL or relative path. Use a relative path (e.g. `/api`) when proxying through your own backend. |
344
+ | `environment` | `'dev' \| 'prod'` | inherits `Config.environment` | Picks `api2.sesamy.com` vs `api2.sesamy.dev`. |
345
+
346
+ When `auth.useHttpCookies` is `true` the API client switches to
347
+ `credentials: 'include'` and drops the `Authorization` header.
348
+
349
+ ## `auth`
350
+
351
+ | Field | Type | Default | Purpose |
352
+ | ------------------ | ---------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
353
+ | `enabled` | `boolean` | `true` | Disable auth entirely (anonymous-only mode). |
354
+ | `domain` | `string` | — | Single custom Auth0 domain. |
355
+ | `domains` | `string[]` | — | Multiple auth domains for multi-tenant / white-label setups. The library picks the entry whose top-level domain matches the current page; falls back to `domain`. |
356
+ | `useRefreshTokens` | `boolean` | `false` | Enable refresh-token rotation in the auth0-plugin. |
357
+ | `useHttpCookies` | `boolean` | `false` | Switch to cookie-based BFF auth. Skips the auth0-plugin even if registered. |
358
+ | `baseUrl` | `string` | `""` | Base URL the BFF plugin prepends to its `/auth/*` paths. Set when running behind a first-party proxy that namespaces auth under a sub-path (e.g. `"/sesamy"` → `/sesamy/auth/userinfo`, `/sesamy/auth/{vendor}/login`). Independent of `api.endpoint`. |
359
+ | `idToken` | `string` | — | Server-injected id-token for SSR. When set, sesamy-js treats the user as authenticated without calling `/auth/userinfo`. |
360
+
361
+ See [Cross-domain authentication](#cross-domain-authentication) below for how
362
+ the library picks between popup and redirect login on cross-domain setups.
363
+
364
+ ## `analytics`
365
+
366
+ | Field | Type | Default | Purpose |
367
+ | ------------- | ----------------- | -------- | --------------------------------------------- |
368
+ | `enabled` | `boolean` | `true` | Toggle the analytics service. |
369
+ | `environment` | `'dev' \| 'prod'` | inherits | Picks `logs.sesamy.com` vs `logs.sesamy.dev`. |
370
+
371
+ The analytics service is built on [GetAnalytics](https://getanalytics.io/) and
372
+ ships these events to the Sesamy interaction endpoint:
373
+
374
+ - Page views, triggered on router updates.
375
+ - Scroll events at 25%, 50%, 75%, 100%.
376
+ - Active vs idle duration.
377
+ - Custom events emitted via `analytics.track(name, properties)`.
378
+ - All events emitted through `events.emit(...)` (prefixed with `event:`).
379
+
380
+ When `consent.enabled` is `true`, analytics storage is gated on the
381
+ `statistics` consent flag — see [Consent](#consent) below.
382
+
383
+ ## `content`
384
+
385
+ An array of `ContentNode` objects describing how to discover articles in the
386
+ DOM and (optionally) what paywall rules apply. Each node currently has
387
+ `type: 'article'` and supports the following matchers and selectors:
388
+
389
+ ```typescript
390
+ {
391
+ type: 'article',
392
+ // Matchers — all optional, AND-ed together
393
+ path?: string, // current URL contains this path
394
+ queryParam?: { key: string; value: string },
395
+ headers?: { name: string; contains: string },
396
+ userAgentContains?: string,
397
+ // Pricing / paywall metadata (forwarded to checkout)
398
+ pass?: string,
399
+ price?: { amount: number; currency: string },
400
+ paywallUrl?: string,
401
+ enablePaywallSettingsUrlFallback?: boolean,
402
+ // DOM selectors (defaults provided — override per site)
403
+ selectors?: {
404
+ article?: { selector: string; attribute?: string },
405
+ image?: { selector: string; attribute?: string },
406
+ title?: { selector: string; attribute?: string },
407
+ excerpt?: { selector: string; attribute?: string },
408
+ price?: { selector: string; attribute?: string },
409
+ currency?: { selector: string; attribute?: string },
410
+ accessLevel?: { selector: string; attribute?: string },
411
+ url?: { selector: string; attribute?: string },
412
+ pass?: { selector: string; attribute?: string },
413
+ id?: { selector: string; attribute?: string },
414
+ paywallUrl?: { selector: string; attribute?: string },
415
+ }
416
+ }
417
+ ```
418
+
419
+ See the [Content](#content-1) section below for `content.list/get/hasAccess/unlock`
420
+ and the modal/notification-bar UI helpers.
421
+
422
+ ## `transform`
423
+
424
+ Selector-based DOM rules applied after content discovery (and after Capsule
425
+ decryption when DCA is enabled).
426
+
427
+ ```typescript
428
+ {
429
+ enabled?: boolean,
430
+ rules: Array<{
431
+ selector: string,
432
+ transform: 'insert' | 'replace' | 'remove' | 'gradient' | 'wrap',
433
+ contentType: 'html' | 'selector' | 'url',
434
+ content?: string,
435
+ insertionPoint?: 'before' | 'after' | 'inside',
436
+ path?: string, // only apply on matching URLs
437
+ entitlement?: string, // only apply when user has this entitlement
438
+ authenticated?: boolean, // only apply for logged-in users
439
+ }>
440
+ }
441
+ ```
442
+
443
+ See [Transforms](#transforms) below for full semantics and examples.
444
+
445
+ ## `capsule`
446
+
447
+ | Field | Type | Default | Purpose |
448
+ | ------------- | -------------- | ------- | -------------------------------------------------------------------------------- |
449
+ | `enabled` | `boolean` | `false` | Initialise the DCA client. Required for any decryption to happen. |
450
+ | `clientBound` | `boolean` | `false` | Wrap the unlock key with RSA-OAEP for client-bound transport. |
451
+ | `rsaKeySize` | `2048 \| 4096` | `2048` | RSA key size when `clientBound` is `true`. |
452
+ | `autoProcess` | `boolean` | `true` | Auto-detect DCA manifests and decrypt on page load + observe for SPA navigation. |
453
+
454
+ When `enabled` is `true` the bootstrap loader pulls in
455
+ `@sesamy/sesamy-js/capsule-plugin` automatically. If you self-host, register
456
+ the plugin on `window.capsulePlugin` before sesamy-js auto-inits or pass it
457
+ via `init(config, { capsulePlugin })`.
10
458
 
11
- The following events are tracked:
459
+ ## `consent`
12
460
 
13
- - Page views, with events triggered on router updates
14
- - Scroll events, with events triggered on scroll at 25%, 50%, 75%, and 100%
15
- - Active and idle duration
461
+ | Field | Type | Default | Purpose |
462
+ | ----------------- | ------------------------------------------------------ | ----------------------------------------- | ------------------------------------------------------------------------------------------------- |
463
+ | `enabled` | `boolean` | — | Required. When `true`, analytics storage is gated on consent. |
464
+ | `cmp` | `'cookiebot' \| 'onetrust' \| 'google-consent-mode'` | — | Auto-integrate with the named consent management platform. |
465
+ | `defaultConsent` | `Partial<ConsentState>` | `{ statistics: false, marketing: false }` | Initial consent state before the CMP responds. |
466
+ | `onConsentChange` | `(update: (c: Partial<ConsentState>) => void) => void` | — | Custom CMP integration — the callback receives a function to push consent updates into sesamy-js. |
16
467
 
17
- ## API
468
+ `ConsentState` has two fields: `statistics` and `marketing`. Read/write at
469
+ runtime via `sesamy.consent.get/set/has` — see [Consent](#consent-api) below.
18
470
 
19
- The following methods are available on the `sesamy` object:
471
+ ## `awaitAllServices`
472
+
473
+ When `true`, the promise returned by `init()` resolves only after analytics,
474
+ auth, content, transforms, and capsule have finished initialising. Default
475
+ `false` so the page can keep rendering while those services start in the
476
+ background.
477
+
478
+ # Authentication configuration
479
+
480
+ ## Custom domains
481
+
482
+ For a single custom Auth0 domain:
483
+
484
+ ```javascript
485
+ {
486
+ auth: {
487
+ clientId: 'your-client-id',
488
+ domain: 'auth.example.com',
489
+ },
490
+ }
491
+ ```
492
+
493
+ ### Multiple domains (multi-tenant / white-label)
494
+
495
+ For applications that operate across multiple domains, use the `domains`
496
+ array. The library extracts the top-level domain from the current page URL
497
+ and picks the matching entry; if no entry matches, it falls back to `domain`,
498
+ then to the default Sesamy auth domain.
499
+
500
+ ```javascript
501
+ {
502
+ auth: {
503
+ clientId: 'your-client-id',
504
+ domains: [
505
+ 'auth.brand1.com',
506
+ 'auth.brand2.com',
507
+ 'auth.example.co.uk',
508
+ ],
509
+ domain: 'default-auth.example.com', // fallback if no match
510
+ },
511
+ }
512
+ ```
513
+
514
+ ## Cross-domain authentication
515
+
516
+ When the application domain differs from the auth domain's top-level domain,
517
+ the library automatically uses popup-based login on Safari/iOS and Android
518
+ to handle the cross-domain cookies properly. Desktop browsers stay on
519
+ redirect-based login regardless.
520
+
521
+ | App domain | Auth domain | Mobile | Desktop |
522
+ | ----------------- | -------------------- | -------- | -------- |
523
+ | `app.example.com` | `auth.different.com` | Popup | Redirect |
524
+ | `app.example.com` | `auth.example.com` | Redirect | Redirect |
525
+
526
+ # API reference
527
+
528
+ All services hang off `window[namespace]` (default `window.sesamy`) and the
529
+ object returned by `init()`. The following methods are available — full
530
+ per-method documentation follows below.
20
531
 
21
532
  - amendments
22
533
  - create: create an amendment to a contract
@@ -28,12 +539,14 @@ The following methods are available on the `sesamy` object:
28
539
  - set: set the attributions for a user
29
540
  - auth
30
541
  - getUser: fetches the user's profile
542
+ - getTokenSilently: retrieves a token without user interaction
31
543
  - isAuthenticated: checks if the user is authenticated
32
544
  - login: smart login that automatically chooses popup or redirect based on browser context
545
+ - loginWithPopup: opens a popup window to complete the login
33
546
  - loginWithRedirect: redirects the user to the login page
34
547
  - logout: logs the user out
35
- - setToken: stores a token in session storage
36
- - getTokenSilently: retrieves a token without user interaction
548
+ - refresh: re-checks the authentication state from the auth provider
549
+ - setToken: stores a token in local storage and triggers the authenticated event
37
550
  - bills
38
551
  - get: get a bill by id
39
552
  - list: lists all the user's bills
@@ -41,27 +554,43 @@ The following methods are available on the `sesamy` object:
41
554
  - detectAdblock: detects if an ad blocker is enabled
42
555
  - isInAppBrowser: detects if the browser is an in-app browser
43
556
  - isIncognito: detects if the browser is in incognito mode
557
+ - capsule
558
+ - getClient: returns the underlying DCA client (when `capsule.enabled` is true)
559
+ - processPage: detect and decrypt every DCA manifest on the current page
44
560
  - checkouts
45
561
  - create: creates a checkout session
46
562
  - get: gets a checkout session by id
47
563
  - update: updates a checkout session
48
564
  - clearCache: clears the cache for the sesamy-js library
565
+ - consent
566
+ - get: returns the current consent state
567
+ - has: checks whether a specific consent flag is granted
568
+ - set: updates the consent state and re-evaluates analytics storage
49
569
  - content
50
- - list: get all articles based on the configured selectors
51
570
  - get: get an article based on an element or a selector
52
571
  - getLanguage: get the current language
53
- - unlock: retrieves the locked content using an element or a css selector
54
572
  - getPropertyFromHTML: extracts a property from HTML content
573
+ - hasAccess: checks if the user has access to a specific article element
574
+ - list: get all articles based on the configured selectors
575
+ - showModal: opens the paywall modal (requires sesamy-components)
576
+ - showNotificationBar: shows an inline notification bar above/below content
577
+ - unlock: retrieves the locked content using an element or a css selector
55
578
  - contracts
56
579
  - cancel: cancel a contract by id
57
580
  - get: get a contract by id
58
581
  - list: lists all the user's contracts
582
+ - diagnostics
583
+ - collect: gathers a snapshot of profile, entitlements, contracts, tags, and tallies for support tickets
584
+ - send: collects diagnostics and uploads them to the Sesamy diagnostics endpoint
59
585
  - entitlements
60
586
  - access: fetches the user's access URL for an entitlement
61
587
  - get: gets a single entitlement by id
62
588
  - hasAccess: checks if the user has access to a specific item
63
589
  - list: lists the user's entitlements
64
590
  - signedLinks: lists signed links registered in the current session
591
+ - events
592
+ - cancel: cancels a cancelable event with an optional reason message
593
+ - emit: emits a custom event that can optionally be canceled by listeners
65
594
  - flags
66
595
  - get: gets a feature flag value
67
596
  - set: sets a feature flag value
@@ -139,189 +668,6 @@ The library can trigger actions based on query parameters. The following query p
139
668
 
140
669
  The library can read the access token from the hash. It is not the preferred way of logging in a user but can be used when redirecting the user across domains where a cookie-based solution is not possible. The hash is not sent to the server so the is no risk of leaking the token. The token hash is passed like this: `#access_token=<token>`
141
670
 
142
- ## Usage
143
-
144
- The script can either be initiated with a JSON object in a script tag or by calling the init method.
145
-
146
- The script will look for a script tag with the id `sesamy-js`, and if it isn't found it will wait for a call to the init function before initializing. The only required part is the `clientId` attribute.
147
-
148
- ```html
149
- <script type="application/json" id="sesamy-js">
150
- {
151
- "clientId": "demo"
152
- }
153
- </script>
154
- ```
155
-
156
- These are the available configuration options, with their default values:
157
-
158
- ```javascript
159
- {
160
- clientId: null,
161
- organization: null,
162
- api: {
163
- namespace: 'sesamy',
164
- endpoint: 'https://api2.sesamy.com'
165
- },
166
- analytics: {
167
- enabled: true,
168
- endpoint: 'https://logs.sesamy.com/events'
169
- },
170
- auth: {
171
- clientId: null,
172
- organization: null,
173
- enabled: true,
174
- domain: null, // Optional: custom Auth0 domain (e.g., 'auth.example.com')
175
- domains: [] // Optional: array of auth domain strings for multi-domain setups
176
- },
177
- content: [
178
- {
179
- type: 'article',
180
- path: '/articles', // Optional: matches URLs containing this path
181
- pass: 'premium', // Optional: pass requirement
182
- price: { // Optional: price information
183
- amount: 9.99,
184
- currency: 'USD'
185
- },
186
- paywallUrl: 'https://example.com/paywall', // Optional: custom paywall URL
187
- enablePaywallSettingsUrlFallback: false, // Optional: enable fallback to sesamy-paywall settings-url
188
- selectors: {
189
- article: { selector: 'article' },
190
- image: { selector: 'img', attribute: 'src' },
191
- title: { selector: 'h1', attribute: 'textContent' },
192
- excerpt: { selector: 'p', attribute: 'textContent' },
193
- price: { selector: 'article', attribute: 'data-price' },
194
- currency: { selector: 'article', attribute: 'data-currency' },
195
- url: { selector: 'link', attribute: 'href' },
196
- id: { selector: 'article', attribute: 'data-id' },
197
- pass: { selector: 'article', attribute: 'data-pass' },
198
- }
199
- }
200
- ],
201
- tranforms: {
202
- enabled: false,
203
- rules: []
204
- }
205
- }
206
- ```
207
-
208
- # Authentication Configuration
209
-
210
- The `auth` configuration object controls how the Sesamy library handles user authentication. Below are the available options:
211
-
212
- ## Basic Configuration
213
-
214
- ```javascript
215
- {
216
- auth: {
217
- clientId: 'your-client-id', // Required: Your Auth0 client ID
218
- organization: 'org_123', // Optional: Auth0 organization ID
219
- enabled: true // Optional: Enable/disable authentication (default: true)
220
- }
221
- }
222
- ```
223
-
224
- ## Custom Domain Configuration
225
-
226
- ### Single Domain
227
-
228
- For custom Auth0 domains, use the `domain` property:
229
-
230
- ```javascript
231
- {
232
- auth: {
233
- clientId: 'your-client-id',
234
- domain: 'auth.example.com' // Optional: Custom Auth0 domain
235
- }
236
- }
237
- ```
238
-
239
- ### Multiple Domains (Multi-tenant/White-label)
240
-
241
- For applications that operate across multiple domains (e.g., white-label solutions, multi-region setups), use the `domains` array. The library will automatically select the appropriate auth domain based on the current page's top-level domain:
242
-
243
- ```javascript
244
- {
245
- auth: {
246
- clientId: 'your-client-id',
247
- domains: [
248
- 'auth.brand1.com',
249
- 'auth.brand2.com',
250
- 'auth.example.co.uk'
251
- ],
252
- domain: 'default-auth.example.com' // Optional: fallback if no match found
253
- }
254
- }
255
- ```
256
-
257
- **How it works:**
258
-
259
- 1. The library extracts the top-level domain from the current page URL
260
- 2. Each domain in the `domains` array is checked - the library derives its top-level domain (e.g., `auth.brand1.com` → `brand1.com`)
261
- 3. If the current page's top-level domain matches a domain in the array, that auth domain is used
262
- 4. If no match is found, it falls back to the `domain` property
263
- 5. If neither is available, it uses the default Sesamy auth domain
264
-
265
- **Example scenarios:**
266
-
267
- ```javascript
268
- // Multi-region setup
269
- {
270
- auth: {
271
- clientId: 'your-client-id',
272
- domains: [
273
- 'auth.us.example.com', // Used when on any *.example.com page
274
- 'auth.uk.example.co.uk', // Used when on any *.example.co.uk page
275
- 'auth.au.example.com.au' // Used when on any *.example.com.au page
276
- ]
277
- }
278
- }
279
-
280
- // White-label setup
281
- {
282
- auth: {
283
- clientId: 'your-client-id',
284
- domains: [
285
- 'auth.client1.com',
286
- 'auth.client2.com',
287
- 'auth.client3.com'
288
- ],
289
- domain: 'auth.myplatform.com' // Default for unlisted domains
290
- }
291
- }
292
- ```
293
-
294
- ## Complete Example
295
-
296
- ```javascript
297
- {
298
- clientId: "demo",
299
- organization: "org_abc123",
300
- auth: {
301
- clientId: "demo",
302
- organization: "org_abc123",
303
- enabled: true,
304
- domains: [
305
- 'auth.example.com',
306
- 'auth.example.co.uk'
307
- ],
308
- domain: 'auth.fallback.com'
309
- }
310
- }
311
- ```
312
-
313
- ## Cross-Domain Authentication
314
-
315
- When your application domain differs from your Auth0 domain's top-level domain, the library automatically uses popup-based login on mobile browsers (Safari/iOS and Android) to handle cross-domain authentication properly. Desktop browsers will use redirect-based login even when cross-domain.
316
-
317
- **Examples:**
318
-
319
- - Your app: `app.example.com`, Auth domain: `auth.different.com`
320
- - Mobile (Safari/iOS/Android): Popup login
321
- - Desktop: Redirect login
322
- - Your app: `app.example.com`, Auth domain: `auth.example.com` (same TLD)
323
- - All browsers: Redirect login
324
-
325
671
  # Custom HTML Attributes
326
672
 
327
673
  ## Visibility
@@ -702,6 +1048,26 @@ getTokenSilently()
702
1048
  - If the publisher uses a custom domain for authentication (so the auth domain matches the publisher's top-level domain), the session token will be stored in an `httpOnly` cookie. In this case, malicious scripts can only access short-lived access tokens, not the session token itself.
703
1049
  - Always ensure you trust third-party scripts running on your site, as they may be able to access tokens stored in local storage.
704
1050
 
1051
+ ## `auth.refresh()`
1052
+
1053
+ Forces sesamy-js to re-check the authentication state from the configured auth
1054
+ plugin (Auth0 SPA or BFF cookie). Useful after an external login or logout has
1055
+ changed cookies/storage outside the SDK's awareness, or after a window
1056
+ re-focus where you suspect the session may have been invalidated server-side.
1057
+
1058
+ ### Returns
1059
+
1060
+ `Promise<void>` — resolves once the auth state has been re-evaluated and any
1061
+ state-change events (`sesamyJsAuthenticated`, `sesamyJsLogout`) have fired.
1062
+
1063
+ ### Example
1064
+
1065
+ ```javascript
1066
+ window.addEventListener('focus', async () => {
1067
+ await window.sesamy.auth.refresh();
1068
+ });
1069
+ ```
1070
+
705
1071
  # Entitlements API
706
1072
 
707
1073
  ## `entitlements.get(entitlementId: string)`
@@ -1933,6 +2299,68 @@ async function unlockSpecificContent() {
1933
2299
  }
1934
2300
  ```
1935
2301
 
2302
+ #### `content.showModal(options: ModalOptions)`
2303
+
2304
+ Opens the Sesamy paywall modal. Requires `sesamy-components` to be loaded
2305
+ (included by the bootstrap loader by default; otherwise load
2306
+ `@sesamy/open-web-components` yourself).
2307
+
2308
+ ### Parameters
2309
+
2310
+ - `options` (`ModalOptions`):
2311
+ - `articleElementOrSelector` (`Element | string`, optional): Article whose
2312
+ metadata seeds the modal. Defaults to the first matched article.
2313
+ - `paywallId` (`string`, optional): Paywall configuration to render. Falls
2314
+ back to the article's `paywallUrl` selector.
2315
+ - `onClose` (`() => void`, optional): Called when the user dismisses the
2316
+ modal.
2317
+
2318
+ ### Returns
2319
+
2320
+ - `Promise<ModalResult>`: Resolves with `{ action: 'purchased' | 'closed' | 'login' }`
2321
+ when the modal is closed.
2322
+
2323
+ ### Example
2324
+
2325
+ ```javascript
2326
+ const result = await window.sesamy.content.showModal({
2327
+ articleElementOrSelector: 'article#main',
2328
+ });
2329
+
2330
+ if (result.action === 'purchased') {
2331
+ await window.sesamy.content.unlock('article#main');
2332
+ }
2333
+ ```
2334
+
2335
+ #### `content.showNotificationBar(options: NotificationBarOptions)`
2336
+
2337
+ Shows an inline notification bar pinned to the top or bottom of the viewport.
2338
+ Useful for unobtrusive prompts (e.g. "Subscribe for unlimited access") that
2339
+ don't block reading.
2340
+
2341
+ ### Parameters
2342
+
2343
+ - `options` (`NotificationBarOptions`):
2344
+ - `position` (`'top' | 'bottom'`): Where to attach the bar.
2345
+ - `articleElementOrSelector` (`Element | string`, optional): Source of
2346
+ metadata.
2347
+ - `paywallId` (`string`, optional): Paywall configuration.
2348
+ - `onClose` (`() => void`, optional): Called on dismissal.
2349
+
2350
+ ### Returns
2351
+
2352
+ - `Promise<NotificationBarResult>`: Resolves with the user action when the
2353
+ bar is dismissed.
2354
+
2355
+ ### Example
2356
+
2357
+ ```javascript
2358
+ window.sesamy.content.showNotificationBar({
2359
+ position: 'bottom',
2360
+ articleElementOrSelector: 'article#main',
2361
+ });
2362
+ ```
2363
+
1936
2364
  ### Flags API
1937
2365
 
1938
2366
  The Flags API allows for client-side feature flag management. These flags are stored in the browser's localStorage and can be used to enable or disable features for specific users.
@@ -2738,6 +3166,145 @@ window.sesamy.transactions
2738
3166
  });
2739
3167
  ```
2740
3168
 
3169
+ # Capsule (DCA) API
3170
+
3171
+ Available when `capsule.enabled` is `true` in the config and the capsule
3172
+ plugin is registered (auto-loaded by the bootstrap, or registered via
3173
+ `window.capsulePlugin` / `init(config, { capsulePlugin })`).
3174
+
3175
+ ## `capsule.processPage()`
3176
+
3177
+ Detects every DCA manifest currently in the DOM, calls the unlock endpoint
3178
+ for each, decrypts the payload, and injects the plaintext into the matching
3179
+ `data-dca-content-name` placeholder. Called automatically on page load when
3180
+ `capsule.autoProcess` is `true` (default) and again whenever the SPA mutates
3181
+ new DCA blocks into the DOM.
3182
+
3183
+ ### Returns
3184
+
3185
+ `Promise<void>` — resolves once every detected manifest has been processed
3186
+ (or has failed gracefully).
3187
+
3188
+ ### Example
3189
+
3190
+ ```javascript
3191
+ // Manually retry decryption after a login completes
3192
+ window.addEventListener('sesamyJsAuthenticated', async () => {
3193
+ await window.sesamy.capsule.processPage();
3194
+ });
3195
+ ```
3196
+
3197
+ ## `capsule.getClient()`
3198
+
3199
+ Returns the underlying [`@sesamy/capsule`](https://www.npmjs.com/package/@sesamy/capsule)
3200
+ DCA client. Use this if you need lower-level access (custom resource fetchers,
3201
+ manual decryption, advanced introspection). Returns `undefined` when capsule
3202
+ is disabled or the plugin failed to load.
3203
+
3204
+ ### Example
3205
+
3206
+ ```javascript
3207
+ const dca = window.sesamy.capsule.getClient();
3208
+ const decoded = dca?.decodeManifest(rawManifest);
3209
+ ```
3210
+
3211
+ # Consent API
3212
+
3213
+ Available always; gates analytics storage when `consent.enabled` is `true` in
3214
+ the config. Integrate with your CMP via the `consent.cmp` config or the
3215
+ `onConsentChange` callback, or drive consent imperatively from these
3216
+ functions.
3217
+
3218
+ ## `consent.get()`
3219
+
3220
+ Returns the current consent state — an object with `statistics` and
3221
+ `marketing` booleans.
3222
+
3223
+ ### Example
3224
+
3225
+ ```javascript
3226
+ const state = window.sesamy.consent.get();
3227
+ if (state.statistics) {
3228
+ // analytics storage is allowed
3229
+ }
3230
+ ```
3231
+
3232
+ ## `consent.set(state: Partial<ConsentState>)`
3233
+
3234
+ Updates one or more consent flags and re-evaluates analytics storage
3235
+ immediately.
3236
+
3237
+ ### Parameters
3238
+
3239
+ - `state` (`Partial<ConsentState>`): Partial consent object — only the keys
3240
+ you want to change need to be provided.
3241
+
3242
+ ### Example
3243
+
3244
+ ```javascript
3245
+ // User accepted statistics but not marketing
3246
+ window.sesamy.consent.set({ statistics: true, marketing: false });
3247
+ ```
3248
+
3249
+ ## `consent.has(type: 'statistics' | 'marketing')`
3250
+
3251
+ Returns `true` when the named consent flag is granted.
3252
+
3253
+ ### Example
3254
+
3255
+ ```javascript
3256
+ if (window.sesamy.consent.has('marketing')) {
3257
+ loadMarketingPixel();
3258
+ }
3259
+ ```
3260
+
3261
+ # Diagnostics API
3262
+
3263
+ Tools to capture a snapshot of the user's runtime state for support tickets.
3264
+ Both methods read profile, entitlements, contracts, tags, and tallies via the
3265
+ already-initialised API.
3266
+
3267
+ ## `diagnostics.collect(fallbackId?: string)`
3268
+
3269
+ Returns the diagnostics payload as an object without uploading it — useful if
3270
+ you want to render it in your own support form.
3271
+
3272
+ ### Parameters
3273
+
3274
+ - `fallbackId` (`string`, optional): Identifier used in the payload when no
3275
+ authenticated user id is available.
3276
+
3277
+ ### Returns
3278
+
3279
+ `Promise<DiagnosticsPayload>` — profile, entitlements, contracts, tags,
3280
+ tallies, library version, and environment metadata.
3281
+
3282
+ ## `diagnostics.send(fallbackId?: string)`
3283
+
3284
+ Collects diagnostics and uploads them to the Sesamy diagnostics endpoint.
3285
+ Returns the diagnostics id you can paste into a support ticket.
3286
+
3287
+ ### Parameters
3288
+
3289
+ - `fallbackId` (`string`, optional): Same as `collect`.
3290
+
3291
+ ### Returns
3292
+
3293
+ `Promise<{ id: string }>`.
3294
+
3295
+ ### Example
3296
+
3297
+ ```javascript
3298
+ async function reportProblem() {
3299
+ const { id } = await window.sesamy.diagnostics.send();
3300
+ alert(`Diagnostics submitted — reference: ${id}`);
3301
+ }
3302
+ ```
3303
+
3304
+ You can also trigger this without writing any code by appending
3305
+ `?sesamy-user-diagnostics=true` to the URL — sesamy-js will collect and
3306
+ submit automatically once initialisation completes.
3307
+
2741
3308
  # Polyfills
2742
3309
 
2743
3310
  The library polyfills the following methods for compatibility with older browsers:
@@ -2887,3 +3454,23 @@ const consumeUrl = client.links.generateConsumeLink(
2887
3454
 
2888
3455
  - **sesamy-js `generateLink`**: Includes authentication token in hash if user is authenticated, automatically includes tracking cookies and attribution data, supports link shortening via TTL
2889
3456
  - **SDK `links.*`**: Pure URL generation without authentication, useful for generating shareable links, links for unauthenticated users, or backend link generation
3457
+
3458
+ # Debugging
3459
+
3460
+ sesamy-js has several debug surfaces that are off by default and zero-cost
3461
+ when off:
3462
+
3463
+ - **Verbose runtime logs.** Append `?sesamy-debug=true` to the URL or set
3464
+ `localStorage.debug = true`. Calls to `sesamy.log(...)` and the SDK's
3465
+ internal logger flush to the console.
3466
+ - **Bootstrap logs.** Pass `debug: true` to `sesamyBootstrap(...)` (or the
3467
+ bootstrap config element). Emits `[sesamy-boot]` lines for chain entry
3468
+ selection, userinfo prefetch source, script load/error events, and
3469
+ fallback trigger reasons. Useful for diagnosing chain regressions without
3470
+ a HAR capture.
3471
+ - **User diagnostics.** Append `?sesamy-user-diagnostics=true` to collect a
3472
+ snapshot of profile, entitlements, contracts, tags, and tallies and upload
3473
+ it to the Sesamy diagnostics endpoint. The payload id is logged to the
3474
+ console for support tickets — see [Diagnostics API](#diagnostics-api).
3475
+ - **Library version.** `sesamy.getVersion()` returns the running version, in
3476
+ case multiple bundles end up on the page.