@monterosa/sdk-identify-kit 2.0.0-rc.2 → 2.0.0-rc.4

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/dist/index.cjs ADDED
@@ -0,0 +1,1224 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var sdkUtil = require('@monterosa/sdk-util');
6
+ var sdkLauncherKit = require('@monterosa/sdk-launcher-kit');
7
+ var sdkCore = require('@monterosa/sdk-core');
8
+ var sdkConnectKit = require('@monterosa/sdk-connect-kit');
9
+ var sdkInteractInterop = require('@monterosa/sdk-interact-interop');
10
+
11
+ /**
12
+ * @license
13
+ * @monterosa/sdk-identify-kit
14
+ *
15
+ * Copyright © 2023-2026 Monterosa Productions Limited. All rights reserved.
16
+ *
17
+ * More details on the license can be found at https://www.monterosa.co/sdk/license
18
+ */
19
+ /**
20
+ * @internal
21
+ */
22
+ var IdentifyEvent;
23
+ (function (IdentifyEvent) {
24
+ IdentifyEvent["StateUpdated"] = "state_updated";
25
+ IdentifyEvent["LoginRequested"] = "login_requested";
26
+ IdentifyEvent["LogoutRequested"] = "logout_requested";
27
+ IdentifyEvent["CredentialsUpdated"] = "credentials_updated";
28
+ IdentifyEvent["LoginPolicyUpdated"] = "login_policy_updated";
29
+ })(IdentifyEvent || (IdentifyEvent = {}));
30
+ /**
31
+ * Defines a set of error codes that may be encountered when using the
32
+ * Identify kit of the Monterosa SDK.
33
+ *
34
+ * @example
35
+ * ```javascript
36
+ * try {
37
+ * // some code that uses the IdentifyKit
38
+ * } catch (err) {
39
+ * if (err.code === IdentifyError.NoCredentials) {
40
+ * // handle missing credentials error
41
+ * } else {
42
+ * // handle other error types
43
+ * }
44
+ * }
45
+ * ```
46
+ *
47
+ * @remarks
48
+ * - The `IdentifyError` enum provides a convenient way to handle errors
49
+ * encountered when using the `IdentifyKit` module. By checking the code
50
+ * property of the caught error against the values of the enum, the error
51
+ * type can be determined and appropriate action taken.
52
+ *
53
+ * - The `IdentifyError` enum is not intended to be instantiated or extended.
54
+ */
55
+ exports.IdentifyError = void 0;
56
+ (function (IdentifyError) {
57
+ /**
58
+ * Indicates an error occurred during the call to the extension API.
59
+ */
60
+ IdentifyError["ExtensionApiError"] = "extension_api_error";
61
+ /**
62
+ * Indicates the extension required by the IdentifyKit is not set up properly.
63
+ */
64
+ IdentifyError["ExtensionNotSetup"] = "extension_not_setup";
65
+ /**
66
+ * Indicates the user's authentication credentials are not available
67
+ * or have expired.
68
+ */
69
+ IdentifyError["NoCredentials"] = "no_credentials";
70
+ /**
71
+ * Indicates an error occurred in the parent app.
72
+ */
73
+ IdentifyError["ParentAppError"] = "parent_app_error";
74
+ /**
75
+ * Indicates an unexpected or invalid login state was encountered.
76
+ */
77
+ IdentifyError["UnexpectedState"] = "unexpected_state";
78
+ /**
79
+ * Indicates there is no parent application to delegate to.
80
+ */
81
+ IdentifyError["NoParentApplication"] = "no_parent_application";
82
+ /**
83
+ * Indicates a timeout occurred when communicating with the parent app.
84
+ */
85
+ IdentifyError["ParentTimeoutError"] = "parent_timeout_error";
86
+ })(exports.IdentifyError || (exports.IdentifyError = {}));
87
+ /**
88
+ * @internal
89
+ */
90
+ const IdentifyErrorMessages = {
91
+ [exports.IdentifyError.ExtensionApiError]: (error) => `Identify extension API returned an error: ${error}`,
92
+ [exports.IdentifyError.ExtensionNotSetup]: () => 'Identify extension is not set up for this project',
93
+ [exports.IdentifyError.NoCredentials]: () => 'Identify credentials are not set',
94
+ [exports.IdentifyError.ParentAppError]: (error) => `Parent application error: ${error}`,
95
+ [exports.IdentifyError.UnexpectedState]: (state) => `Unexpected LoginState: ${state}`,
96
+ [exports.IdentifyError.NoParentApplication]: () => 'No parent application available for delegation',
97
+ [exports.IdentifyError.ParentTimeoutError]: (error) => `Parent application timeout: ${error}. Please check if the identify-kit is imported on the parent page.`,
98
+ };
99
+ var IdentifyAction;
100
+ (function (IdentifyAction) {
101
+ IdentifyAction["Login"] = "identifyLogin";
102
+ IdentifyAction["Logout"] = "identifyLogout";
103
+ IdentifyAction["RequestLogin"] = "identifyRequestLogin";
104
+ IdentifyAction["RequestLogout"] = "identifyRequestLogout";
105
+ IdentifyAction["GetCredentials"] = "identifyGetCredentials";
106
+ IdentifyAction["SetCredentials"] = "identifySetCredentials";
107
+ IdentifyAction["ClearCredentials"] = "identifyClearCredentials";
108
+ })(IdentifyAction || (IdentifyAction = {}));
109
+
110
+ /**
111
+ * @license
112
+ * @monterosa/sdk-identify-kit
113
+ *
114
+ * Copyright © 2023 Monterosa Productions Limited. All rights reserved.
115
+ *
116
+ * More details on the license can be found at https://www.monterosa.co/sdk/license
117
+ */
118
+ const EXTENSION_ID = 'lvis-id-custom-tab';
119
+ const SIGNATURE_TTL = 10000;
120
+
121
+ /**
122
+ * @license
123
+ * @monterosa/sdk-identify-kit
124
+ *
125
+ * Copyright © 2023 Monterosa Productions Limited. All rights reserved.
126
+ *
127
+ * More details on the license can be found at https://www.monterosa.co/sdk/license
128
+ */
129
+ class Identify extends sdkUtil.Emitter {
130
+ constructor(sdk, options = {}) {
131
+ super();
132
+ this.sdk = sdk;
133
+ this.wasLoggedIn = false;
134
+ this._signature = null;
135
+ this._userData = null;
136
+ this._state = {
137
+ state: 'logged_out',
138
+ };
139
+ this._options = Object.assign({ strategy: 'email', version: 1 }, options);
140
+ }
141
+ static async fetchIdentifyHost(host, projectId) {
142
+ var _a, _b;
143
+ const listings = await sdkInteractInterop.fetchListings(host, projectId);
144
+ const extensionAssets = (_b = (_a = listings.assets) === null || _a === void 0 ? void 0 : _a[EXTENSION_ID]) !== null && _b !== void 0 ? _b : [];
145
+ const endpointAsset = extensionAssets.find(({ name }) => name === 'endpoint');
146
+ if (!endpointAsset) {
147
+ throw sdkUtil.createError(exports.IdentifyError.ExtensionNotSetup, IdentifyErrorMessages);
148
+ }
149
+ return endpointAsset.data;
150
+ }
151
+ get state() {
152
+ return this._state;
153
+ }
154
+ set state(state) {
155
+ if (this._state.state === state.state) {
156
+ return;
157
+ }
158
+ switch (state.state) {
159
+ case 'logged_in':
160
+ this.wasLoggedIn = true;
161
+ break;
162
+ case 'error':
163
+ this.wasLoggedIn = false;
164
+ break;
165
+ case 'logged_out':
166
+ case 'pending':
167
+ // preserve wasLoggedIn — these states reflect transient transport
168
+ // state, not a deliberate logout (credentials are the gate for that)
169
+ break;
170
+ default: {
171
+ // Exhaustiveness check: TypeScript errors if a new state is added
172
+ // but not handled. This is a compile-time error.
173
+ //
174
+ // See: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking
175
+ const exhaustiveCheck = state.state;
176
+ throw sdkUtil.createError(exports.IdentifyError.UnexpectedState, IdentifyErrorMessages, String(exhaustiveCheck));
177
+ }
178
+ }
179
+ this._state = state;
180
+ this.emit(IdentifyEvent.StateUpdated, state);
181
+ }
182
+ get options() {
183
+ return this._options;
184
+ }
185
+ set signature(signature) {
186
+ if (this._signature === signature) {
187
+ return;
188
+ }
189
+ this._signature = signature;
190
+ clearTimeout(this.signatureExpireTimeout);
191
+ if (signature !== null) {
192
+ this.signatureExpireTimeout = setTimeout(() => {
193
+ this.signature = null;
194
+ }, SIGNATURE_TTL);
195
+ }
196
+ }
197
+ get signature() {
198
+ return this._signature;
199
+ }
200
+ set userData(data) {
201
+ if (this._userData === data) {
202
+ return;
203
+ }
204
+ this._userData = data;
205
+ }
206
+ get userData() {
207
+ return this._userData;
208
+ }
209
+ async getUrl(path = '') {
210
+ const { host, projectId } = this.sdk.options;
211
+ if (this.host === undefined) {
212
+ this.host = await Identify.fetchIdentifyHost(host, projectId);
213
+ }
214
+ const url = new URL(this.host);
215
+ const { version, strategy, provider } = this._options;
216
+ url.pathname = `/v${version}${path}`;
217
+ url.searchParams.set('projectId', projectId);
218
+ url.searchParams.set('deviceId', sdkCore.getDeviceId());
219
+ url.searchParams.set('strategy', strategy);
220
+ if (provider !== undefined) {
221
+ url.searchParams.set('provider', provider);
222
+ }
223
+ return url.toString();
224
+ }
225
+ }
226
+
227
+ /**
228
+ * @license
229
+ * @monterosa/sdk-identify-kit
230
+ *
231
+ * Copyright © 2025-2026 Monterosa Productions Limited. All rights reserved.
232
+ *
233
+ * More details on the license can be found at https://www.monterosa.co/sdk/license
234
+ */
235
+ const entries = new Map();
236
+ function getExperiences() {
237
+ return Array.from(entries.values()).map((entry) => entry.experience);
238
+ }
239
+ function addExperience(experience, unsubs = []) {
240
+ entries.set(experience.id, {
241
+ experience,
242
+ unsubs: new Set(unsubs),
243
+ });
244
+ }
245
+ function deleteExperience(experience) {
246
+ const entry = entries.get(experience.id);
247
+ if (entry) {
248
+ entry.unsubs.forEach((unsub) => unsub());
249
+ entries.delete(experience.id);
250
+ }
251
+ }
252
+
253
+ /**
254
+ * @license
255
+ * @monterosa/sdk-identify-kit
256
+ *
257
+ * Copyright © 2026 Monterosa Productions Limited. All rights reserved.
258
+ *
259
+ * More details on the license can be found at https://www.monterosa.co/sdk/license
260
+ */
261
+ /**
262
+ * Locally-owned login policy.
263
+ *
264
+ * Defaults to `'disabled'`. Set via the public `setLoginPolicy`. Never
265
+ * propagated across the wire — each level owns its own policy.
266
+ *
267
+ * @internal
268
+ */
269
+ class LoginPolicyStore extends sdkUtil.Emitter {
270
+ constructor() {
271
+ super();
272
+ this._policy = 'disabled';
273
+ }
274
+ static getInstance() {
275
+ if (!LoginPolicyStore.instance) {
276
+ LoginPolicyStore.instance = new LoginPolicyStore();
277
+ }
278
+ return LoginPolicyStore.instance;
279
+ }
280
+ get policy() {
281
+ return this._policy;
282
+ }
283
+ set policy(next) {
284
+ if (this._policy === next) {
285
+ return;
286
+ }
287
+ this._policy = next;
288
+ this.emit(IdentifyEvent.LoginPolicyUpdated, next);
289
+ }
290
+ subscribe(handler) {
291
+ return sdkUtil.subscribe(this, IdentifyEvent.LoginPolicyUpdated, handler);
292
+ }
293
+ reset() {
294
+ this._policy = 'disabled';
295
+ this.off(IdentifyEvent.LoginPolicyUpdated);
296
+ }
297
+ }
298
+
299
+ /**
300
+ * @license
301
+ * @monterosa/sdk-identify-kit
302
+ *
303
+ * Copyright © 2026 Monterosa Productions Limited. All rights reserved.
304
+ *
305
+ * More details on the license can be found at https://www.monterosa.co/sdk/license
306
+ */
307
+ /**
308
+ * Root-owned credentials slot.
309
+ *
310
+ * Single source of truth at the root of an integration; an internal
311
+ * mirror at every other level, kept in sync via `Login` / `Logout`
312
+ * broadcasts. Not exposed publicly — public access flows through
313
+ * `setCredentials` / `getCredentials` / `clearCredentials` and
314
+ * `onCredentialsUpdated`.
315
+ *
316
+ * @internal
317
+ */
318
+ class CredentialsStore extends sdkUtil.Emitter {
319
+ constructor() {
320
+ super();
321
+ this._credentials = null;
322
+ }
323
+ static getInstance() {
324
+ if (!CredentialsStore.instance) {
325
+ CredentialsStore.instance = new CredentialsStore();
326
+ }
327
+ return CredentialsStore.instance;
328
+ }
329
+ get credentials() {
330
+ return this._credentials;
331
+ }
332
+ set credentials(next) {
333
+ if (this._credentials === next) {
334
+ return;
335
+ }
336
+ this._credentials = next;
337
+ this.emit(IdentifyEvent.CredentialsUpdated, next);
338
+ }
339
+ subscribe(handler) {
340
+ return sdkUtil.subscribe(this, IdentifyEvent.CredentialsUpdated, handler);
341
+ }
342
+ reset() {
343
+ this._credentials = null;
344
+ this.off(IdentifyEvent.CredentialsUpdated);
345
+ }
346
+ }
347
+
348
+ /**
349
+ * @license
350
+ * identify-kit
351
+ *
352
+ * Copyright © 2023-2026 Monterosa Productions Limited. All rights reserved.
353
+ *
354
+ * More details on the license can be found at https://www.monterosa.co/sdk/license
355
+ */
356
+ const loginPolicyStore = LoginPolicyStore.getInstance();
357
+ const credentialsStore = CredentialsStore.getInstance();
358
+ /**
359
+ * @internal
360
+ */
361
+ const identifyKits = new Map();
362
+ /**
363
+ * Emitter for identify request events that are not tied to a specific
364
+ * identify instance (login / logout requests).
365
+ *
366
+ * @internal
367
+ */
368
+ const requestsEmitter = new sdkUtil.Emitter();
369
+ /**
370
+ * Read the current locally-owned login policy.
371
+ *
372
+ * Returns `'disabled'` by default.
373
+ *
374
+ * @internal
375
+ */
376
+ function getLoginPolicy() {
377
+ return loginPolicyStore.policy;
378
+ }
379
+ /**
380
+ * Set the locally-owned login policy.
381
+ *
382
+ * All `Identify` instances at this level honour the same policy; the
383
+ * value is never propagated across the wire (each level of a multi-level
384
+ * integration owns its own policy).
385
+ *
386
+ * Policy gates the Enmasse `loginTask` only — credentials always mirror
387
+ * from upstream regardless of policy. A `'disabled'` level still
388
+ * receives credentials via the module-load pull and the cascading
389
+ * `Login` / `Logout` broadcasts; it just doesn't attempt Enmasse
390
+ * validation against them.
391
+ *
392
+ * When the policy transitions to `'auto'`, enqueues a sync-state task
393
+ * for every existing identify so that any in-flight session is picked
394
+ * up.
395
+ *
396
+ * Idempotent: setting the same policy that's already in effect is a
397
+ * no-op.
398
+ *
399
+ * @param policy - `'auto'` to enable auto-login, `'disabled'` to opt out.
400
+ */
401
+ function setLoginPolicy(policy) {
402
+ if (loginPolicyStore.policy === policy) {
403
+ return;
404
+ }
405
+ loginPolicyStore.policy = policy;
406
+ if (policy !== 'auto') {
407
+ return;
408
+ }
409
+ // On transition to 'auto': if the local credentials slot already
410
+ // has a value (typical after the non-root module-load pull, or after
411
+ // a setCredentials at the root, or after an upstream Login broadcast
412
+ // has cascaded down), enqueue a login task for each existing
413
+ // identify. If the slot is empty, do nothing — a future credentials
414
+ // arrival via `applyLogin` will trigger the login under this new
415
+ // policy.
416
+ const { credentials } = credentialsStore;
417
+ if (credentials === null) {
418
+ return;
419
+ }
420
+ for (const identify of identifyKits.values()) {
421
+ enqueueLogin(identify, credentials);
422
+ }
423
+ }
424
+ /**
425
+ * Whether the user should be auto-logged in on reconnect.
426
+ *
427
+ * True when the user was previously logged in, the policy is `'auto'`,
428
+ * and credentials are available on the identify instance.
429
+ *
430
+ * @internal
431
+ */
432
+ function shouldLoginOnReconnect(identify) {
433
+ return (identify.wasLoggedIn &&
434
+ getLoginPolicy() === 'auto' &&
435
+ credentialsStore.credentials !== null);
436
+ }
437
+ /**
438
+ * @internal
439
+ */
440
+ const taskQueue = new sdkUtil.TaskQueue();
441
+ function isSdk(value) {
442
+ return value instanceof sdkCore.Sdk;
443
+ }
444
+ async function api(url, token, method = 'GET') {
445
+ const headers = {
446
+ accept: 'application/json',
447
+ };
448
+ let credentials;
449
+ // Only include Authorization header for bearer token authentication
450
+ // When token is 'cookie', use credentials: 'include' to ensure HttpOnly
451
+ // cookies are sent
452
+ if (token === 'cookie') {
453
+ // Use credentials: 'include' to ensure HttpOnly cookies are sent
454
+ credentials = 'include';
455
+ }
456
+ else {
457
+ headers.Authorization = `Bearer ${token}`;
458
+ }
459
+ const response = await fetch(url, {
460
+ method,
461
+ headers,
462
+ credentials,
463
+ });
464
+ const data = (await response.json());
465
+ if (data.result < 0) {
466
+ throw sdkUtil.createError(exports.IdentifyError.ExtensionApiError, IdentifyErrorMessages, data.message);
467
+ }
468
+ return data;
469
+ }
470
+ /**
471
+ * @internal
472
+ */
473
+ async function sendParentRequest(action, payload) {
474
+ const parentApp = sdkLauncherKit.getParentApplication();
475
+ if (parentApp === null) {
476
+ throw sdkUtil.createError(exports.IdentifyError.NoParentApplication, IdentifyErrorMessages);
477
+ }
478
+ try {
479
+ const response = await sdkLauncherKit.sendSdkRequest(parentApp, action, payload);
480
+ const { error, data } = response.payload;
481
+ if (error !== undefined) {
482
+ throw sdkUtil.createError(exports.IdentifyError.ParentAppError, IdentifyErrorMessages, error);
483
+ }
484
+ return data;
485
+ }
486
+ catch (err) {
487
+ if (err instanceof sdkUtil.MonterosaError &&
488
+ err.code === sdkLauncherKit.BridgeError.RequestTimeoutError) {
489
+ const errorMessage = sdkUtil.getErrorMessage(err);
490
+ throw sdkUtil.createError(exports.IdentifyError.ParentTimeoutError, IdentifyErrorMessages, errorMessage);
491
+ }
492
+ throw err;
493
+ }
494
+ }
495
+ /**
496
+ * @internal
497
+ */
498
+ async function login(identify, credentials) {
499
+ const signature = await getSessionSignatureMemoized(identify, credentials);
500
+ const conn = await sdkConnectKit.getConnect(identify.sdk.options.host);
501
+ await sdkConnectKit.connect(conn);
502
+ await sdkConnectKit.login(conn, ...signature);
503
+ }
504
+ const loginWithRetry = sdkUtil.withRetryAsync(login);
505
+ /**
506
+ * @internal
507
+ */
508
+ async function logout(identify) {
509
+ const conn = await sdkConnectKit.getConnect(identify.sdk.options.host);
510
+ await sdkConnectKit.connect(conn);
511
+ await sdkConnectKit.logout(conn);
512
+ }
513
+ const logoutWithRetry = sdkUtil.withRetryAsync(logout);
514
+ /**
515
+ * @internal
516
+ */
517
+ async function loginTask(identify, credentials) {
518
+ if (identify.state.state === 'logged_in') {
519
+ return;
520
+ }
521
+ try {
522
+ identify.state = {
523
+ state: 'pending',
524
+ };
525
+ await loginWithRetry(identify, credentials);
526
+ // Update state only if there are no pending tasks in the queue
527
+ if (taskQueue.isEmpty()) {
528
+ identify.state = {
529
+ state: 'logged_in',
530
+ };
531
+ }
532
+ }
533
+ catch (err) {
534
+ // Abandon any pending login/logout/sync tasks on failure — letting
535
+ // them run after an error would put the user in an inconsistent
536
+ // state. Note: this also drops tasks for unrelated identifies that
537
+ // share the queue; acceptable because the typical runtime has one
538
+ // identify per project.
539
+ taskQueue.clear();
540
+ identify.state = {
541
+ state: 'error',
542
+ error: err instanceof Error ? err.message : 'Unknown error',
543
+ errorType: 'login',
544
+ };
545
+ }
546
+ }
547
+ /**
548
+ * @internal
549
+ */
550
+ async function logoutTask(identify) {
551
+ if (identify.state.state === 'logged_out') {
552
+ return;
553
+ }
554
+ try {
555
+ identify.state = {
556
+ state: 'pending',
557
+ };
558
+ await logoutWithRetry(identify);
559
+ // Update state only if there are no pending tasks in the queue
560
+ if (taskQueue.isEmpty()) {
561
+ identify.state = {
562
+ state: 'logged_out',
563
+ };
564
+ }
565
+ }
566
+ catch (err) {
567
+ // Abandon any pending login/logout/sync tasks on failure — see
568
+ // `loginTask` for the rationale.
569
+ taskQueue.clear();
570
+ identify.state = {
571
+ state: 'error',
572
+ error: err instanceof Error ? err.message : 'Unknown error',
573
+ errorType: 'logout',
574
+ };
575
+ }
576
+ }
577
+ /**
578
+ * Enqueue a `loginTask` for the given identify when `policy === 'auto'`.
579
+ * The downstream `Login` broadcast is emitted by {@link applyLogin}.
580
+ *
581
+ * @internal
582
+ */
583
+ function enqueueLogin(identify, credentials, prioritise = false) {
584
+ if (getLoginPolicy() !== 'auto') {
585
+ return;
586
+ }
587
+ if (prioritise) {
588
+ taskQueue.enqueueFront(() => loginTask(identify, credentials));
589
+ }
590
+ else {
591
+ taskQueue.enqueue(() => loginTask(identify, credentials));
592
+ }
593
+ }
594
+ /**
595
+ * Reset the identify's identity-derived caches and enqueue a `logoutTask`
596
+ * when `policy === 'auto'`.
597
+ *
598
+ * @internal
599
+ */
600
+ function enqueueLogout(identify) {
601
+ identify.userData = null;
602
+ identify.signature = null;
603
+ if (getLoginPolicy() !== 'auto') {
604
+ return;
605
+ }
606
+ taskQueue.enqueue(() => logoutTask(identify));
607
+ }
608
+ /**
609
+ * Apply new credentials at this level: update the slot, enqueue a
610
+ * `loginTask` for every existing identify on the null-to-set transition,
611
+ * and broadcast `Login` downstream to every embedded experience.
612
+ *
613
+ * Idempotent on token refresh: re-setting an already-set slot updates
614
+ * the value and broadcasts, but does not re-enqueue logins (their
615
+ * `loginTask` would short-circuit on `state === 'logged_in'` anyway).
616
+ *
617
+ * @internal
618
+ */
619
+ function applyLogin(credentials) {
620
+ const shouldLogin = credentialsStore.credentials === null;
621
+ credentialsStore.credentials = credentials;
622
+ if (shouldLogin) {
623
+ for (const identify of identifyKits.values()) {
624
+ enqueueLogin(identify, credentials);
625
+ }
626
+ }
627
+ for (const experience of getExperiences()) {
628
+ sdkLauncherKit.sendSdkMessage(experience, IdentifyAction.Login, credentials);
629
+ }
630
+ }
631
+ /**
632
+ * Clear credentials at this level: null out the slot, enqueue a
633
+ * `logoutTask` for every existing identify on the set-to-null
634
+ * transition, and broadcast `Logout` downstream to every embedded
635
+ * experience.
636
+ *
637
+ * @internal
638
+ */
639
+ function applyLogout() {
640
+ const shouldLogout = credentialsStore.credentials !== null;
641
+ credentialsStore.credentials = null;
642
+ if (shouldLogout) {
643
+ for (const identify of identifyKits.values()) {
644
+ enqueueLogout(identify);
645
+ }
646
+ }
647
+ for (const experience of getExperiences()) {
648
+ sdkLauncherKit.sendSdkMessage(experience, IdentifyAction.Logout, {});
649
+ }
650
+ }
651
+ /**
652
+ * Pull state from the parent application and apply locally.
653
+ *
654
+ * Fires once at non-root bridge module load, regardless of
655
+ * `loginPolicy` — token presence is the customer-side sign-in state and
656
+ * must mirror upstream whether or not we choose to validate against
657
+ * Enmasse. Best-effort: a missing or non-identify-kit parent silently
658
+ * resolves with no slot change.
659
+ *
660
+ * Uses {@link applyLogin} so the slot update cascades downstream to
661
+ * this level's own embedded experiences and per-identify loginTasks
662
+ * are enqueued under `loginPolicy === 'auto'`.
663
+ *
664
+ * @internal
665
+ */
666
+ async function pullStateFromParent() {
667
+ let credentials;
668
+ try {
669
+ credentials = await getCredentials();
670
+ }
671
+ catch (_a) {
672
+ return;
673
+ }
674
+ if (credentials !== null) {
675
+ applyLogin(credentials);
676
+ }
677
+ }
678
+ function getIdentify(sdkOrOptions, options) {
679
+ let sdk;
680
+ let identifyOptions;
681
+ if (isSdk(sdkOrOptions)) {
682
+ /**
683
+ * Interface: getIdentify(sdk, options?)
684
+ */
685
+ sdk = sdkOrOptions;
686
+ if (options !== undefined) {
687
+ identifyOptions = options;
688
+ }
689
+ else {
690
+ identifyOptions = {};
691
+ }
692
+ }
693
+ else if (sdkOrOptions !== undefined) {
694
+ /**
695
+ * Interface: getIdentify(options)
696
+ */
697
+ sdk = sdkCore.getSdk();
698
+ identifyOptions = sdkOrOptions;
699
+ }
700
+ else {
701
+ /**
702
+ * Interface: getIdentify()
703
+ */
704
+ sdk = sdkCore.getSdk();
705
+ identifyOptions = {};
706
+ }
707
+ const { projectId } = sdk.options;
708
+ if (identifyKits.has(projectId)) {
709
+ return identifyKits.get(projectId);
710
+ }
711
+ const identify = new Identify(sdk, identifyOptions);
712
+ watchConnectionStatus(identify);
713
+ identifyKits.set(projectId, identify);
714
+ return identify;
715
+ }
716
+ /**
717
+ * Requests a user login.
718
+ *
719
+ * @example
720
+ * ```javascript
721
+ * import { configure } from '@monterosa/sdk-core';
722
+ * import { requestLogin } from '@monterosa/sdk-identify-kit';
723
+ *
724
+ * configure({ host: '...', projectId: '...' });
725
+ *
726
+ * try {
727
+ * await requestLogin();
728
+ *
729
+ * console.log('Login request successful');
730
+ * } catch (err) {
731
+ * console.error('Error requesting login:', err.message)
732
+ * }
733
+ * ```
734
+ *
735
+ * @remarks
736
+ * - If the app is running within a third-party application that uses
737
+ * the Monterosa SDK, the function delegates to the parent app
738
+ * to handle the login process.
739
+ *
740
+ * @returns A Promise that resolves with `void` when the login request
741
+ * is completed.
742
+ */
743
+ async function requestLogin() {
744
+ if (sdkLauncherKit.getParentApplication() !== null) {
745
+ await sendParentRequest(IdentifyAction.RequestLogin);
746
+ return;
747
+ }
748
+ requestsEmitter.emit(IdentifyEvent.LoginRequested);
749
+ }
750
+ /**
751
+ * Read the current credentials, pulling from the parent application when
752
+ * called from an embedded experience.
753
+ *
754
+ * At the root this returns the canonical slot value. At a non-root
755
+ * experience this dispatches a `GetCredentials` request upstream and
756
+ * resolves with the response, ensuring the caller always sees the
757
+ * authoritative value rather than a possibly-stale local mirror.
758
+ *
759
+ * @returns A Promise that resolves to the current credentials, or `null`
760
+ * if none are set.
761
+ */
762
+ async function getCredentials() {
763
+ if (sdkLauncherKit.getParentApplication() === null) {
764
+ return credentialsStore.credentials;
765
+ }
766
+ return sendParentRequest(IdentifyAction.GetCredentials);
767
+ }
768
+ /**
769
+ * Requests a user logout.
770
+ *
771
+ * @example
772
+ * ```javascript
773
+ * import { configure } from '@monterosa/sdk-core';
774
+ * import { requestLogout } from '@monterosa/sdk-identify-kit';
775
+ *
776
+ * configure({ host: '...', projectId: '...' });
777
+ *
778
+ * try {
779
+ * await requestLogout();
780
+ *
781
+ * console.log('Logout request successful');
782
+ * } catch (err) {
783
+ * console.error('Error requesting logout:', err.message)
784
+ * }
785
+ * ```
786
+ *
787
+ * @remarks
788
+ * - If the app is running within a third-party application that uses
789
+ * the Monterosa SDK, the function delegates to the parent app
790
+ * to handle the logout process.
791
+ *
792
+ * @returns A Promise that resolves with `void` when the logout request
793
+ * is completed.
794
+ */
795
+ async function requestLogout() {
796
+ if (sdkLauncherKit.getParentApplication() !== null) {
797
+ await sendParentRequest(IdentifyAction.RequestLogout);
798
+ return;
799
+ }
800
+ requestsEmitter.emit(IdentifyEvent.LogoutRequested);
801
+ }
802
+ /**
803
+ * @internal
804
+ *
805
+ * Returns a signature for a user session. The signature is based on the
806
+ * user's identifying information provided in the `IdentifyKit` instance.
807
+ *
808
+ * @remarks
809
+ * This function does not handle errors. Callers are responsible for catching
810
+ * and handling failures.
811
+ *
812
+ * @param identify - An instance of the `IdentifyKit` class used for user
813
+ * identification.
814
+ * @param credentials - The credentials of the user.
815
+ *
816
+ * @returns A Promise that resolves to a `Signature` object.
817
+ */
818
+ async function getSessionSignature(identify, credentials) {
819
+ if (identify.signature !== null) {
820
+ return identify.signature;
821
+ }
822
+ const url = await identify.getUrl('/user/check');
823
+ const { data } = await api(url, credentials.token);
824
+ const signature = [data.userId, data.timeStamp, data.signature];
825
+ identify.signature = signature;
826
+ return signature;
827
+ }
828
+ /**
829
+ * A memoized version of the `getSessionSignature` function. Memo key is
830
+ * the credentials token, so both leaf-side and bridge-side callers share
831
+ * the same cache for the same token.
832
+ */
833
+ const getSessionSignatureMemoized = sdkUtil.memoizePromise(getSessionSignature, (identify, credentials) => credentials.token, {
834
+ clearOnResolve: true,
835
+ clearOnReject: true,
836
+ });
837
+ /**
838
+ * Fetches user data for the given credentials.
839
+ *
840
+ * Credentials are required explicitly so the precondition is visible at
841
+ * the call site — without them the function cannot be called. Obtain a
842
+ * `Credentials` value via {@link getCredentials} or the
843
+ * {@link onCredentialsUpdated} callback.
844
+ *
845
+ * @example
846
+ * ```javascript
847
+ * import { configure } from '@monterosa/sdk-core';
848
+ * import {
849
+ * getIdentify,
850
+ * getUserData,
851
+ * getCredentials,
852
+ * } from '@monterosa/sdk-identify-kit';
853
+ *
854
+ * configure({ host: '...', projectId: '...' });
855
+ *
856
+ * const identify = getIdentify();
857
+ * const credentials = await getCredentials();
858
+ *
859
+ * if (credentials !== null) {
860
+ * const userData = await getUserData(identify, credentials);
861
+ * console.log('User data:', userData);
862
+ * }
863
+ * ```
864
+ *
865
+ * @remarks
866
+ * Cached on the identify instance after the first successful fetch.
867
+ * Cleared on {@link clearCredentials} via the logout path.
868
+ *
869
+ * @param identify - An instance of `IdentifyKit` that provides the user
870
+ * identification functionality.
871
+ * @param credentials - The user's authentication credentials. The
872
+ * request authorises against the value's token.
873
+ * @returns A Promise that resolves to a key-value object representing
874
+ * user data. The structure and content of the user data object depend
875
+ * on the identify service provider and may vary.
876
+ */
877
+ async function getUserData(identify, credentials) {
878
+ if (identify.userData !== null) {
879
+ return identify.userData;
880
+ }
881
+ const url = await identify.getUrl('/user');
882
+ const { data } = await api(url, credentials.token);
883
+ identify.userData = data;
884
+ return data;
885
+ }
886
+ /**
887
+ * Sets the user's authentication credentials.
888
+ *
889
+ * @example
890
+ * ```javascript
891
+ * import { configure } from '@monterosa/sdk-core';
892
+ * import { setCredentials } from '@monterosa/sdk-identify-kit';
893
+ *
894
+ * configure({ host: '...', projectId: '...' });
895
+ *
896
+ * await setCredentials({ token: 'abc123' });
897
+ * ```
898
+ *
899
+ * @remarks
900
+ * - At the root of an integration, the credentials are stored locally
901
+ * and a `Login` broadcast is sent to every embedded experience.
902
+ * - When called from an embedded experience, the call is relayed
903
+ * upstream via the `SetCredentials` wire action. The credentials are
904
+ * ultimately applied at the root and propagate back down via the
905
+ * cascading `Login` broadcast.
906
+ *
907
+ * @param credentials - An object representing the user's authentication
908
+ * credentials.
909
+ * @returns A Promise that resolves to `void` once the credentials have
910
+ * been written (root) or the upstream relay has completed (non-root).
911
+ */
912
+ async function setCredentials(credentials) {
913
+ if (sdkLauncherKit.getParentApplication() !== null) {
914
+ await sendParentRequest(IdentifyAction.SetCredentials, credentials);
915
+ return;
916
+ }
917
+ applyLogin(credentials);
918
+ }
919
+ /**
920
+ * Clears the user's authentication credentials.
921
+ *
922
+ * @example
923
+ * ```javascript
924
+ * import { configure } from '@monterosa/sdk-core';
925
+ * import { clearCredentials } from '@monterosa/sdk-identify-kit';
926
+ *
927
+ * configure({ host: '...', projectId: '...' });
928
+ *
929
+ * await clearCredentials();
930
+ * ```
931
+ *
932
+ * @remarks
933
+ * - At the root of an integration, the credentials slot is cleared and
934
+ * a `Logout` broadcast is sent to every embedded experience.
935
+ * - When called from an embedded experience, the call is relayed
936
+ * upstream via the `ClearCredentials` wire action. The slot is
937
+ * cleared at the root and the cascading `Logout` broadcast propagates
938
+ * back down.
939
+ *
940
+ * @returns A Promise that resolves to `void` once the slot has been
941
+ * cleared (root) or the upstream relay has completed (non-root).
942
+ */
943
+ async function clearCredentials() {
944
+ if (sdkLauncherKit.getParentApplication() !== null) {
945
+ await sendParentRequest(IdentifyAction.ClearCredentials);
946
+ return;
947
+ }
948
+ applyLogout();
949
+ }
950
+ /**
951
+ * Registers a callback function that will be called whenever the `LoginState`
952
+ * object associated with the `IdentifyKit` instance is updated.
953
+ *
954
+ * @example
955
+ * ```javascript
956
+ * import { configure } from '@monterosa/sdk-core';
957
+ * import { getIdentify, onStateUpdated } from '@monterosa/sdk-identify-kit';
958
+ *
959
+ * configure({ host: '...', projectId: '...' });
960
+ *
961
+ * const identify = getIdentify();
962
+ *
963
+ * const unsubscribe = onStateUpdated(identify, (state) => {
964
+ * console.log("State updated:", state);
965
+ * });
966
+ *
967
+ * // Call unsubscribe() to unregister the callback function
968
+ * // unsubscribe();
969
+ * ```
970
+ *
971
+ * @param identify - An instance of `IdentifyKit` that provides the user
972
+ * identification functionality.
973
+ * @param callback - The callback function to register. This function will be
974
+ * called with the updated `LoginState` object as its only argument.
975
+ * @returns An `Unsubscribe` function that can be called to unregister
976
+ * the callback function.
977
+ */
978
+ function onStateUpdated(identify, callback) {
979
+ return sdkUtil.subscribe(identify, IdentifyEvent.StateUpdated, callback);
980
+ }
981
+ /**
982
+ * Registers a callback function that will be called whenever the
983
+ * credentials slot is updated.
984
+ *
985
+ * @example
986
+ * ```javascript
987
+ * import { configure } from '@monterosa/sdk-core';
988
+ * import { onCredentialsUpdated } from '@monterosa/sdk-identify-kit';
989
+ *
990
+ * configure({ host: '...', projectId: '...' });
991
+ *
992
+ * const unsubscribe = onCredentialsUpdated((credentials) => {
993
+ * if (credentials !== null) {
994
+ * console.log("Credentials updated:", credentials);
995
+ * } else {
996
+ * console.log("Credentials cleared.");
997
+ * }
998
+ * });
999
+ *
1000
+ * // Call unsubscribe() to unregister the callback function
1001
+ * // unsubscribe();
1002
+ * ```
1003
+ *
1004
+ * @param callback - The callback function to register. This function will be
1005
+ * called with the updated `Credentials` object as its only argument.
1006
+ * If the value `null` is received, it indicates that the credentials have
1007
+ * been cleared.
1008
+ * @returns An `Unsubscribe` function that can be called to unregister
1009
+ * the callback function.
1010
+ */
1011
+ function onCredentialsUpdated(callback) {
1012
+ return credentialsStore.subscribe(callback);
1013
+ }
1014
+ /**
1015
+ * Registers a callback function that will be called when a login is requested
1016
+ * by an Experience.
1017
+ *
1018
+ * @example
1019
+ * ```javascript
1020
+ * import { configure } from '@monterosa/sdk-core';
1021
+ * import { onLoginRequestedByExperience } from '@monterosa/sdk-identify-kit';
1022
+ *
1023
+ * configure({ host: '...', projectId: '...' });
1024
+ *
1025
+ * const unsubscribe = onLoginRequestedByExperience(() => {
1026
+ * showLoginForm();
1027
+ * });
1028
+ *
1029
+ * // Call unsubscribe() to unregister the callback function
1030
+ * // unsubscribe();
1031
+ * ```
1032
+ *
1033
+ * @param callback - The callback function to register. This function will
1034
+ * be called when a login is requested by an Experience.
1035
+ * @returns An `Unsubscribe` function that can be called to unregister
1036
+ * the callback function.
1037
+ */
1038
+ function onLoginRequestedByExperience(callback) {
1039
+ return sdkUtil.subscribe(requestsEmitter, IdentifyEvent.LoginRequested, callback);
1040
+ }
1041
+ /**
1042
+ * Registers a callback function that will be called when a logout is requested
1043
+ * by an Experience.
1044
+ *
1045
+ * @example
1046
+ * ```javascript
1047
+ * import { configure } from '@monterosa/sdk-core';
1048
+ * import { onLogoutRequestedByExperience } from '@monterosa/sdk-identify-kit';
1049
+ *
1050
+ * configure({ host: '...', projectId: '...' });
1051
+ *
1052
+ * const unsubscribe = onLogoutRequestedByExperience(() => {
1053
+ * // logout requested by experience
1054
+ * // perform logout from Auth service
1055
+ * });
1056
+ *
1057
+ * // Call unsubscribe() to unregister the callback function
1058
+ * // unsubscribe();
1059
+ * ```
1060
+ *
1061
+ * @param callback - The callback function to register. This function will
1062
+ * be called when a logout is requested by an Experience.
1063
+ * @returns An `Unsubscribe` function that can be called to unregister
1064
+ * the callback function.
1065
+ */
1066
+ function onLogoutRequestedByExperience(callback) {
1067
+ return sdkUtil.subscribe(requestsEmitter, IdentifyEvent.LogoutRequested, callback);
1068
+ }
1069
+ /**
1070
+ *
1071
+ * @internal
1072
+ */
1073
+ async function watchConnectionStatus(identify) {
1074
+ const conn = await sdkConnectKit.getConnect(identify.sdk.options.host);
1075
+ sdkConnectKit.onConnected(conn, () => {
1076
+ if (shouldLoginOnReconnect(identify)) {
1077
+ enqueueLogin(identify, credentialsStore.credentials, true);
1078
+ }
1079
+ taskQueue.resume();
1080
+ });
1081
+ sdkConnectKit.onConnecting(conn, () => {
1082
+ taskQueue.pause();
1083
+ // If the user was logged in before the connection loss, set the state
1084
+ // to pending to notify that the user is no longer logged in and a login
1085
+ // attempt will be made when the connection is restored.
1086
+ if (identify.state.state === 'logged_in') {
1087
+ identify.state = {
1088
+ state: 'pending',
1089
+ };
1090
+ }
1091
+ });
1092
+ sdkConnectKit.onDisconnected(conn, () => {
1093
+ taskQueue.pause();
1094
+ taskQueue.clear();
1095
+ // If the user was logged in before the connection loss, set the state
1096
+ // to logged_out to notify that the user is no longer logged in.
1097
+ if (identify.state.state === 'logged_in') {
1098
+ identify.state = {
1099
+ state: 'logged_out',
1100
+ };
1101
+ }
1102
+ });
1103
+ }
1104
+
1105
+ /**
1106
+ * @license
1107
+ * @monterosa/sdk-identify-kit
1108
+ *
1109
+ * Copyright © 2023-2026 Monterosa Productions Limited. All rights reserved.
1110
+ *
1111
+ * More details on the license can be found at https://www.monterosa.co/sdk/license
1112
+ */
1113
+ /**
1114
+ * Process an incoming `Login` / `Logout` broadcast from the parent
1115
+ * application. Updates the local credentials slot, enqueues login/logout
1116
+ * for every existing identify, and forwards the broadcast downstream to
1117
+ * this level's own embedded experiences — the mechanism that cascades a
1118
+ * root-level `setCredentials` through every nested experience.
1119
+ *
1120
+ * @internal
1121
+ */
1122
+ function handleParentMessage(message) {
1123
+ switch (message.action) {
1124
+ case IdentifyAction.Login: {
1125
+ applyLogin(message.payload);
1126
+ break;
1127
+ }
1128
+ case IdentifyAction.Logout: {
1129
+ applyLogout();
1130
+ break;
1131
+ }
1132
+ }
1133
+ }
1134
+ /**
1135
+ * @internal
1136
+ */
1137
+ function handleExperienceEmbedded(experience) {
1138
+ const sdkMessageUnsub = sdkLauncherKit.onSdkMessage(experience, async (message) => {
1139
+ if (!Object.values(IdentifyAction).includes(message.action)) {
1140
+ return;
1141
+ }
1142
+ const respond = (payload) => sdkLauncherKit.respondToSdkMessage(experience, message, payload);
1143
+ try {
1144
+ switch (message.action) {
1145
+ case IdentifyAction.RequestLogin: {
1146
+ await requestLogin();
1147
+ respond();
1148
+ break;
1149
+ }
1150
+ case IdentifyAction.RequestLogout: {
1151
+ await requestLogout();
1152
+ respond();
1153
+ break;
1154
+ }
1155
+ case IdentifyAction.GetCredentials: {
1156
+ const data = await getCredentials();
1157
+ respond({ data });
1158
+ break;
1159
+ }
1160
+ case IdentifyAction.SetCredentials: {
1161
+ await setCredentials(message.payload);
1162
+ respond();
1163
+ break;
1164
+ }
1165
+ case IdentifyAction.ClearCredentials: {
1166
+ await clearCredentials();
1167
+ respond();
1168
+ break;
1169
+ }
1170
+ }
1171
+ }
1172
+ catch (err) {
1173
+ respond({
1174
+ error: sdkUtil.getErrorMessage(err),
1175
+ });
1176
+ }
1177
+ });
1178
+ addExperience(experience, [sdkMessageUnsub]);
1179
+ }
1180
+ /**
1181
+ * @internal
1182
+ */
1183
+ function handleExperienceUnmounted(experience) {
1184
+ deleteExperience(experience);
1185
+ }
1186
+ /**
1187
+ * @internal
1188
+ */
1189
+ function handleExperienceStateChange(experience, state) {
1190
+ if (state === 'mounted') {
1191
+ handleExperienceEmbedded(experience);
1192
+ return;
1193
+ }
1194
+ if (state === 'unmounted') {
1195
+ handleExperienceUnmounted(experience);
1196
+ }
1197
+ }
1198
+ function subscribeToExperienceLifecycle() {
1199
+ sdkLauncherKit.onStateChanged(handleExperienceStateChange);
1200
+ }
1201
+ function connectToParentApplication() {
1202
+ const parentApp = sdkLauncherKit.getParentApplication();
1203
+ if (parentApp === null) {
1204
+ return;
1205
+ }
1206
+ sdkLauncherKit.onSdkMessage(parentApp, handleParentMessage);
1207
+ pullStateFromParent();
1208
+ }
1209
+ subscribeToExperienceLifecycle();
1210
+ connectToParentApplication();
1211
+
1212
+ exports.clearCredentials = clearCredentials;
1213
+ exports.getCredentials = getCredentials;
1214
+ exports.getIdentify = getIdentify;
1215
+ exports.getUserData = getUserData;
1216
+ exports.onCredentialsUpdated = onCredentialsUpdated;
1217
+ exports.onLoginRequestedByExperience = onLoginRequestedByExperience;
1218
+ exports.onLogoutRequestedByExperience = onLogoutRequestedByExperience;
1219
+ exports.onStateUpdated = onStateUpdated;
1220
+ exports.requestLogin = requestLogin;
1221
+ exports.requestLogout = requestLogout;
1222
+ exports.setCredentials = setCredentials;
1223
+ exports.setLoginPolicy = setLoginPolicy;
1224
+ //# sourceMappingURL=index.cjs.map