@nymbalhq/sdk 1.5.0 → 1.7.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/LICENSE +21 -0
- package/README.md +6 -0
- package/dist/{chunk-EFRWJT3L.js → chunk-2EEZZXZX.js} +102 -21
- package/dist/chunk-2EEZZXZX.js.map +1 -0
- package/dist/{chunk-333Z6VKZ.js → chunk-7LQGOJIT.js} +2 -2
- package/dist/{chunk-QUSTGYBM.js → chunk-CJVA46FR.js} +2 -2
- package/dist/{chunk-L7EZURSK.js → chunk-PDZKR65O.js} +23 -5
- package/dist/chunk-PDZKR65O.js.map +1 -0
- package/dist/{chunk-BAWTE4QS.js → chunk-VYX72KGY.js} +6 -3
- package/dist/chunk-VYX72KGY.js.map +1 -0
- package/dist/{chunk-KRVUYBKB.js → chunk-XS7RNJSY.js} +2 -2
- package/dist/{chunk-FWEQBOEX.js → chunk-YHXC7DS5.js} +2 -2
- package/dist/{core-DCBjP2Ev.d.ts → core-BviXjOsX.d.ts} +46 -9
- package/dist/core.d.ts +1 -1
- package/dist/core.js +7 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +9 -7
- package/dist/ns/auth.js +2 -2
- package/dist/ns/cart.js +2 -2
- package/dist/ns/checkout.js +3 -3
- package/dist/ns/product-list.js +2 -2
- package/dist/ns/product.js +2 -2
- package/dist/ns/search.js +2 -2
- package/package.json +2 -2
- package/dist/chunk-BAWTE4QS.js.map +0 -1
- package/dist/chunk-EFRWJT3L.js.map +0 -1
- package/dist/chunk-L7EZURSK.js.map +0 -1
- /package/dist/{chunk-333Z6VKZ.js.map → chunk-7LQGOJIT.js.map} +0 -0
- /package/dist/{chunk-QUSTGYBM.js.map → chunk-CJVA46FR.js.map} +0 -0
- /package/dist/{chunk-KRVUYBKB.js.map → chunk-XS7RNJSY.js.map} +0 -0
- /package/dist/{chunk-FWEQBOEX.js.map → chunk-YHXC7DS5.js.map} +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 nymbal contributors
|
|
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,6 @@
|
|
|
1
|
+
# @nymbalhq/sdk
|
|
2
|
+
|
|
3
|
+
The client side of a Nymbal store, independent of any framework: stores for the catalogue, cart, account and
|
|
4
|
+
checkout over the store's API.
|
|
5
|
+
|
|
6
|
+
See the [documentation](https://github.com/nymbalhq/nymbal/blob/main/apps/docs/src/content/docs/index.md).
|
|
@@ -24,6 +24,8 @@ var FetchClient = class {
|
|
|
24
24
|
refreshing = null;
|
|
25
25
|
/** The token `GET /api/csrf` answered: the API's `nymbal.csrf` cookie, which this origin may not be able to read. */
|
|
26
26
|
csrfToken = null;
|
|
27
|
+
/** A session restore's refresh, which every request sent meanwhile waits on so that none goes out as a guest. */
|
|
28
|
+
held = null;
|
|
27
29
|
constructor(options) {
|
|
28
30
|
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
29
31
|
this.credentials = options.credentials ?? "include";
|
|
@@ -31,11 +33,32 @@ var FetchClient = class {
|
|
|
31
33
|
this.getCartToken = options.getCartToken;
|
|
32
34
|
this.onUnauthorized = options.onUnauthorized;
|
|
33
35
|
}
|
|
36
|
+
/** Starts the session's refresh, or joins the one in flight: a client sends one refresh at a time. */
|
|
37
|
+
refreshSession() {
|
|
38
|
+
if (this.onUnauthorized === void 0) return Promise.resolve();
|
|
39
|
+
return this.refreshing ?? this.refresh(this.onUnauthorized);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* As `refreshSession`, and every other request sent until the refresh settles waits for it, at most
|
|
43
|
+
* `REFRESH_WAIT_MS`. Held from this call on, before any await: a page's first requests go in the same tick.
|
|
44
|
+
*/
|
|
45
|
+
restoreSession() {
|
|
46
|
+
const held = this.waitFor(this.refreshSession()).then(
|
|
47
|
+
() => void 0,
|
|
48
|
+
() => void 0
|
|
49
|
+
);
|
|
50
|
+
this.held = held;
|
|
51
|
+
void held.then(() => {
|
|
52
|
+
if (this.held === held) this.held = null;
|
|
53
|
+
});
|
|
54
|
+
return held;
|
|
55
|
+
}
|
|
34
56
|
async request(method, path, body, options = {}) {
|
|
35
57
|
return (await this.requestWithMeta(method, path, body, options)).data;
|
|
36
58
|
}
|
|
37
59
|
/** Like `request`, keeping the envelope's `meta` (pagination cursors live there). */
|
|
38
60
|
async requestWithMeta(method, path, body, options = {}) {
|
|
61
|
+
if (this.held !== null && options.authRefresh !== true) await this.held;
|
|
39
62
|
const sentDuring = this.refreshing;
|
|
40
63
|
let { response, bearer } = await this.doFetch(method, path, body, options);
|
|
41
64
|
if (await this.isCsrfRejection(response)) {
|
|
@@ -44,7 +67,12 @@ var FetchClient = class {
|
|
|
44
67
|
}
|
|
45
68
|
if (response.status === 401 && this.onUnauthorized && options.authRefresh !== true) {
|
|
46
69
|
const pending = this.refreshing;
|
|
47
|
-
const retry = async () =>
|
|
70
|
+
const retry = async () => {
|
|
71
|
+
if (options.keepIdentity === true && bearer !== null && this.getAccessToken() === null) {
|
|
72
|
+
return this.parseResponse(response);
|
|
73
|
+
}
|
|
74
|
+
return this.parseResponse((await this.doFetch(method, path, body, options)).response);
|
|
75
|
+
};
|
|
48
76
|
if (pending === null && this.getAccessToken() !== bearer) {
|
|
49
77
|
return retry();
|
|
50
78
|
}
|
|
@@ -224,7 +252,9 @@ function createNymbalApiAdapter(fetchClient) {
|
|
|
224
252
|
},
|
|
225
253
|
checkout: {
|
|
226
254
|
create(details) {
|
|
227
|
-
return fetchClient.request("POST", "/api/checkout", details
|
|
255
|
+
return fetchClient.request("POST", "/api/checkout", details, {
|
|
256
|
+
keepIdentity: true
|
|
257
|
+
});
|
|
228
258
|
}
|
|
229
259
|
},
|
|
230
260
|
customers: {
|
|
@@ -311,11 +341,50 @@ function createNymbalApiAdapter(fetchClient) {
|
|
|
311
341
|
};
|
|
312
342
|
}
|
|
313
343
|
|
|
344
|
+
// src/auth/session.ts
|
|
345
|
+
var SESSION_HINT_KEY = "nymbal.auth.session";
|
|
346
|
+
var ACCESS_TOKEN_KEY = "nymbal.auth.accessToken";
|
|
347
|
+
function withStorage(use, fallback) {
|
|
348
|
+
try {
|
|
349
|
+
return typeof globalThis.localStorage === "undefined" ? fallback : use(globalThis.localStorage);
|
|
350
|
+
} catch {
|
|
351
|
+
return fallback;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function hasSessionHint() {
|
|
355
|
+
return withStorage((storage) => storage.getItem(SESSION_HINT_KEY) === "1", false);
|
|
356
|
+
}
|
|
357
|
+
function markSession() {
|
|
358
|
+
withStorage((storage) => storage.setItem(SESSION_HINT_KEY, "1"), void 0);
|
|
359
|
+
}
|
|
360
|
+
function forgetSession() {
|
|
361
|
+
withStorage((storage) => storage.removeItem(SESSION_HINT_KEY), void 0);
|
|
362
|
+
}
|
|
363
|
+
function adoptLegacyToken() {
|
|
364
|
+
withStorage((storage) => {
|
|
365
|
+
if (storage.getItem(ACCESS_TOKEN_KEY) === null) return;
|
|
366
|
+
storage.removeItem(ACCESS_TOKEN_KEY);
|
|
367
|
+
storage.setItem(SESSION_HINT_KEY, "1");
|
|
368
|
+
}, void 0);
|
|
369
|
+
}
|
|
370
|
+
function isSessionRefusal(err) {
|
|
371
|
+
return err instanceof NymbalApiError && (err.status === 401 || err.status === 403);
|
|
372
|
+
}
|
|
373
|
+
async function renewSession(adapter, tokenManager) {
|
|
374
|
+
try {
|
|
375
|
+
const result = await adapter.customers.refresh();
|
|
376
|
+
tokenManager.setTokens(result.accessToken, result.expiresIn);
|
|
377
|
+
} catch (err) {
|
|
378
|
+
console.warn("[NymbalClient] token refresh failed", err);
|
|
379
|
+
tokenManager.clear();
|
|
380
|
+
if (isSessionRefusal(err)) forgetSession();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
314
384
|
// src/auth/token-manager.ts
|
|
315
|
-
var DEFAULT_STORAGE_KEY = "nymbal.auth.accessToken";
|
|
316
385
|
function createTokenManager(options) {
|
|
317
386
|
const storageMode = options?.storage ?? "memory";
|
|
318
|
-
const storageKey = options?.storageKey ??
|
|
387
|
+
const storageKey = options?.storageKey ?? ACCESS_TOKEN_KEY;
|
|
319
388
|
let memoryToken = null;
|
|
320
389
|
let refreshTimer = null;
|
|
321
390
|
let refreshFn = null;
|
|
@@ -337,6 +406,7 @@ function createTokenManager(options) {
|
|
|
337
406
|
}
|
|
338
407
|
function setTokens(accessToken, expiresIn) {
|
|
339
408
|
setAccessToken(accessToken);
|
|
409
|
+
markSession();
|
|
340
410
|
scheduleAutoRefresh(expiresIn);
|
|
341
411
|
}
|
|
342
412
|
function clear() {
|
|
@@ -355,7 +425,7 @@ function createTokenManager(options) {
|
|
|
355
425
|
if (!refreshFn) return;
|
|
356
426
|
try {
|
|
357
427
|
const result = await refreshFn();
|
|
358
|
-
setTokens(result.accessToken, result.expiresIn);
|
|
428
|
+
if (result) setTokens(result.accessToken, result.expiresIn);
|
|
359
429
|
} catch (err) {
|
|
360
430
|
console.error("[TokenManager] auto-refresh failed, clearing session", err);
|
|
361
431
|
clear();
|
|
@@ -394,6 +464,11 @@ var STORE_BUILT = /* @__PURE__ */ Symbol.for("nymbal.client.onStoreBuilt");
|
|
|
394
464
|
function isStore(value) {
|
|
395
465
|
return typeof value === "object" && value !== null && typeof value.subscribe === "function" && typeof value.getState === "function";
|
|
396
466
|
}
|
|
467
|
+
var RESUME_SESSION = /* @__PURE__ */ Symbol.for("nymbal.client.resumeSession");
|
|
468
|
+
function resumeSession(client) {
|
|
469
|
+
const hook = client[RESUME_SESSION];
|
|
470
|
+
return typeof hook === "function" ? hook() : Promise.resolve();
|
|
471
|
+
}
|
|
397
472
|
function onStoreBuilt(client, listener) {
|
|
398
473
|
const hook = client[STORE_BUILT];
|
|
399
474
|
if (typeof hook === "function") return hook(listener);
|
|
@@ -404,27 +479,22 @@ function onStoreBuilt(client, listener) {
|
|
|
404
479
|
};
|
|
405
480
|
}
|
|
406
481
|
function createNymbalClient(options) {
|
|
482
|
+
if (options.tokenStorage !== "localStorage") adoptLegacyToken();
|
|
407
483
|
const tokenManager = createTokenManager({
|
|
408
484
|
...options.tokenStorage !== void 0 && { storage: options.tokenStorage }
|
|
409
485
|
});
|
|
410
486
|
let adapter;
|
|
487
|
+
let fetchClient;
|
|
488
|
+
const renew = () => renewSession(adapter, tokenManager);
|
|
411
489
|
if (options.adapter) {
|
|
412
490
|
adapter = options.adapter;
|
|
413
491
|
} else {
|
|
414
|
-
|
|
492
|
+
fetchClient = new FetchClient({
|
|
415
493
|
baseUrl: options.baseUrl,
|
|
416
494
|
...options.credentials !== void 0 && { credentials: options.credentials },
|
|
417
495
|
getAccessToken: () => tokenManager.getAccessToken(),
|
|
418
496
|
...options.getCartToken !== void 0 && { getCartToken: options.getCartToken },
|
|
419
|
-
onUnauthorized:
|
|
420
|
-
try {
|
|
421
|
-
const result = await adapter.customers.refresh();
|
|
422
|
-
tokenManager.setTokens(result.accessToken, result.expiresIn);
|
|
423
|
-
} catch (err) {
|
|
424
|
-
console.warn("[NymbalClient] token refresh on unauthorized failed", err);
|
|
425
|
-
tokenManager.clear();
|
|
426
|
-
}
|
|
427
|
-
}
|
|
497
|
+
onUnauthorized: renew
|
|
428
498
|
});
|
|
429
499
|
adapter = createNymbalApiAdapter(fetchClient);
|
|
430
500
|
}
|
|
@@ -469,7 +539,7 @@ function createNymbalClient(options) {
|
|
|
469
539
|
return recentlyViewedOf().list();
|
|
470
540
|
}
|
|
471
541
|
};
|
|
472
|
-
tokenManager.scheduleRefresh(() =>
|
|
542
|
+
tokenManager.scheduleRefresh(() => fetchClient?.refreshSession() ?? renew());
|
|
473
543
|
const built = {};
|
|
474
544
|
const listeners = /* @__PURE__ */ new Set();
|
|
475
545
|
let destroyed = false;
|
|
@@ -485,12 +555,17 @@ function createNymbalClient(options) {
|
|
|
485
555
|
listeners.delete(listener);
|
|
486
556
|
};
|
|
487
557
|
};
|
|
488
|
-
const
|
|
558
|
+
const resume = () => tokenManager.getAccessToken() || !hasSessionHint() ? Promise.resolve() : context.restoreSession();
|
|
559
|
+
const client = Object.defineProperties(
|
|
489
560
|
{ capabilities, recentlyViewed, customer, orders, adapter, destroy },
|
|
490
|
-
STORE_BUILT,
|
|
491
|
-
{ value: storeBuilt }
|
|
561
|
+
{ [STORE_BUILT]: { value: storeBuilt }, [RESUME_SESSION]: { value: resume } }
|
|
492
562
|
);
|
|
493
|
-
const context = {
|
|
563
|
+
const context = {
|
|
564
|
+
adapter,
|
|
565
|
+
tokenManager,
|
|
566
|
+
client,
|
|
567
|
+
restoreSession: () => fetchClient?.restoreSession() ?? renew()
|
|
568
|
+
};
|
|
494
569
|
function build(name) {
|
|
495
570
|
if (destroyed) throw new Error(`client.${name} was read after the client was destroyed`);
|
|
496
571
|
const factory = factories()[name];
|
|
@@ -512,9 +587,15 @@ export {
|
|
|
512
587
|
NymbalApiError,
|
|
513
588
|
FetchClient,
|
|
514
589
|
createNymbalApiAdapter,
|
|
590
|
+
SESSION_HINT_KEY,
|
|
591
|
+
hasSessionHint,
|
|
592
|
+
forgetSession,
|
|
593
|
+
isSessionRefusal,
|
|
594
|
+
renewSession,
|
|
515
595
|
createTokenManager,
|
|
516
596
|
defineNamespace,
|
|
597
|
+
resumeSession,
|
|
517
598
|
onStoreBuilt,
|
|
518
599
|
createNymbalClient
|
|
519
600
|
};
|
|
520
|
-
//# sourceMappingURL=chunk-
|
|
601
|
+
//# sourceMappingURL=chunk-2EEZZXZX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapter/fetch-client.ts","../src/adapter/nymbal-api-adapter.ts","../src/auth/session.ts","../src/auth/token-manager.ts","../src/client.ts"],"sourcesContent":["export class NymbalApiError extends Error {\n constructor(\n public readonly code: string,\n message: string,\n public readonly status: number,\n public readonly context?: Record<string, unknown>,\n ) {\n super(message)\n this.name = 'NymbalApiError'\n }\n}\n\nexport interface FetchClientOptions {\n baseUrl: string\n credentials?: RequestCredentials\n getAccessToken?: () => string | null\n getCartToken?: () => string | null\n onUnauthorized?: () => Promise<void>\n}\n\n/** Per request: headers sent beside the client's own. */\nexport interface RequestOptions {\n headers?: Record<string, string>\n /**\n * The token refresh itself (what `onUnauthorized` sends through this client): its 401 is thrown, never waited on or\n * sent again, so a refused refresh cannot wait on itself. An unmarked one that is refused is cut off after\n * 10 s with the original 401.\n */\n authRefresh?: boolean\n /**\n * Sent with a customer's bearer, never sent again without one: when the 401's refresh leaves no token, the 401 is\n * thrown. For a request the API would otherwise answer as a guest (a checkout begin).\n */\n keepIdentity?: boolean\n}\n\n/**\n * The longest a refused request waits on a refresh before its 401 is thrown: far beyond one healthy refresh round trip,\n * and the bound on a refresh that, unmarked, waits on itself.\n */\nconst REFRESH_WAIT_MS = 10_000\n\nconst CSRF_HEADER = 'x-csrf-token'\nconst UNSAFE_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\ninterface Envelope<T> {\n data?: T\n meta?: Record<string, unknown>\n error?: { code: string; message: string; context?: Record<string, unknown> }\n}\n\nexport class FetchClient {\n private readonly baseUrl: string\n private readonly credentials: RequestCredentials\n private readonly getAccessToken: () => string | null\n private readonly getCartToken: (() => string | null) | undefined\n private readonly onUnauthorized: (() => Promise<void>) | undefined\n /** The refresh a refused request started, which every request refused meanwhile waits on rather than its own. */\n private refreshing: Promise<void> | null = null\n /** The token `GET /api/csrf` answered: the API's `nymbal.csrf` cookie, which this origin may not be able to read. */\n private csrfToken: Promise<string> | null = null\n /** A session restore's refresh, which every request sent meanwhile waits on so that none goes out as a guest. */\n private held: Promise<void> | null = null\n\n constructor(options: FetchClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/+$/, '')\n this.credentials = options.credentials ?? 'include'\n this.getAccessToken = options.getAccessToken ?? (() => null)\n this.getCartToken = options.getCartToken\n this.onUnauthorized = options.onUnauthorized\n }\n\n /** Starts the session's refresh, or joins the one in flight: a client sends one refresh at a time. */\n refreshSession(): Promise<void> {\n if (this.onUnauthorized === undefined) return Promise.resolve()\n return this.refreshing ?? this.refresh(this.onUnauthorized)\n }\n\n /**\n * As `refreshSession`, and every other request sent until the refresh settles waits for it, at most\n * `REFRESH_WAIT_MS`. Held from this call on, before any await: a page's first requests go in the same tick.\n */\n restoreSession(): Promise<void> {\n // A failed or unanswered refresh releases the held requests as well; they go with whatever token there is.\n const held = this.waitFor(this.refreshSession()).then(\n () => undefined,\n () => undefined,\n )\n this.held = held\n void held.then(() => {\n if (this.held === held) this.held = null\n })\n return held\n }\n\n async request<T>(\n method: string,\n path: string,\n body?: unknown,\n options: RequestOptions = {},\n ): Promise<T> {\n return (await this.requestWithMeta<T>(method, path, body, options)).data\n }\n\n /** Like `request`, keeping the envelope's `meta` (pagination cursors live there). */\n async requestWithMeta<T>(\n method: string,\n path: string,\n body?: unknown,\n options: RequestOptions = {},\n ): Promise<{ data: T; meta: Record<string, unknown> }> {\n if (this.held !== null && options.authRefresh !== true) await this.held\n // Read before the send's first await: the refresh, if any, in flight when this request was sent.\n const sentDuring = this.refreshing\n let { response, bearer } = await this.doFetch(method, path, body, options)\n\n // A stale token (the cookie was replaced or expired) is fetched again once; a second refusal is thrown.\n if (await this.isCsrfRejection(response)) {\n this.csrfToken = null\n ;({ response, bearer } = await this.doFetch(method, path, body, options))\n }\n\n if (response.status === 401 && this.onUnauthorized && options.authRefresh !== true) {\n const pending = this.refreshing\n const retry = async () => {\n // Sent as a customer, and the refresh left no token: sent again it would act as a guest.\n if (options.keepIdentity === true && bearer !== null && this.getAccessToken() === null) {\n return this.parseResponse<T>(response)\n }\n return this.parseResponse<T>((await this.doFetch(method, path, body, options)).response)\n }\n if (pending === null && this.getAccessToken() !== bearer) {\n // A refresh that ended after this was sent has replaced its token already.\n return retry()\n }\n if (pending !== null && pending === sentDuring) {\n // Sent during the refresh with the token it replaces: sent again once it has, and only if it did.\n if ((await this.waitFor(pending)) && this.getAccessToken() !== bearer) return retry()\n } else if (await this.waitFor(pending ?? this.refresh(this.onUnauthorized))) {\n // Every request refused with the old token waits on the one refresh and is sent again once.\n return retry()\n }\n }\n\n return this.parseResponse<T>(response)\n }\n\n /** Whether the refresh ended within `REFRESH_WAIT_MS`; a refresh that fails throws here, as it always has. */\n private async waitFor(refresh: Promise<void>): Promise<boolean> {\n let timer: ReturnType<typeof setTimeout> | undefined\n const bound = new Promise<false>((resolve) => {\n timer = setTimeout(() => resolve(false), REFRESH_WAIT_MS)\n })\n try {\n return await Promise.race([refresh.then(() => true as const), bound])\n } finally {\n clearTimeout(timer)\n }\n }\n\n private refresh(onUnauthorized: () => Promise<void>): Promise<void> {\n const refresh = Promise.resolve()\n .then(onUnauthorized)\n .finally(() => {\n this.refreshing = null\n })\n this.refreshing = refresh\n return refresh\n }\n\n private csrf(): Promise<string> {\n this.csrfToken ??= this.fetchCsrfToken().catch((err: unknown) => {\n this.csrfToken = null\n throw err\n })\n return this.csrfToken\n }\n\n private async fetchCsrfToken(): Promise<string> {\n const response = await globalThis.fetch(`${this.baseUrl}/api/csrf`, {\n method: 'GET',\n credentials: this.credentials,\n })\n const { data } = await this.parseResponse<{ token: string }>(response)\n return data.token\n }\n\n private async isCsrfRejection(response: Response): Promise<boolean> {\n if (response.status !== 403) return false\n const text = await response.clone().text()\n try {\n return (JSON.parse(text) as Envelope<unknown>).error?.code === 'csrf.invalid'\n } catch {\n return false\n }\n }\n\n private async doFetch(\n method: string,\n path: string,\n body: unknown,\n options: RequestOptions,\n ): Promise<{ response: Response; bearer: string | null }> {\n const headers: Record<string, string> = { ...options.headers }\n const cookies: string[] = []\n\n if (UNSAFE_METHODS.has(method.toUpperCase())) {\n const csrf = await this.csrf()\n headers[CSRF_HEADER] = csrf\n // Outside a browser there is no cookie jar to send the pair's cookie half; a browser drops this header.\n cookies.push(`nymbal.csrf=${csrf}`)\n }\n\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json'\n }\n\n const token = this.getAccessToken()\n if (token) {\n headers['Authorization'] = `Bearer ${token}`\n }\n\n const cartToken = this.getCartToken?.()\n if (cartToken) {\n cookies.unshift(`nymbal.cart=${cartToken}`)\n }\n if (cookies.length > 0) {\n headers['Cookie'] = cookies.join('; ')\n }\n\n const response = await globalThis.fetch(`${this.baseUrl}${path}`, {\n method,\n headers,\n credentials: this.credentials,\n ...(body !== undefined && { body: JSON.stringify(body) }),\n })\n return { response, bearer: token }\n }\n\n private async parseResponse<T>(\n response: Response,\n ): Promise<{ data: T; meta: Record<string, unknown> }> {\n const text = await response.text()\n if (!text) {\n return { data: undefined as T, meta: {} }\n }\n\n const envelope = JSON.parse(text) as Envelope<T>\n\n if (envelope.error) {\n throw new NymbalApiError(\n envelope.error.code,\n envelope.error.message,\n response.status,\n envelope.error.context,\n )\n }\n\n return { data: envelope.data as T, meta: envelope.meta ?? {} }\n }\n}\n","import {\n ORDER_EMAIL_HEADER,\n ORDER_KEY_HEADER,\n type AlertKind,\n type Cart,\n type Customer,\n type CustomerAddress,\n type Order,\n type PublicReviewsResult,\n type ReviewResult,\n type ShopperOrder,\n type StoreCapabilities,\n} from '@nymbalhq/types'\nimport type { CommerceAdapter } from './commerce-adapter.js'\nimport type { FetchClient } from './fetch-client.js'\nimport type {\n DenormalisedProduct,\n ProductListParams,\n ProductListResult,\n ProductSearchParams,\n ProductSearchResult,\n CheckoutDetails,\n CheckoutResult,\n RegisterParams,\n AuthResult,\n NewAddress,\n OrderPage,\n} from '../types.js'\n\nexport function createNymbalApiAdapter(fetchClient: FetchClient): CommerceAdapter {\n // One read for a signed-in owner's order and a guest's: the key and the email, when given, go in headers.\n const getOrder = (orderNumber: string, { key, email }: { key?: string; email?: string } = {}) =>\n fetchClient.request<ShopperOrder>(\n 'GET',\n `/api/orders/${encodeURIComponent(orderNumber)}`,\n undefined,\n {\n headers: {\n ...(key !== undefined && { [ORDER_KEY_HEADER]: key }),\n ...(email !== undefined && { [ORDER_EMAIL_HEADER]: email }),\n },\n },\n )\n\n return {\n capabilities: {\n get() {\n return fetchClient.request<StoreCapabilities>('GET', '/api/capabilities')\n },\n },\n\n cart: {\n get() {\n return fetchClient.request<Cart>('GET', '/api/cart')\n },\n addItem(variantId: string, qty: number) {\n return fetchClient.request<Cart>('POST', '/api/cart/items', { variantId, qty })\n },\n removeItem(variantId: string) {\n return fetchClient.request<Cart>(\n 'DELETE',\n `/api/cart/items/${encodeURIComponent(variantId)}`,\n )\n },\n updateQuantity(variantId: string, qty: number) {\n return fetchClient.request<Cart>(\n 'PATCH',\n `/api/cart/items/${encodeURIComponent(variantId)}`,\n { qty },\n )\n },\n clear() {\n return fetchClient.request<Cart>('DELETE', '/api/cart')\n },\n },\n\n products: {\n getBySlug(slug: string) {\n return fetchClient.request<DenormalisedProduct>(\n 'GET',\n `/api/products/${encodeURIComponent(slug)}`,\n )\n },\n list(params?: ProductListParams) {\n const search = new URLSearchParams()\n if (params?.limit !== undefined) search.set('limit', String(params.limit))\n if (params?.cursor !== undefined) search.set('cursor', params.cursor)\n if (params?.category !== undefined) search.set('category', params.category)\n if (params?.sort !== undefined) search.set('sort', params.sort)\n const qs = search.toString()\n return fetchClient.request<ProductListResult>('GET', `/api/products${qs ? `?${qs}` : ''}`)\n },\n search(query: string, params?: ProductSearchParams) {\n const search = new URLSearchParams()\n search.set('q', query)\n if (params?.limit !== undefined) search.set('limit', String(params.limit))\n if (params?.cursor !== undefined) search.set('cursor', params.cursor)\n if (params?.category !== undefined) search.set('category', params.category)\n return fetchClient.request<ProductSearchResult>(\n 'GET',\n `/api/products/search?${search.toString()}`,\n )\n },\n },\n\n checkout: {\n create(details: CheckoutDetails) {\n // Placed as the signed-in customer or refused: never re-sent as a guest after a failed refresh.\n return fetchClient.request<CheckoutResult>('POST', '/api/checkout', details, {\n keepIdentity: true,\n })\n },\n },\n\n customers: {\n register(params: RegisterParams) {\n return fetchClient.request<AuthResult>('POST', '/api/auth/register', params)\n },\n login(email: string, password: string) {\n return fetchClient.request<AuthResult>('POST', '/api/auth/login', { email, password })\n },\n async logout() {\n await fetchClient.request<{ ok: boolean }>('POST', '/api/auth/logout')\n },\n refresh() {\n return fetchClient.request<{ accessToken: string; expiresIn: number }>(\n 'POST',\n '/api/auth/refresh',\n undefined,\n { authRefresh: true },\n )\n },\n getProfile() {\n return fetchClient.request<Customer>('GET', '/api/customers/me')\n },\n updateProfile(updates: Partial<Pick<Customer, 'firstName' | 'lastName' | 'phone'>>) {\n return fetchClient.request<Customer>('PATCH', '/api/customers/me', updates)\n },\n listAddresses() {\n return fetchClient.request<CustomerAddress[]>('GET', '/api/customers/me/addresses')\n },\n addAddress(address: NewAddress) {\n return fetchClient.request<CustomerAddress>('POST', '/api/customers/me/addresses', address)\n },\n async removeAddress(id: string) {\n await fetchClient.request<{ ok: boolean }>(\n 'DELETE',\n `/api/customers/me/addresses/${encodeURIComponent(id)}`,\n )\n },\n async listOrders(params?: { cursor?: string }): Promise<OrderPage> {\n const qs =\n params?.cursor !== undefined ? `?cursor=${encodeURIComponent(params.cursor)}` : ''\n const { data, meta } = await fetchClient.requestWithMeta<Order[]>('GET', `/api/orders${qs}`)\n const nextCursor = typeof meta.nextCursor === 'string' ? meta.nextCursor : null\n return { items: data ?? [], nextCursor }\n },\n getOrder,\n },\n\n orders: {\n get: getOrder,\n async requestLink({ orderNumber, email }: { orderNumber: string; email: string }) {\n await fetchClient.request<{ sent: string }>('POST', '/api/orders/link', {\n orderNumber,\n email,\n })\n },\n },\n\n recentlyViewed: {\n async record(productId: string) {\n await fetchClient.request<void>('POST', '/api/recently-viewed', { productId })\n },\n list() {\n return fetchClient.request<{ items: DenormalisedProduct[] }>('GET', '/api/recently-viewed')\n },\n },\n\n reviews: {\n getForProduct(productId: string, params?: { limit?: number; cursor?: string }) {\n const search = new URLSearchParams()\n if (params?.limit !== undefined) search.set('limit', String(params.limit))\n if (params?.cursor !== undefined) search.set('cursor', params.cursor)\n const qs = search.toString()\n return fetchClient.request<PublicReviewsResult>(\n 'GET',\n `/api/products/${encodeURIComponent(productId)}/reviews${qs ? `?${qs}` : ''}`,\n )\n },\n submit(review: { productId: string; rating: number; title: string; body: string }) {\n return fetchClient.request<ReviewResult>('POST', '/api/reviews', review)\n },\n },\n\n alerts: {\n subscribe(request: { email: string; variantId: string; kind: AlertKind }) {\n return fetchClient.request<{ status: 'pending' }>('POST', '/api/alerts/subscribe', request)\n },\n },\n }\n}\n","import type { CommerceAdapter } from '../adapter/commerce-adapter.js'\nimport { NymbalApiError } from '../adapter/fetch-client.js'\nimport type { TokenManager } from './token-manager.js'\n\n/**\n * Set in `localStorage` while the browser holds a session the refresh cookie can bring back. It holds no token: a page\n * load with no hint is a guest's, and restores nothing.\n */\nexport const SESSION_HINT_KEY = 'nymbal.auth.session'\n\n/** Where `tokenStorage: 'localStorage'` keeps the access token, and where every client kept it before. */\nexport const ACCESS_TOKEN_KEY = 'nymbal.auth.accessToken'\n\n/** Runs `use` on `localStorage`, or answers `fallback` where there is none or it throws (a locked-down window). */\nfunction withStorage<T>(use: (storage: Storage) => T, fallback: T): T {\n try {\n return typeof globalThis.localStorage === 'undefined' ? fallback : use(globalThis.localStorage)\n } catch {\n return fallback\n }\n}\n\nexport function hasSessionHint(): boolean {\n return withStorage((storage) => storage.getItem(SESSION_HINT_KEY) === '1', false)\n}\n\nexport function markSession(): void {\n withStorage((storage) => storage.setItem(SESSION_HINT_KEY, '1'), undefined)\n}\n\nexport function forgetSession(): void {\n withStorage((storage) => storage.removeItem(SESSION_HINT_KEY), undefined)\n}\n\n/** Removes an access token an earlier version kept in `localStorage`; the shopper it belonged to keeps their session. */\nexport function adoptLegacyToken(): void {\n withStorage((storage) => {\n if (storage.getItem(ACCESS_TOKEN_KEY) === null) return\n storage.removeItem(ACCESS_TOKEN_KEY)\n storage.setItem(SESSION_HINT_KEY, '1')\n }, undefined)\n}\n\n/** The API refused the session itself, as against a refresh that never reached it or that it failed to answer. */\nexport function isSessionRefusal(err: unknown): boolean {\n return err instanceof NymbalApiError && (err.status === 401 || err.status === 403)\n}\n\n/**\n * One refresh from the refresh cookie. It never throws: a failure leaves no token, and forgets the session only when\n * the API refused it, so an API that is down or restarting signs no one out for good.\n */\nexport async function renewSession(\n adapter: CommerceAdapter,\n tokenManager: TokenManager,\n): Promise<void> {\n try {\n const result = await adapter.customers.refresh()\n tokenManager.setTokens(result.accessToken, result.expiresIn)\n } catch (err) {\n console.warn('[NymbalClient] token refresh failed', err)\n tokenManager.clear()\n if (isSessionRefusal(err)) forgetSession()\n }\n}\n","import { ACCESS_TOKEN_KEY, markSession } from './session.js'\n\n/** A refresh: its token for the manager to hold, or nothing when it has set (or cleared) the token itself. */\ntype RefreshFn = () => Promise<{ accessToken: string; expiresIn: number } | void>\n\nexport interface TokenManagerOptions {\n storage?: 'memory' | 'localStorage'\n storageKey?: string\n}\n\nexport interface TokenManager {\n getAccessToken(): string | null\n setTokens(accessToken: string, expiresIn: number): void\n clear(): void\n scheduleRefresh(refreshFn: RefreshFn): void\n destroy(): void\n}\n\nexport function createTokenManager(options?: TokenManagerOptions): TokenManager {\n const storageMode = options?.storage ?? 'memory'\n const storageKey = options?.storageKey ?? ACCESS_TOKEN_KEY\n\n let memoryToken: string | null = null\n let refreshTimer: ReturnType<typeof setTimeout> | null = null\n let refreshFn: RefreshFn | null = null\n\n function getAccessToken(): string | null {\n if (storageMode === 'localStorage' && typeof globalThis.localStorage !== 'undefined') {\n return globalThis.localStorage.getItem(storageKey)\n }\n return memoryToken\n }\n\n function setAccessToken(token: string | null): void {\n memoryToken = token\n if (storageMode === 'localStorage' && typeof globalThis.localStorage !== 'undefined') {\n if (token) {\n globalThis.localStorage.setItem(storageKey, token)\n } else {\n globalThis.localStorage.removeItem(storageKey)\n }\n }\n }\n\n function setTokens(accessToken: string, expiresIn: number): void {\n setAccessToken(accessToken)\n markSession()\n scheduleAutoRefresh(expiresIn)\n }\n\n function clear(): void {\n setAccessToken(null)\n if (refreshTimer !== null) {\n clearTimeout(refreshTimer)\n refreshTimer = null\n }\n }\n\n function scheduleAutoRefresh(expiresIn: number): void {\n if (refreshTimer !== null) {\n clearTimeout(refreshTimer)\n }\n const delayMs = Math.max(1000, expiresIn * 0.8 * 1000)\n refreshTimer = setTimeout(async () => {\n if (!refreshFn) return\n try {\n const result = await refreshFn()\n if (result) setTokens(result.accessToken, result.expiresIn)\n } catch (err) {\n console.error('[TokenManager] auto-refresh failed, clearing session', err)\n clear()\n }\n }, delayMs)\n }\n\n function scheduleRefresh(fn: RefreshFn): void {\n refreshFn = fn\n }\n\n function destroy(): void {\n clear()\n refreshFn = null\n }\n\n return { getAccessToken, setTokens, clear, scheduleRefresh, destroy }\n}\n","import type { CommerceAdapter } from './adapter/commerce-adapter.js'\nimport { FetchClient } from './adapter/fetch-client.js'\nimport { createNymbalApiAdapter } from './adapter/nymbal-api-adapter.js'\nimport { adoptLegacyToken, hasSessionHint, renewSession } from './auth/session.js'\nimport { createTokenManager, type TokenManager } from './auth/token-manager.js'\nimport type { CartStore } from './store/cart-store.js'\nimport type { ProductStore } from './store/product-store.js'\nimport type { ProductListStore } from './store/product-list-store.js'\nimport type { SearchStore } from './store/search-store.js'\nimport type { CheckoutStore } from './store/checkout-store.js'\nimport type { AuthStore } from './store/auth-store.js'\nimport type { Store } from './store/create-store.js'\nimport type { CustomerAddress, ShopperOrder, StoreCapabilities } from '@nymbalhq/types'\nimport type { DenormalisedProduct, NewAddress, OrderPage } from './types.js'\n\nexport interface NymbalClientOptions {\n baseUrl: string\n adapter?: CommerceAdapter\n tokenStorage?: 'memory' | 'localStorage'\n credentials?: RequestCredentials\n getCartToken?: () => string | null\n}\n\n/** The signed-in customer's own account; every call sends the session's bearer token. */\nexport interface CustomerAccount {\n addresses: {\n list(): Promise<CustomerAddress[]>\n create(address: NewAddress): Promise<CustomerAddress>\n remove(id: string): Promise<void>\n }\n orders: {\n list(opts?: { cursor?: string }): Promise<OrderPage>\n /** `key`, the order's key from its checkout, reads it with no session; without one, only its owner can. */\n get(orderNumber: string, options?: { key?: string }): Promise<ShopperOrder>\n }\n}\n\n/**\n * A guest's orders, opened with the key from the checkout or the order email. Once the key has expired, the read is\n * refused with `orders.key_expired` and opens with the order's email beside the key; the email travels in a header,\n * never in a URL.\n */\nexport interface GuestOrders {\n get(orderNumber: string, options?: { key?: string; email?: string }): Promise<ShopperOrder>\n /** Asks the store to email the order's link to the order's own email; it answers the same whatever matched. */\n requestLink(request: { orderNumber: string; email: string }): Promise<void>\n}\n\n/** The store's capabilities report, read once per client: what it can do does not change while it runs. */\nexport interface StoreCapabilitiesReader {\n get(): Promise<StoreCapabilities>\n}\n\n/** The shopper's recently viewed products, kept by the store under the signed-in customer or the browser's visitor. */\nexport interface RecentlyViewed {\n /** Records a view of the product; the store ignores a product that is not active. */\n record(productId: string): Promise<void>\n /** The shopper's active products, newest first. */\n list(): Promise<{ items: DenormalisedProduct[] }>\n}\n\nexport interface NymbalClient {\n capabilities: StoreCapabilitiesReader\n recentlyViewed: RecentlyViewed\n cart: CartStore\n product: ProductStore\n productList: ProductListStore\n search: SearchStore\n checkout: CheckoutStore\n auth: AuthStore\n customer: CustomerAccount\n orders: GuestOrders\n adapter: CommerceAdapter\n destroy(): void\n}\n\n/** The client's store namespaces, each built on its first read by the factory its `@nymbalhq/sdk/ns/<module>` registers. */\nexport type StoreNamespace = 'cart' | 'product' | 'productList' | 'search' | 'checkout' | 'auth'\n\nconst STORE_NAMESPACES: StoreNamespace[] = [\n 'cart',\n 'product',\n 'productList',\n 'search',\n 'checkout',\n 'auth',\n]\n\n/** What a namespace's factory builds its store from: the client's adapter and token manager, and the client itself. */\nexport interface NamespaceContext {\n adapter: CommerceAdapter\n tokenManager: TokenManager\n client: NymbalClient\n /** The client's one session refresh, holding its other requests until it settles; it never throws. */\n restoreSession: () => Promise<void>\n}\n\ntype NamespaceFactory<K extends StoreNamespace> = (context: NamespaceContext) => NymbalClient[K]\n\ntype Factories = { [K in StoreNamespace]?: NamespaceFactory<K> }\n\n// On globalThis, like the payment drivers: a namespace module of one SDK copy registers for a client of another.\nconst FACTORIES_KEY = Symbol.for('nymbal.sdk.namespaces')\n\nfunction factories(): Factories {\n const holder = globalThis as Record<symbol, Factories | undefined>\n holder[FACTORIES_KEY] ??= {}\n return holder[FACTORIES_KEY]\n}\n\n/** Registers the factory of a store namespace; each `ns` module calls it on import. */\nexport function defineNamespace<K extends StoreNamespace>(\n name: K,\n factory: NamespaceFactory<K>,\n): void {\n factories()[name] = factory as Factories[K]\n}\n\n/** What `onStoreBuilt` hands its listener: any store, a hand-made client's included, reads and subscribes. */\ntype ObservedStore = Pick<Store<unknown>, 'getState' | 'subscribe'>\n\ntype ClientStore = ObservedStore & Pick<Store<unknown>, 'destroy'>\n\ntype StoreListener = (store: ObservedStore) => void\n\n// On a symbol, not in a module's registry, so a client made by another copy of the SDK still reports its stores.\nconst STORE_BUILT = Symbol.for('nymbal.client.onStoreBuilt')\n\ntype StoreBuiltHook = (listener: StoreListener) => () => void\n\nfunction isStore(value: unknown): value is ObservedStore {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as ObservedStore).subscribe === 'function' &&\n typeof (value as ObservedStore).getState === 'function'\n )\n}\n\n// On a symbol, as STORE_BUILT: the session's refresh is the client's, so a page starts it without the auth store.\nconst RESUME_SESSION = Symbol.for('nymbal.client.resumeSession')\n\ntype ResumeSessionHook = () => Promise<void>\n\n/**\n * `auth.restore()`'s refresh without the auth store, for a page that reads no profile: with the session hint and no\n * token held, the one refresh from the refresh cookie, which every other request the client sends meanwhile waits for.\n * A guest's sends nothing. An `auth.restore()` called while it is in flight shares it. Never throws.\n */\nexport function resumeSession(client: NymbalClient): Promise<void> {\n const hook = (client as unknown as Record<symbol, ResumeSessionHook | undefined>)[RESUME_SESSION]\n return typeof hook === 'function' ? hook() : Promise.resolve()\n}\n\n/**\n * Calls `listener` with each store namespace the client has built, and with each it builds later; returns the\n * unsubscribe. A client not made by `createNymbalClient` has its stores already: each is reported once, now.\n */\nexport function onStoreBuilt(client: NymbalClient, listener: StoreListener): () => void {\n const hook = (client as unknown as Record<symbol, StoreBuiltHook | undefined>)[STORE_BUILT]\n if (typeof hook === 'function') return hook(listener)\n for (const value of Object.values(client)) {\n if (isStore(value)) listener(value)\n }\n return () => {}\n}\n\nexport function createNymbalClient(options: NymbalClientOptions): NymbalClient {\n if (options.tokenStorage !== 'localStorage') adoptLegacyToken()\n const tokenManager = createTokenManager({\n ...(options.tokenStorage !== undefined && { storage: options.tokenStorage }),\n })\n\n let adapter: CommerceAdapter\n let fetchClient: FetchClient | undefined\n const renew = () => renewSession(adapter, tokenManager)\n\n if (options.adapter) {\n adapter = options.adapter\n } else {\n fetchClient = new FetchClient({\n baseUrl: options.baseUrl,\n ...(options.credentials !== undefined && { credentials: options.credentials }),\n getAccessToken: () => tokenManager.getAccessToken(),\n ...(options.getCartToken !== undefined && { getCartToken: options.getCartToken }),\n onUnauthorized: renew,\n })\n adapter = createNymbalApiAdapter(fetchClient)\n }\n\n const customer: CustomerAccount = {\n addresses: {\n list: () => adapter.customers.listAddresses(),\n create: (address) => adapter.customers.addAddress(address),\n remove: (id) => adapter.customers.removeAddress(id),\n },\n orders: {\n list: (opts) => adapter.customers.listOrders(opts),\n get: (orderNumber, options) => adapter.customers.getOrder(orderNumber, options),\n },\n }\n\n // A failed read is not kept, so the next get() asks again.\n let report: Promise<StoreCapabilities> | null = null\n const capabilities: StoreCapabilitiesReader = {\n get() {\n if (report === null) {\n const reader = adapter.capabilities\n report =\n reader === undefined\n ? Promise.reject(new Error('This client’s adapter cannot read the store capabilities.'))\n : reader.get()\n report.catch(() => {\n report = null\n })\n }\n return report\n },\n }\n\n // The adapter is the client's for its whole life, so whether it reads guests' orders is known here.\n const noGuestOrders = () =>\n Promise.reject(new Error('This client’s adapter cannot open a guest’s order.'))\n const orders: GuestOrders = adapter.orders ?? { get: noGuestOrders, requestLink: noGuestOrders }\n\n const recentlyViewedOf = (): NonNullable<CommerceAdapter['recentlyViewed']> => {\n const reader = adapter.recentlyViewed\n if (reader === undefined) {\n throw new Error('This client’s adapter cannot record or list recently viewed products.')\n }\n return reader\n }\n const recentlyViewed: RecentlyViewed = {\n async record(productId) {\n await recentlyViewedOf().record(productId)\n },\n async list() {\n return recentlyViewedOf().list()\n },\n }\n\n tokenManager.scheduleRefresh(() => fetchClient?.refreshSession() ?? renew())\n\n const built: { [K in StoreNamespace]?: ClientStore } = {}\n const listeners = new Set<StoreListener>()\n let destroyed = false\n\n function destroy(): void {\n destroyed = true\n for (const store of Object.values(built)) store.destroy()\n tokenManager.destroy()\n }\n\n const storeBuilt: StoreBuiltHook = (listener) => {\n for (const store of Object.values(built)) listener(store)\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n }\n const resume: ResumeSessionHook = () =>\n tokenManager.getAccessToken() || !hasSessionHint() ? Promise.resolve() : context.restoreSession()\n const client = Object.defineProperties(\n { capabilities, recentlyViewed, customer, orders, adapter, destroy },\n { [STORE_BUILT]: { value: storeBuilt }, [RESUME_SESSION]: { value: resume } },\n ) as NymbalClient\n const context: NamespaceContext = {\n adapter,\n tokenManager,\n client,\n restoreSession: () => fetchClient?.restoreSession() ?? renew(),\n }\n\n function build(name: StoreNamespace): ClientStore {\n // Building one now would leave a live store on a client whose destroy() has run.\n if (destroyed) throw new Error(`client.${name} was read after the client was destroyed`)\n const factory = factories()[name] as NamespaceFactory<StoreNamespace> | undefined\n if (factory === undefined) {\n const module = name === 'productList' ? 'product-list' : name\n throw new Error(`client.${name} is not in this client: import '@nymbalhq/sdk/ns/${module}'`)\n }\n const store = (built[name] = factory(context))\n for (const listener of listeners) listener(store)\n return store\n }\n\n // Defined properties are not enumerable: walking or spreading the client builds no store.\n for (const name of STORE_NAMESPACES) {\n Object.defineProperty(client, name, { get: () => built[name] ?? build(name) })\n }\n\n return client\n}\n"],"mappings":"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","names":["options"]}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-U6QF2DRY.js";
|
|
4
4
|
import {
|
|
5
5
|
defineNamespace
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-2EEZZXZX.js";
|
|
7
7
|
|
|
8
8
|
// src/store/cart-store.ts
|
|
9
9
|
function deriveItemCount(items) {
|
|
@@ -130,4 +130,4 @@ defineNamespace("cart", ({ adapter }) => createCartStore(adapter));
|
|
|
130
130
|
export {
|
|
131
131
|
createCartStore
|
|
132
132
|
};
|
|
133
|
-
//# sourceMappingURL=chunk-
|
|
133
|
+
//# sourceMappingURL=chunk-7LQGOJIT.js.map
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-U6QF2DRY.js";
|
|
4
4
|
import {
|
|
5
5
|
defineNamespace
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-2EEZZXZX.js";
|
|
7
7
|
|
|
8
8
|
// src/store/product-store.ts
|
|
9
9
|
var INITIAL_STATE = {
|
|
@@ -49,4 +49,4 @@ defineNamespace("product", ({ adapter }) => createProductStore(adapter));
|
|
|
49
49
|
export {
|
|
50
50
|
createProductStore
|
|
51
51
|
};
|
|
52
|
-
//# sourceMappingURL=chunk-
|
|
52
|
+
//# sourceMappingURL=chunk-CJVA46FR.js.map
|
|
@@ -2,8 +2,12 @@ import {
|
|
|
2
2
|
createStore
|
|
3
3
|
} from "./chunk-U6QF2DRY.js";
|
|
4
4
|
import {
|
|
5
|
-
defineNamespace
|
|
6
|
-
|
|
5
|
+
defineNamespace,
|
|
6
|
+
forgetSession,
|
|
7
|
+
hasSessionHint,
|
|
8
|
+
isSessionRefusal,
|
|
9
|
+
renewSession
|
|
10
|
+
} from "./chunk-2EEZZXZX.js";
|
|
7
11
|
|
|
8
12
|
// src/store/auth-store.ts
|
|
9
13
|
var INITIAL_STATE = {
|
|
@@ -12,7 +16,7 @@ var INITIAL_STATE = {
|
|
|
12
16
|
loading: false,
|
|
13
17
|
error: null
|
|
14
18
|
};
|
|
15
|
-
function createAuthStore(adapter, tokenManager) {
|
|
19
|
+
function createAuthStore(adapter, tokenManager, restoreSession = () => renewSession(adapter, tokenManager)) {
|
|
16
20
|
const store = createStore(INITIAL_STATE);
|
|
17
21
|
async function login(email, password) {
|
|
18
22
|
store.setState({ loading: true, error: null });
|
|
@@ -51,6 +55,7 @@ function createAuthStore(adapter, tokenManager) {
|
|
|
51
55
|
try {
|
|
52
56
|
await adapter.customers.logout();
|
|
53
57
|
tokenManager.clear();
|
|
58
|
+
forgetSession();
|
|
54
59
|
store.setState({
|
|
55
60
|
customer: null,
|
|
56
61
|
isAuthenticated: false,
|
|
@@ -58,6 +63,7 @@ function createAuthStore(adapter, tokenManager) {
|
|
|
58
63
|
});
|
|
59
64
|
} catch (err) {
|
|
60
65
|
tokenManager.clear();
|
|
66
|
+
forgetSession();
|
|
61
67
|
store.setState({
|
|
62
68
|
customer: null,
|
|
63
69
|
isAuthenticated: false,
|
|
@@ -73,6 +79,7 @@ function createAuthStore(adapter, tokenManager) {
|
|
|
73
79
|
} catch (err) {
|
|
74
80
|
console.error("[AuthStore] token refresh failed, logging out", err);
|
|
75
81
|
tokenManager.clear();
|
|
82
|
+
if (isSessionRefusal(err)) forgetSession();
|
|
76
83
|
store.setState({
|
|
77
84
|
customer: null,
|
|
78
85
|
isAuthenticated: false
|
|
@@ -108,6 +115,13 @@ function createAuthStore(adapter, tokenManager) {
|
|
|
108
115
|
});
|
|
109
116
|
}
|
|
110
117
|
}
|
|
118
|
+
async function restore() {
|
|
119
|
+
if (tokenManager.getAccessToken()) return loadProfile();
|
|
120
|
+
if (!hasSessionHint()) return;
|
|
121
|
+
await restoreSession();
|
|
122
|
+
if (tokenManager.getAccessToken()) return loadProfile();
|
|
123
|
+
store.setState({ customer: null, isAuthenticated: false });
|
|
124
|
+
}
|
|
111
125
|
return {
|
|
112
126
|
getState: store.getState,
|
|
113
127
|
subscribe: store.subscribe,
|
|
@@ -117,14 +131,18 @@ function createAuthStore(adapter, tokenManager) {
|
|
|
117
131
|
refreshToken,
|
|
118
132
|
updateProfile,
|
|
119
133
|
loadProfile,
|
|
134
|
+
restore,
|
|
120
135
|
destroy: store.destroy
|
|
121
136
|
};
|
|
122
137
|
}
|
|
123
138
|
|
|
124
139
|
// src/ns/auth.ts
|
|
125
|
-
defineNamespace(
|
|
140
|
+
defineNamespace(
|
|
141
|
+
"auth",
|
|
142
|
+
({ adapter, tokenManager, restoreSession }) => createAuthStore(adapter, tokenManager, restoreSession)
|
|
143
|
+
);
|
|
126
144
|
|
|
127
145
|
export {
|
|
128
146
|
createAuthStore
|
|
129
147
|
};
|
|
130
|
-
//# sourceMappingURL=chunk-
|
|
148
|
+
//# sourceMappingURL=chunk-PDZKR65O.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store/auth-store.ts","../src/ns/auth.ts"],"sourcesContent":["import type { Customer } from '@nymbalhq/types'\nimport type { CommerceAdapter } from '../adapter/commerce-adapter.js'\nimport { forgetSession, hasSessionHint, isSessionRefusal, renewSession } from '../auth/session.js'\nimport type { TokenManager } from '../auth/token-manager.js'\nimport type { AuthState, RegisterParams } from '../types.js'\nimport { createStore } from './create-store.js'\n\nconst INITIAL_STATE: AuthState = {\n customer: null,\n isAuthenticated: false,\n loading: false,\n error: null,\n}\n\nexport interface AuthStore {\n getState(): AuthState\n subscribe(listener: () => void): () => void\n login(email: string, password: string): Promise<void>\n register(params: RegisterParams): Promise<void>\n logout(): Promise<void>\n refreshToken(): Promise<void>\n updateProfile(updates: Partial<Pick<Customer, 'firstName' | 'lastName' | 'phone'>>): Promise<void>\n loadProfile(): Promise<void>\n /**\n * Brings a page load's session back: the profile with a token held; with none, one refresh from the refresh cookie\n * when this browser has signed in (the session hint), then the profile; a guest's sends nothing. Never throws.\n */\n restore(): Promise<void>\n destroy(): void\n}\n\nexport function createAuthStore(\n adapter: CommerceAdapter,\n tokenManager: TokenManager,\n restoreSession: () => Promise<void> = () => renewSession(adapter, tokenManager),\n): AuthStore {\n const store = createStore<AuthState>(INITIAL_STATE)\n\n async function login(email: string, password: string): Promise<void> {\n store.setState({ loading: true, error: null })\n try {\n const result = await adapter.customers.login(email, password)\n tokenManager.setTokens(result.accessToken, result.expiresIn)\n store.setState({\n customer: result.customer,\n isAuthenticated: true,\n loading: false,\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n store.setState({ loading: false, error: message })\n throw err\n }\n }\n\n async function register(params: RegisterParams): Promise<void> {\n store.setState({ loading: true, error: null })\n try {\n const result = await adapter.customers.register(params)\n tokenManager.setTokens(result.accessToken, result.expiresIn)\n store.setState({\n customer: result.customer,\n isAuthenticated: true,\n loading: false,\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n store.setState({ loading: false, error: message })\n throw err\n }\n }\n\n async function logout(): Promise<void> {\n store.setState({ loading: true, error: null })\n try {\n await adapter.customers.logout()\n tokenManager.clear()\n forgetSession()\n store.setState({\n customer: null,\n isAuthenticated: false,\n loading: false,\n })\n } catch (err) {\n tokenManager.clear()\n forgetSession()\n store.setState({\n customer: null,\n isAuthenticated: false,\n loading: false,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n async function refreshToken(): Promise<void> {\n try {\n const result = await adapter.customers.refresh()\n tokenManager.setTokens(result.accessToken, result.expiresIn)\n } catch (err) {\n console.error('[AuthStore] token refresh failed, logging out', err)\n tokenManager.clear()\n if (isSessionRefusal(err)) forgetSession()\n store.setState({\n customer: null,\n isAuthenticated: false,\n })\n }\n }\n\n async function updateProfile(\n updates: Partial<Pick<Customer, 'firstName' | 'lastName' | 'phone'>>,\n ): Promise<void> {\n store.setState({ loading: true, error: null })\n try {\n const customer = await adapter.customers.updateProfile(updates)\n store.setState({ customer, loading: false })\n } catch (err) {\n store.setState({\n loading: false,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n async function loadProfile(): Promise<void> {\n if (!tokenManager.getAccessToken()) return\n store.setState({ loading: true, error: null })\n try {\n const customer = await adapter.customers.getProfile()\n store.setState({\n customer,\n isAuthenticated: true,\n loading: false,\n })\n } catch (err) {\n store.setState({\n loading: false,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n async function restore(): Promise<void> {\n if (tokenManager.getAccessToken()) return loadProfile()\n if (!hasSessionHint()) return\n await restoreSession()\n if (tokenManager.getAccessToken()) return loadProfile()\n store.setState({ customer: null, isAuthenticated: false })\n }\n\n return {\n getState: store.getState,\n subscribe: store.subscribe,\n login,\n register,\n logout,\n refreshToken,\n updateProfile,\n loadProfile,\n restore,\n destroy: store.destroy,\n }\n}\n","import { defineNamespace } from '../client.js'\nimport { createAuthStore } from '../store/auth-store.js'\n\ndefineNamespace('auth', ({ adapter, tokenManager, restoreSession }) =>\n createAuthStore(adapter, tokenManager, restoreSession),\n)\n"],"mappings":"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","names":[]}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
NymbalApiError,
|
|
6
6
|
defineNamespace
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-2EEZZXZX.js";
|
|
8
8
|
|
|
9
9
|
// src/store/checkout-store.ts
|
|
10
10
|
var INITIAL_STATE = {
|
|
@@ -15,6 +15,7 @@ var INITIAL_STATE = {
|
|
|
15
15
|
shippingMethod: null,
|
|
16
16
|
paymentStatus: "idle",
|
|
17
17
|
error: null,
|
|
18
|
+
errorCode: null,
|
|
18
19
|
order: null,
|
|
19
20
|
placedOrderNumber: null,
|
|
20
21
|
placedOrderKey: null
|
|
@@ -45,12 +46,13 @@ function createCheckoutStore(adapter, cart) {
|
|
|
45
46
|
async function submitPayment() {
|
|
46
47
|
const state = store.getState();
|
|
47
48
|
if (!state.shippingAddress || !state.billingAddress) {
|
|
48
|
-
store.setState({ error: "Shipping and billing addresses are required" });
|
|
49
|
+
store.setState({ error: "Shipping and billing addresses are required", errorCode: null });
|
|
49
50
|
return null;
|
|
50
51
|
}
|
|
51
52
|
store.setState({
|
|
52
53
|
paymentStatus: "processing",
|
|
53
54
|
error: null,
|
|
55
|
+
errorCode: null,
|
|
54
56
|
placedOrderNumber: null,
|
|
55
57
|
placedOrderKey: null
|
|
56
58
|
});
|
|
@@ -78,6 +80,7 @@ function createCheckoutStore(adapter, cart) {
|
|
|
78
80
|
store.setState({
|
|
79
81
|
paymentStatus: "failed",
|
|
80
82
|
error: err instanceof Error ? err.message : String(err),
|
|
83
|
+
errorCode: err instanceof NymbalApiError ? err.code : null,
|
|
81
84
|
...placed
|
|
82
85
|
});
|
|
83
86
|
if (placed.placedOrderNumber !== null) await cart?.load();
|
|
@@ -110,4 +113,4 @@ defineNamespace("checkout", ({ adapter, client }) => createCheckoutStore(adapter
|
|
|
110
113
|
export {
|
|
111
114
|
createCheckoutStore
|
|
112
115
|
};
|
|
113
|
-
//# sourceMappingURL=chunk-
|
|
116
|
+
//# sourceMappingURL=chunk-VYX72KGY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store/checkout-store.ts","../src/ns/checkout.ts"],"sourcesContent":["import type { Address } from '@nymbalhq/types'\nimport type { CommerceAdapter } from '../adapter/commerce-adapter.js'\nimport { NymbalApiError } from '../adapter/fetch-client.js'\nimport type { CartStore } from './cart-store.js'\nimport type { CheckoutResult, CheckoutState, ShippingMethod } from '../types.js'\nimport { createStore } from './create-store.js'\n\nconst INITIAL_STATE: CheckoutState = {\n step: 'contact',\n email: '',\n shippingAddress: null,\n billingAddress: null,\n shippingMethod: null,\n paymentStatus: 'idle',\n error: null,\n errorCode: null,\n order: null,\n placedOrderNumber: null,\n placedOrderKey: null,\n}\n\n/**\n * The order a refused resubmit names (409 `checkout.cart_empty` or `checkout.payment_in_progress`), if any, and the key\n * the refusal carries to read it.\n */\nfunction namedOrder(err: unknown): Pick<CheckoutState, 'placedOrderNumber' | 'placedOrderKey'> {\n const context = err instanceof NymbalApiError ? err.context : undefined\n const orderNumber = context?.['orderNumber']\n const orderKey = context?.['orderKey']\n return typeof orderNumber === 'string'\n ? {\n placedOrderNumber: orderNumber,\n placedOrderKey: typeof orderKey === 'string' ? orderKey : null,\n }\n : { placedOrderNumber: null, placedOrderKey: null }\n}\n\nexport interface CheckoutStore {\n getState(): CheckoutState\n subscribe(listener: () => void): () => void\n setEmail(email: string): void\n setShippingAddress(address: Address): void\n setBillingAddress(address: Address): void\n setShippingMethod(method: ShippingMethod): void\n /** Places the order; resolves to the server's result, or null when there is none. */\n submitPayment(): Promise<CheckoutResult | null>\n /** The provider confirmed the payment on the client (Stripe's `confirmCardPayment`). */\n markConfirmed(): void\n reset(): void\n destroy(): void\n}\n\n/**\n * `cart`, when given, is loaded again once the server has answered a submit: placing an order changes the cart on the\n * server (emptied when confirmed, kept while awaiting payment), and whatever renders the cart store must show that.\n */\nexport function createCheckoutStore(adapter: CommerceAdapter, cart?: CartStore): CheckoutStore {\n const store = createStore<CheckoutState>(INITIAL_STATE)\n\n function setEmail(email: string): void {\n store.setState({ email, step: 'shipping' })\n }\n\n function setShippingAddress(address: Address): void {\n store.setState({ shippingAddress: address })\n }\n\n function setBillingAddress(address: Address): void {\n store.setState({ billingAddress: address })\n }\n\n function setShippingMethod(method: ShippingMethod): void {\n store.setState({ shippingMethod: method, step: 'payment' })\n }\n\n async function submitPayment(): Promise<CheckoutResult | null> {\n const state = store.getState()\n if (!state.shippingAddress || !state.billingAddress) {\n store.setState({ error: 'Shipping and billing addresses are required', errorCode: null })\n return null\n }\n store.setState({\n paymentStatus: 'processing',\n error: null,\n errorCode: null,\n placedOrderNumber: null,\n placedOrderKey: null,\n })\n try {\n const result = await adapter.checkout.create({\n email: state.email,\n billingAddress: state.billingAddress,\n shippingAddress: state.shippingAddress,\n })\n const { status, reason } = result.payment\n store.setState({\n paymentStatus: status,\n order: {\n orderId: result.orderId,\n orderNumber: result.orderNumber,\n paymentIntent: result.paymentIntent,\n },\n error:\n status === 'failed'\n ? `Your payment did not go through${reason ? ` (${reason})` : ''}. Please try again.`\n : null,\n ...(status === 'succeeded' && { step: 'confirmation' as const }),\n })\n await cart?.load()\n return result\n } catch (err) {\n const placed = namedOrder(err)\n store.setState({\n paymentStatus: 'failed',\n error: err instanceof Error ? err.message : String(err),\n errorCode: err instanceof NymbalApiError ? err.code : null,\n ...placed,\n })\n // A refusal naming an order: the cart became (or is becoming) that order on the server.\n if (placed.placedOrderNumber !== null) await cart?.load()\n return null\n }\n }\n\n function markConfirmed(): void {\n store.setState({ paymentStatus: 'succeeded', step: 'confirmation' })\n }\n\n function reset(): void {\n store.setState(INITIAL_STATE)\n }\n\n return {\n getState: store.getState,\n subscribe: store.subscribe,\n setEmail,\n setShippingAddress,\n setBillingAddress,\n setShippingMethod,\n submitPayment,\n markConfirmed,\n reset,\n destroy: store.destroy,\n }\n}\n","import './cart.js'\nimport { defineNamespace } from '../client.js'\nimport { createCheckoutStore } from '../store/checkout-store.js'\n\n// The checkout places its order on the client's own cart, so one cart serves both.\ndefineNamespace('checkout', ({ adapter, client }) => createCheckoutStore(adapter, client.cart))\n"],"mappings":"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","names":[]}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-U6QF2DRY.js";
|
|
4
4
|
import {
|
|
5
5
|
defineNamespace
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-2EEZZXZX.js";
|
|
7
7
|
|
|
8
8
|
// src/store/product-list-store.ts
|
|
9
9
|
import { DEFAULT_PRODUCT_SORT } from "@nymbalhq/types";
|
|
@@ -115,4 +115,4 @@ defineNamespace("productList", ({ adapter }) => createProductListStore(adapter))
|
|
|
115
115
|
export {
|
|
116
116
|
createProductListStore
|
|
117
117
|
};
|
|
118
|
-
//# sourceMappingURL=chunk-
|
|
118
|
+
//# sourceMappingURL=chunk-XS7RNJSY.js.map
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-U6QF2DRY.js";
|
|
4
4
|
import {
|
|
5
5
|
defineNamespace
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-2EEZZXZX.js";
|
|
7
7
|
|
|
8
8
|
// src/store/search-store.ts
|
|
9
9
|
var INITIAL_STATE = {
|
|
@@ -89,4 +89,4 @@ defineNamespace("search", ({ adapter }) => createSearchStore(adapter));
|
|
|
89
89
|
export {
|
|
90
90
|
createSearchStore
|
|
91
91
|
};
|
|
92
|
-
//# sourceMappingURL=chunk-
|
|
92
|
+
//# sourceMappingURL=chunk-YHXC7DS5.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Address, Order, VariantOption, CartItem, Facet, ProductSortKey, PaymentIntent, OrderStatus, CheckoutPayment, Customer, StoreCapabilities, ShopperOrder, Cart, CustomerAddress,
|
|
1
|
+
import { Address, Order, VariantOption, ProductRating, CartItem, Facet, ProductSortKey, PaymentIntent, OrderStatus, CheckoutPayment, Customer, StoreCapabilities, ShopperOrder, Cart, CustomerAddress, PublicReviewsResult, ReviewResult, AlertKind } from '@nymbalhq/types';
|
|
2
2
|
|
|
3
3
|
interface DenormalisedVariant {
|
|
4
4
|
id: string;
|
|
@@ -34,6 +34,8 @@ interface DenormalisedProduct {
|
|
|
34
34
|
}>;
|
|
35
35
|
createdAt: string;
|
|
36
36
|
updatedAt: string;
|
|
37
|
+
/** Served by the product routes, not stored: null when the product has no approved review. */
|
|
38
|
+
rating?: ProductRating | null;
|
|
37
39
|
}
|
|
38
40
|
interface CartState {
|
|
39
41
|
items: CartItem[];
|
|
@@ -92,6 +94,8 @@ interface CheckoutState {
|
|
|
92
94
|
/** What the server said about the payment; `succeeded` only once the order is paid. */
|
|
93
95
|
paymentStatus: 'idle' | 'processing' | 'requires_action' | 'succeeded' | 'failed';
|
|
94
96
|
error: string | null;
|
|
97
|
+
/** The API's code for `error` (`auth.token_invalid`, …), null when the error came from no API answer. */
|
|
98
|
+
errorCode: string | null;
|
|
95
99
|
order: {
|
|
96
100
|
orderId: string;
|
|
97
101
|
orderNumber: string;
|
|
@@ -236,7 +240,7 @@ interface CommerceAdapter {
|
|
|
236
240
|
getForProduct(productId: string, params?: {
|
|
237
241
|
limit?: number;
|
|
238
242
|
cursor?: string;
|
|
239
|
-
}): Promise<
|
|
243
|
+
}): Promise<PublicReviewsResult>;
|
|
240
244
|
submit(review: {
|
|
241
245
|
productId: string;
|
|
242
246
|
rating: number;
|
|
@@ -259,6 +263,11 @@ interface CommerceAdapter {
|
|
|
259
263
|
};
|
|
260
264
|
}
|
|
261
265
|
|
|
266
|
+
/** A refresh: its token for the manager to hold, or nothing when it has set (or cleared) the token itself. */
|
|
267
|
+
type RefreshFn = () => Promise<{
|
|
268
|
+
accessToken: string;
|
|
269
|
+
expiresIn: number;
|
|
270
|
+
} | void>;
|
|
262
271
|
interface TokenManagerOptions {
|
|
263
272
|
storage?: 'memory' | 'localStorage';
|
|
264
273
|
storageKey?: string;
|
|
@@ -267,10 +276,7 @@ interface TokenManager {
|
|
|
267
276
|
getAccessToken(): string | null;
|
|
268
277
|
setTokens(accessToken: string, expiresIn: number): void;
|
|
269
278
|
clear(): void;
|
|
270
|
-
scheduleRefresh(refreshFn:
|
|
271
|
-
accessToken: string;
|
|
272
|
-
expiresIn: number;
|
|
273
|
-
}>): void;
|
|
279
|
+
scheduleRefresh(refreshFn: RefreshFn): void;
|
|
274
280
|
destroy(): void;
|
|
275
281
|
}
|
|
276
282
|
declare function createTokenManager(options?: TokenManagerOptions): TokenManager;
|
|
@@ -353,9 +359,14 @@ interface AuthStore {
|
|
|
353
359
|
refreshToken(): Promise<void>;
|
|
354
360
|
updateProfile(updates: Partial<Pick<Customer, 'firstName' | 'lastName' | 'phone'>>): Promise<void>;
|
|
355
361
|
loadProfile(): Promise<void>;
|
|
362
|
+
/**
|
|
363
|
+
* Brings a page load's session back: the profile with a token held; with none, one refresh from the refresh cookie
|
|
364
|
+
* when this browser has signed in (the session hint), then the profile; a guest's sends nothing. Never throws.
|
|
365
|
+
*/
|
|
366
|
+
restore(): Promise<void>;
|
|
356
367
|
destroy(): void;
|
|
357
368
|
}
|
|
358
|
-
declare function createAuthStore(adapter: CommerceAdapter, tokenManager: TokenManager): AuthStore;
|
|
369
|
+
declare function createAuthStore(adapter: CommerceAdapter, tokenManager: TokenManager, restoreSession?: () => Promise<void>): AuthStore;
|
|
359
370
|
|
|
360
371
|
type Listener = () => void;
|
|
361
372
|
interface Store<TState> {
|
|
@@ -436,6 +447,12 @@ interface NymbalClient {
|
|
|
436
447
|
/** What `onStoreBuilt` hands its listener: any store, a hand-made client's included, reads and subscribes. */
|
|
437
448
|
type ObservedStore = Pick<Store<unknown>, 'getState' | 'subscribe'>;
|
|
438
449
|
type StoreListener = (store: ObservedStore) => void;
|
|
450
|
+
/**
|
|
451
|
+
* `auth.restore()`'s refresh without the auth store, for a page that reads no profile: with the session hint and no
|
|
452
|
+
* token held, the one refresh from the refresh cookie, which every other request the client sends meanwhile waits for.
|
|
453
|
+
* A guest's sends nothing. An `auth.restore()` called while it is in flight shares it. Never throws.
|
|
454
|
+
*/
|
|
455
|
+
declare function resumeSession(client: NymbalClient): Promise<void>;
|
|
439
456
|
/**
|
|
440
457
|
* Calls `listener` with each store namespace the client has built, and with each it builds later; returns the
|
|
441
458
|
* unsubscribe. A client not made by `createNymbalClient` has its stores already: each is reported once, now.
|
|
@@ -469,6 +486,12 @@ interface PaymentDriver {
|
|
|
469
486
|
declare function registerPaymentDriver(name: string, driver: PaymentDriver): void;
|
|
470
487
|
declare function getPaymentDriver(name: string): PaymentDriver | undefined;
|
|
471
488
|
|
|
489
|
+
/**
|
|
490
|
+
* Set in `localStorage` while the browser holds a session the refresh cookie can bring back. It holds no token: a page
|
|
491
|
+
* load with no hint is a guest's, and restores nothing.
|
|
492
|
+
*/
|
|
493
|
+
declare const SESSION_HINT_KEY = "nymbal.auth.session";
|
|
494
|
+
|
|
472
495
|
declare class NymbalApiError extends Error {
|
|
473
496
|
readonly code: string;
|
|
474
497
|
readonly status: number;
|
|
@@ -491,6 +514,11 @@ interface RequestOptions {
|
|
|
491
514
|
* 10 s with the original 401.
|
|
492
515
|
*/
|
|
493
516
|
authRefresh?: boolean;
|
|
517
|
+
/**
|
|
518
|
+
* Sent with a customer's bearer, never sent again without one: when the 401's refresh leaves no token, the 401 is
|
|
519
|
+
* thrown. For a request the API would otherwise answer as a guest (a checkout begin).
|
|
520
|
+
*/
|
|
521
|
+
keepIdentity?: boolean;
|
|
494
522
|
}
|
|
495
523
|
declare class FetchClient {
|
|
496
524
|
private readonly baseUrl;
|
|
@@ -502,7 +530,16 @@ declare class FetchClient {
|
|
|
502
530
|
private refreshing;
|
|
503
531
|
/** The token `GET /api/csrf` answered: the API's `nymbal.csrf` cookie, which this origin may not be able to read. */
|
|
504
532
|
private csrfToken;
|
|
533
|
+
/** A session restore's refresh, which every request sent meanwhile waits on so that none goes out as a guest. */
|
|
534
|
+
private held;
|
|
505
535
|
constructor(options: FetchClientOptions);
|
|
536
|
+
/** Starts the session's refresh, or joins the one in flight: a client sends one refresh at a time. */
|
|
537
|
+
refreshSession(): Promise<void>;
|
|
538
|
+
/**
|
|
539
|
+
* As `refreshSession`, and every other request sent until the refresh settles waits for it, at most
|
|
540
|
+
* `REFRESH_WAIT_MS`. Held from this call on, before any await: a page's first requests go in the same tick.
|
|
541
|
+
*/
|
|
542
|
+
restoreSession(): Promise<void>;
|
|
506
543
|
request<T>(method: string, path: string, body?: unknown, options?: RequestOptions): Promise<T>;
|
|
507
544
|
/** Like `request`, keeping the envelope's `meta` (pagination cursors live there). */
|
|
508
545
|
requestWithMeta<T>(method: string, path: string, body?: unknown, options?: RequestOptions): Promise<{
|
|
@@ -519,5 +556,5 @@ declare class FetchClient {
|
|
|
519
556
|
private parseResponse;
|
|
520
557
|
}
|
|
521
558
|
|
|
522
|
-
export {
|
|
523
|
-
export type { AuthResult as A,
|
|
559
|
+
export { getPaymentDriver as $, FetchClient as F, createAuthStore as M, createCartStore as Q, SESSION_HINT_KEY as S, createCheckoutStore as U, createNymbalClient as V, createProductListStore as W, createProductStore as X, createSearchStore as Y, createStore as Z, createTokenManager as _, registerPaymentDriver as a0, onStoreBuilt as a1, resumeSession as a2, NymbalApiError as m };
|
|
560
|
+
export type { AuthResult as A, SearchState as B, CommerceAdapter as C, DenormalisedProduct as D, SearchStore as E, GuestOrders as G, ShippingMethod as H, Store as I, StoreCapabilitiesReader as J, TokenManagerOptions as K, Listener as L, NewAddress as N, OrderPage as O, PaymentConfirmContext as P, RecentlyViewed as R, TokenManager as T, AuthState as a, AuthStore as b, CartState as c, CartStore as d, CheckoutDetails as e, CheckoutResult as f, CheckoutState as g, CheckoutStep as h, CheckoutStore as i, CustomerAccount as j, DenormalisedVariant as k, FetchClientOptions as l, NymbalClient as n, NymbalClientOptions as o, PaymentConfirmation as p, PaymentDriver as q, ProductListParams as r, ProductListResult as s, ProductListState as t, ProductListStore as u, ProductSearchParams as v, ProductSearchResult as w, ProductState as x, ProductStore as y, RegisterParams as z };
|
package/dist/core.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { j as CustomerAccount, G as GuestOrders, m as NymbalApiError, n as NymbalClient, o as NymbalClientOptions, P as PaymentConfirmContext, p as PaymentConfirmation, q as PaymentDriver, R as RecentlyViewed,
|
|
1
|
+
export { j as CustomerAccount, G as GuestOrders, m as NymbalApiError, n as NymbalClient, o as NymbalClientOptions, P as PaymentConfirmContext, p as PaymentConfirmation, q as PaymentDriver, R as RecentlyViewed, S as SESSION_HINT_KEY, J as StoreCapabilitiesReader, V as createNymbalClient, $ as getPaymentDriver, a1 as onStoreBuilt, a0 as registerPaymentDriver, a2 as resumeSession } from './core-BviXjOsX.js';
|
|
2
2
|
import '@nymbalhq/types';
|
package/dist/core.js
CHANGED
|
@@ -4,14 +4,18 @@ import {
|
|
|
4
4
|
} from "./chunk-4GP43MKT.js";
|
|
5
5
|
import {
|
|
6
6
|
NymbalApiError,
|
|
7
|
+
SESSION_HINT_KEY,
|
|
7
8
|
createNymbalClient,
|
|
8
|
-
onStoreBuilt
|
|
9
|
-
|
|
9
|
+
onStoreBuilt,
|
|
10
|
+
resumeSession
|
|
11
|
+
} from "./chunk-2EEZZXZX.js";
|
|
10
12
|
export {
|
|
11
13
|
NymbalApiError,
|
|
14
|
+
SESSION_HINT_KEY,
|
|
12
15
|
createNymbalClient,
|
|
13
16
|
getPaymentDriver,
|
|
14
17
|
onStoreBuilt,
|
|
15
|
-
registerPaymentDriver
|
|
18
|
+
registerPaymentDriver,
|
|
19
|
+
resumeSession
|
|
16
20
|
};
|
|
17
21
|
//# sourceMappingURL=core.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { F as FetchClient, C as CommerceAdapter } from './core-
|
|
2
|
-
export { A as AuthResult, a as AuthState, b as AuthStore, c as CartState, d as CartStore, e as CheckoutDetails, f as CheckoutResult, g as CheckoutState, h as CheckoutStep, i as CheckoutStore, j as CustomerAccount, D as DenormalisedProduct, k as DenormalisedVariant, l as FetchClientOptions, G as GuestOrders, L as Listener, N as NewAddress, m as NymbalApiError, n as NymbalClient, o as NymbalClientOptions, O as OrderPage, P as PaymentConfirmContext, p as PaymentConfirmation, q as PaymentDriver, r as ProductListParams, s as ProductListResult, t as ProductListState, u as ProductListStore, v as ProductSearchParams, w as ProductSearchResult, x as ProductState, y as ProductStore, R as RecentlyViewed, z as RegisterParams, S as
|
|
3
|
-
export { AlertKind, PaymentClient, ProductSortKey, ReviewResult as ReviewSubmitResult,
|
|
1
|
+
import { F as FetchClient, C as CommerceAdapter } from './core-BviXjOsX.js';
|
|
2
|
+
export { A as AuthResult, a as AuthState, b as AuthStore, c as CartState, d as CartStore, e as CheckoutDetails, f as CheckoutResult, g as CheckoutState, h as CheckoutStep, i as CheckoutStore, j as CustomerAccount, D as DenormalisedProduct, k as DenormalisedVariant, l as FetchClientOptions, G as GuestOrders, L as Listener, N as NewAddress, m as NymbalApiError, n as NymbalClient, o as NymbalClientOptions, O as OrderPage, P as PaymentConfirmContext, p as PaymentConfirmation, q as PaymentDriver, r as ProductListParams, s as ProductListResult, t as ProductListState, u as ProductListStore, v as ProductSearchParams, w as ProductSearchResult, x as ProductState, y as ProductStore, R as RecentlyViewed, z as RegisterParams, S as SESSION_HINT_KEY, B as SearchState, E as SearchStore, H as ShippingMethod, I as Store, J as StoreCapabilitiesReader, T as TokenManager, K as TokenManagerOptions, M as createAuthStore, Q as createCartStore, U as createCheckoutStore, V as createNymbalClient, W as createProductListStore, X as createProductStore, Y as createSearchStore, Z as createStore, _ as createTokenManager, $ as getPaymentDriver, a0 as registerPaymentDriver } from './core-BviXjOsX.js';
|
|
3
|
+
export { AlertKind, PaymentClient, ProductRating, ProductSortKey, PublicReview, ReviewResult as ReviewSubmitResult, PublicReviewsResult as ReviewsListResult, StoreCapabilities } from '@nymbalhq/types';
|
|
4
4
|
|
|
5
5
|
declare function createNymbalApiAdapter(fetchClient: FetchClient): CommerceAdapter;
|
|
6
6
|
|
package/dist/index.js
CHANGED
|
@@ -4,35 +4,37 @@ import {
|
|
|
4
4
|
} from "./chunk-4GP43MKT.js";
|
|
5
5
|
import {
|
|
6
6
|
createProductStore
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-CJVA46FR.js";
|
|
8
8
|
import {
|
|
9
9
|
createProductListStore
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-XS7RNJSY.js";
|
|
11
11
|
import {
|
|
12
12
|
createSearchStore
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-YHXC7DS5.js";
|
|
14
14
|
import {
|
|
15
15
|
createCheckoutStore
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-VYX72KGY.js";
|
|
17
17
|
import {
|
|
18
18
|
createCartStore
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-7LQGOJIT.js";
|
|
20
20
|
import {
|
|
21
21
|
createAuthStore
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-PDZKR65O.js";
|
|
23
23
|
import {
|
|
24
24
|
createStore
|
|
25
25
|
} from "./chunk-U6QF2DRY.js";
|
|
26
26
|
import {
|
|
27
27
|
FetchClient,
|
|
28
28
|
NymbalApiError,
|
|
29
|
+
SESSION_HINT_KEY,
|
|
29
30
|
createNymbalApiAdapter,
|
|
30
31
|
createNymbalClient,
|
|
31
32
|
createTokenManager
|
|
32
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-2EEZZXZX.js";
|
|
33
34
|
export {
|
|
34
35
|
FetchClient,
|
|
35
36
|
NymbalApiError,
|
|
37
|
+
SESSION_HINT_KEY,
|
|
36
38
|
createAuthStore,
|
|
37
39
|
createCartStore,
|
|
38
40
|
createCheckoutStore,
|
package/dist/ns/auth.js
CHANGED
package/dist/ns/cart.js
CHANGED
package/dist/ns/checkout.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "../chunk-
|
|
2
|
-
import "../chunk-
|
|
1
|
+
import "../chunk-VYX72KGY.js";
|
|
2
|
+
import "../chunk-7LQGOJIT.js";
|
|
3
3
|
import "../chunk-U6QF2DRY.js";
|
|
4
|
-
import "../chunk-
|
|
4
|
+
import "../chunk-2EEZZXZX.js";
|
|
5
5
|
//# sourceMappingURL=checkout.js.map
|
package/dist/ns/product-list.js
CHANGED
package/dist/ns/product.js
CHANGED
package/dist/ns/search.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nymbalhq/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Framework-agnostic commerce SDK with pub/sub stores and adapter interface",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"dist"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@nymbalhq/types": "1.
|
|
45
|
+
"@nymbalhq/types": "1.7.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/store/checkout-store.ts","../src/ns/checkout.ts"],"sourcesContent":["import type { Address } from '@nymbalhq/types'\nimport type { CommerceAdapter } from '../adapter/commerce-adapter.js'\nimport { NymbalApiError } from '../adapter/fetch-client.js'\nimport type { CartStore } from './cart-store.js'\nimport type { CheckoutResult, CheckoutState, ShippingMethod } from '../types.js'\nimport { createStore } from './create-store.js'\n\nconst INITIAL_STATE: CheckoutState = {\n step: 'contact',\n email: '',\n shippingAddress: null,\n billingAddress: null,\n shippingMethod: null,\n paymentStatus: 'idle',\n error: null,\n order: null,\n placedOrderNumber: null,\n placedOrderKey: null,\n}\n\n/**\n * The order a refused resubmit names (409 `checkout.cart_empty` or `checkout.payment_in_progress`), if any, and the key\n * the refusal carries to read it.\n */\nfunction namedOrder(err: unknown): Pick<CheckoutState, 'placedOrderNumber' | 'placedOrderKey'> {\n const context = err instanceof NymbalApiError ? err.context : undefined\n const orderNumber = context?.['orderNumber']\n const orderKey = context?.['orderKey']\n return typeof orderNumber === 'string'\n ? {\n placedOrderNumber: orderNumber,\n placedOrderKey: typeof orderKey === 'string' ? orderKey : null,\n }\n : { placedOrderNumber: null, placedOrderKey: null }\n}\n\nexport interface CheckoutStore {\n getState(): CheckoutState\n subscribe(listener: () => void): () => void\n setEmail(email: string): void\n setShippingAddress(address: Address): void\n setBillingAddress(address: Address): void\n setShippingMethod(method: ShippingMethod): void\n /** Places the order; resolves to the server's result, or null when there is none. */\n submitPayment(): Promise<CheckoutResult | null>\n /** The provider confirmed the payment on the client (Stripe's `confirmCardPayment`). */\n markConfirmed(): void\n reset(): void\n destroy(): void\n}\n\n/**\n * `cart`, when given, is loaded again once the server has answered a submit: placing an order changes the cart on the\n * server (emptied when confirmed, kept while awaiting payment), and whatever renders the cart store must show that.\n */\nexport function createCheckoutStore(adapter: CommerceAdapter, cart?: CartStore): CheckoutStore {\n const store = createStore<CheckoutState>(INITIAL_STATE)\n\n function setEmail(email: string): void {\n store.setState({ email, step: 'shipping' })\n }\n\n function setShippingAddress(address: Address): void {\n store.setState({ shippingAddress: address })\n }\n\n function setBillingAddress(address: Address): void {\n store.setState({ billingAddress: address })\n }\n\n function setShippingMethod(method: ShippingMethod): void {\n store.setState({ shippingMethod: method, step: 'payment' })\n }\n\n async function submitPayment(): Promise<CheckoutResult | null> {\n const state = store.getState()\n if (!state.shippingAddress || !state.billingAddress) {\n store.setState({ error: 'Shipping and billing addresses are required' })\n return null\n }\n store.setState({\n paymentStatus: 'processing',\n error: null,\n placedOrderNumber: null,\n placedOrderKey: null,\n })\n try {\n const result = await adapter.checkout.create({\n email: state.email,\n billingAddress: state.billingAddress,\n shippingAddress: state.shippingAddress,\n })\n const { status, reason } = result.payment\n store.setState({\n paymentStatus: status,\n order: {\n orderId: result.orderId,\n orderNumber: result.orderNumber,\n paymentIntent: result.paymentIntent,\n },\n error:\n status === 'failed'\n ? `Your payment did not go through${reason ? ` (${reason})` : ''}. Please try again.`\n : null,\n ...(status === 'succeeded' && { step: 'confirmation' as const }),\n })\n await cart?.load()\n return result\n } catch (err) {\n const placed = namedOrder(err)\n store.setState({\n paymentStatus: 'failed',\n error: err instanceof Error ? err.message : String(err),\n ...placed,\n })\n // A refusal naming an order: the cart became (or is becoming) that order on the server.\n if (placed.placedOrderNumber !== null) await cart?.load()\n return null\n }\n }\n\n function markConfirmed(): void {\n store.setState({ paymentStatus: 'succeeded', step: 'confirmation' })\n }\n\n function reset(): void {\n store.setState(INITIAL_STATE)\n }\n\n return {\n getState: store.getState,\n subscribe: store.subscribe,\n setEmail,\n setShippingAddress,\n setBillingAddress,\n setShippingMethod,\n submitPayment,\n markConfirmed,\n reset,\n destroy: store.destroy,\n }\n}\n","import './cart.js'\nimport { defineNamespace } from '../client.js'\nimport { createCheckoutStore } from '../store/checkout-store.js'\n\n// The checkout places its order on the client's own cart, so one cart serves both.\ndefineNamespace('checkout', ({ adapter, client }) => createCheckoutStore(adapter, client.cart))\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapter/fetch-client.ts","../src/adapter/nymbal-api-adapter.ts","../src/auth/token-manager.ts","../src/client.ts"],"sourcesContent":["export class NymbalApiError extends Error {\n constructor(\n public readonly code: string,\n message: string,\n public readonly status: number,\n public readonly context?: Record<string, unknown>,\n ) {\n super(message)\n this.name = 'NymbalApiError'\n }\n}\n\nexport interface FetchClientOptions {\n baseUrl: string\n credentials?: RequestCredentials\n getAccessToken?: () => string | null\n getCartToken?: () => string | null\n onUnauthorized?: () => Promise<void>\n}\n\n/** Per request: headers sent beside the client's own. */\nexport interface RequestOptions {\n headers?: Record<string, string>\n /**\n * The token refresh itself (what `onUnauthorized` sends through this client): its 401 is thrown, never waited on or\n * sent again, so a refused refresh cannot wait on itself. An unmarked one that is refused is cut off after\n * 10 s with the original 401.\n */\n authRefresh?: boolean\n}\n\n/**\n * The longest a refused request waits on a refresh before its 401 is thrown: far beyond one healthy refresh round trip,\n * and the bound on a refresh that, unmarked, waits on itself.\n */\nconst REFRESH_WAIT_MS = 10_000\n\nconst CSRF_HEADER = 'x-csrf-token'\nconst UNSAFE_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\ninterface Envelope<T> {\n data?: T\n meta?: Record<string, unknown>\n error?: { code: string; message: string; context?: Record<string, unknown> }\n}\n\nexport class FetchClient {\n private readonly baseUrl: string\n private readonly credentials: RequestCredentials\n private readonly getAccessToken: () => string | null\n private readonly getCartToken: (() => string | null) | undefined\n private readonly onUnauthorized: (() => Promise<void>) | undefined\n /** The refresh a refused request started, which every request refused meanwhile waits on rather than its own. */\n private refreshing: Promise<void> | null = null\n /** The token `GET /api/csrf` answered: the API's `nymbal.csrf` cookie, which this origin may not be able to read. */\n private csrfToken: Promise<string> | null = null\n\n constructor(options: FetchClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/+$/, '')\n this.credentials = options.credentials ?? 'include'\n this.getAccessToken = options.getAccessToken ?? (() => null)\n this.getCartToken = options.getCartToken\n this.onUnauthorized = options.onUnauthorized\n }\n\n async request<T>(\n method: string,\n path: string,\n body?: unknown,\n options: RequestOptions = {},\n ): Promise<T> {\n return (await this.requestWithMeta<T>(method, path, body, options)).data\n }\n\n /** Like `request`, keeping the envelope's `meta` (pagination cursors live there). */\n async requestWithMeta<T>(\n method: string,\n path: string,\n body?: unknown,\n options: RequestOptions = {},\n ): Promise<{ data: T; meta: Record<string, unknown> }> {\n // Read before the first await: the refresh, if any, in flight when this request was sent.\n const sentDuring = this.refreshing\n let { response, bearer } = await this.doFetch(method, path, body, options)\n\n // A stale token (the cookie was replaced or expired) is fetched again once; a second refusal is thrown.\n if (await this.isCsrfRejection(response)) {\n this.csrfToken = null\n ;({ response, bearer } = await this.doFetch(method, path, body, options))\n }\n\n if (response.status === 401 && this.onUnauthorized && options.authRefresh !== true) {\n const pending = this.refreshing\n const retry = async () =>\n this.parseResponse<T>((await this.doFetch(method, path, body, options)).response)\n if (pending === null && this.getAccessToken() !== bearer) {\n // A refresh that ended after this was sent has replaced its token already.\n return retry()\n }\n if (pending !== null && pending === sentDuring) {\n // Sent during the refresh with the token it replaces: sent again once it has, and only if it did.\n if ((await this.waitFor(pending)) && this.getAccessToken() !== bearer) return retry()\n } else if (await this.waitFor(pending ?? this.refresh(this.onUnauthorized))) {\n // Every request refused with the old token waits on the one refresh and is sent again once.\n return retry()\n }\n }\n\n return this.parseResponse<T>(response)\n }\n\n /** Whether the refresh ended within `REFRESH_WAIT_MS`; a refresh that fails throws here, as it always has. */\n private async waitFor(refresh: Promise<void>): Promise<boolean> {\n let timer: ReturnType<typeof setTimeout> | undefined\n const bound = new Promise<false>((resolve) => {\n timer = setTimeout(() => resolve(false), REFRESH_WAIT_MS)\n })\n try {\n return await Promise.race([refresh.then(() => true as const), bound])\n } finally {\n clearTimeout(timer)\n }\n }\n\n private refresh(onUnauthorized: () => Promise<void>): Promise<void> {\n const refresh = Promise.resolve()\n .then(onUnauthorized)\n .finally(() => {\n this.refreshing = null\n })\n this.refreshing = refresh\n return refresh\n }\n\n private csrf(): Promise<string> {\n this.csrfToken ??= this.fetchCsrfToken().catch((err: unknown) => {\n this.csrfToken = null\n throw err\n })\n return this.csrfToken\n }\n\n private async fetchCsrfToken(): Promise<string> {\n const response = await globalThis.fetch(`${this.baseUrl}/api/csrf`, {\n method: 'GET',\n credentials: this.credentials,\n })\n const { data } = await this.parseResponse<{ token: string }>(response)\n return data.token\n }\n\n private async isCsrfRejection(response: Response): Promise<boolean> {\n if (response.status !== 403) return false\n const text = await response.clone().text()\n try {\n return (JSON.parse(text) as Envelope<unknown>).error?.code === 'csrf.invalid'\n } catch {\n return false\n }\n }\n\n private async doFetch(\n method: string,\n path: string,\n body: unknown,\n options: RequestOptions,\n ): Promise<{ response: Response; bearer: string | null }> {\n const headers: Record<string, string> = { ...options.headers }\n const cookies: string[] = []\n\n if (UNSAFE_METHODS.has(method.toUpperCase())) {\n const csrf = await this.csrf()\n headers[CSRF_HEADER] = csrf\n // Outside a browser there is no cookie jar to send the pair's cookie half; a browser drops this header.\n cookies.push(`nymbal.csrf=${csrf}`)\n }\n\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json'\n }\n\n const token = this.getAccessToken()\n if (token) {\n headers['Authorization'] = `Bearer ${token}`\n }\n\n const cartToken = this.getCartToken?.()\n if (cartToken) {\n cookies.unshift(`nymbal.cart=${cartToken}`)\n }\n if (cookies.length > 0) {\n headers['Cookie'] = cookies.join('; ')\n }\n\n const response = await globalThis.fetch(`${this.baseUrl}${path}`, {\n method,\n headers,\n credentials: this.credentials,\n ...(body !== undefined && { body: JSON.stringify(body) }),\n })\n return { response, bearer: token }\n }\n\n private async parseResponse<T>(\n response: Response,\n ): Promise<{ data: T; meta: Record<string, unknown> }> {\n const text = await response.text()\n if (!text) {\n return { data: undefined as T, meta: {} }\n }\n\n const envelope = JSON.parse(text) as Envelope<T>\n\n if (envelope.error) {\n throw new NymbalApiError(\n envelope.error.code,\n envelope.error.message,\n response.status,\n envelope.error.context,\n )\n }\n\n return { data: envelope.data as T, meta: envelope.meta ?? {} }\n }\n}\n","import {\n ORDER_EMAIL_HEADER,\n ORDER_KEY_HEADER,\n type AlertKind,\n type Cart,\n type Customer,\n type CustomerAddress,\n type Order,\n type ReviewsResult,\n type ReviewResult,\n type ShopperOrder,\n type StoreCapabilities,\n} from '@nymbalhq/types'\nimport type { CommerceAdapter } from './commerce-adapter.js'\nimport type { FetchClient } from './fetch-client.js'\nimport type {\n DenormalisedProduct,\n ProductListParams,\n ProductListResult,\n ProductSearchParams,\n ProductSearchResult,\n CheckoutDetails,\n CheckoutResult,\n RegisterParams,\n AuthResult,\n NewAddress,\n OrderPage,\n} from '../types.js'\n\nexport function createNymbalApiAdapter(fetchClient: FetchClient): CommerceAdapter {\n // One read for a signed-in owner's order and a guest's: the key and the email, when given, go in headers.\n const getOrder = (orderNumber: string, { key, email }: { key?: string; email?: string } = {}) =>\n fetchClient.request<ShopperOrder>(\n 'GET',\n `/api/orders/${encodeURIComponent(orderNumber)}`,\n undefined,\n {\n headers: {\n ...(key !== undefined && { [ORDER_KEY_HEADER]: key }),\n ...(email !== undefined && { [ORDER_EMAIL_HEADER]: email }),\n },\n },\n )\n\n return {\n capabilities: {\n get() {\n return fetchClient.request<StoreCapabilities>('GET', '/api/capabilities')\n },\n },\n\n cart: {\n get() {\n return fetchClient.request<Cart>('GET', '/api/cart')\n },\n addItem(variantId: string, qty: number) {\n return fetchClient.request<Cart>('POST', '/api/cart/items', { variantId, qty })\n },\n removeItem(variantId: string) {\n return fetchClient.request<Cart>(\n 'DELETE',\n `/api/cart/items/${encodeURIComponent(variantId)}`,\n )\n },\n updateQuantity(variantId: string, qty: number) {\n return fetchClient.request<Cart>(\n 'PATCH',\n `/api/cart/items/${encodeURIComponent(variantId)}`,\n { qty },\n )\n },\n clear() {\n return fetchClient.request<Cart>('DELETE', '/api/cart')\n },\n },\n\n products: {\n getBySlug(slug: string) {\n return fetchClient.request<DenormalisedProduct>(\n 'GET',\n `/api/products/${encodeURIComponent(slug)}`,\n )\n },\n list(params?: ProductListParams) {\n const search = new URLSearchParams()\n if (params?.limit !== undefined) search.set('limit', String(params.limit))\n if (params?.cursor !== undefined) search.set('cursor', params.cursor)\n if (params?.category !== undefined) search.set('category', params.category)\n if (params?.sort !== undefined) search.set('sort', params.sort)\n const qs = search.toString()\n return fetchClient.request<ProductListResult>('GET', `/api/products${qs ? `?${qs}` : ''}`)\n },\n search(query: string, params?: ProductSearchParams) {\n const search = new URLSearchParams()\n search.set('q', query)\n if (params?.limit !== undefined) search.set('limit', String(params.limit))\n if (params?.cursor !== undefined) search.set('cursor', params.cursor)\n if (params?.category !== undefined) search.set('category', params.category)\n return fetchClient.request<ProductSearchResult>(\n 'GET',\n `/api/products/search?${search.toString()}`,\n )\n },\n },\n\n checkout: {\n create(details: CheckoutDetails) {\n return fetchClient.request<CheckoutResult>('POST', '/api/checkout', details)\n },\n },\n\n customers: {\n register(params: RegisterParams) {\n return fetchClient.request<AuthResult>('POST', '/api/auth/register', params)\n },\n login(email: string, password: string) {\n return fetchClient.request<AuthResult>('POST', '/api/auth/login', { email, password })\n },\n async logout() {\n await fetchClient.request<{ ok: boolean }>('POST', '/api/auth/logout')\n },\n refresh() {\n return fetchClient.request<{ accessToken: string; expiresIn: number }>(\n 'POST',\n '/api/auth/refresh',\n undefined,\n { authRefresh: true },\n )\n },\n getProfile() {\n return fetchClient.request<Customer>('GET', '/api/customers/me')\n },\n updateProfile(updates: Partial<Pick<Customer, 'firstName' | 'lastName' | 'phone'>>) {\n return fetchClient.request<Customer>('PATCH', '/api/customers/me', updates)\n },\n listAddresses() {\n return fetchClient.request<CustomerAddress[]>('GET', '/api/customers/me/addresses')\n },\n addAddress(address: NewAddress) {\n return fetchClient.request<CustomerAddress>('POST', '/api/customers/me/addresses', address)\n },\n async removeAddress(id: string) {\n await fetchClient.request<{ ok: boolean }>(\n 'DELETE',\n `/api/customers/me/addresses/${encodeURIComponent(id)}`,\n )\n },\n async listOrders(params?: { cursor?: string }): Promise<OrderPage> {\n const qs =\n params?.cursor !== undefined ? `?cursor=${encodeURIComponent(params.cursor)}` : ''\n const { data, meta } = await fetchClient.requestWithMeta<Order[]>('GET', `/api/orders${qs}`)\n const nextCursor = typeof meta.nextCursor === 'string' ? meta.nextCursor : null\n return { items: data ?? [], nextCursor }\n },\n getOrder,\n },\n\n orders: {\n get: getOrder,\n async requestLink({ orderNumber, email }: { orderNumber: string; email: string }) {\n await fetchClient.request<{ sent: string }>('POST', '/api/orders/link', {\n orderNumber,\n email,\n })\n },\n },\n\n recentlyViewed: {\n async record(productId: string) {\n await fetchClient.request<void>('POST', '/api/recently-viewed', { productId })\n },\n list() {\n return fetchClient.request<{ items: DenormalisedProduct[] }>('GET', '/api/recently-viewed')\n },\n },\n\n reviews: {\n getForProduct(productId: string, params?: { limit?: number; cursor?: string }) {\n const search = new URLSearchParams()\n if (params?.limit !== undefined) search.set('limit', String(params.limit))\n if (params?.cursor !== undefined) search.set('cursor', params.cursor)\n const qs = search.toString()\n return fetchClient.request<ReviewsResult>(\n 'GET',\n `/api/products/${encodeURIComponent(productId)}/reviews${qs ? `?${qs}` : ''}`,\n )\n },\n submit(review: { productId: string; rating: number; title: string; body: string }) {\n return fetchClient.request<ReviewResult>('POST', '/api/reviews', review)\n },\n },\n\n alerts: {\n subscribe(request: { email: string; variantId: string; kind: AlertKind }) {\n return fetchClient.request<{ status: 'pending' }>('POST', '/api/alerts/subscribe', request)\n },\n },\n }\n}\n","export interface TokenManagerOptions {\n storage?: 'memory' | 'localStorage'\n storageKey?: string\n}\n\nexport interface TokenManager {\n getAccessToken(): string | null\n setTokens(accessToken: string, expiresIn: number): void\n clear(): void\n scheduleRefresh(refreshFn: () => Promise<{ accessToken: string; expiresIn: number }>): void\n destroy(): void\n}\n\nconst DEFAULT_STORAGE_KEY = 'nymbal.auth.accessToken'\n\nexport function createTokenManager(options?: TokenManagerOptions): TokenManager {\n const storageMode = options?.storage ?? 'memory'\n const storageKey = options?.storageKey ?? DEFAULT_STORAGE_KEY\n\n let memoryToken: string | null = null\n let refreshTimer: ReturnType<typeof setTimeout> | null = null\n let refreshFn: (() => Promise<{ accessToken: string; expiresIn: number }>) | null = null\n\n function getAccessToken(): string | null {\n if (storageMode === 'localStorage' && typeof globalThis.localStorage !== 'undefined') {\n return globalThis.localStorage.getItem(storageKey)\n }\n return memoryToken\n }\n\n function setAccessToken(token: string | null): void {\n memoryToken = token\n if (storageMode === 'localStorage' && typeof globalThis.localStorage !== 'undefined') {\n if (token) {\n globalThis.localStorage.setItem(storageKey, token)\n } else {\n globalThis.localStorage.removeItem(storageKey)\n }\n }\n }\n\n function setTokens(accessToken: string, expiresIn: number): void {\n setAccessToken(accessToken)\n scheduleAutoRefresh(expiresIn)\n }\n\n function clear(): void {\n setAccessToken(null)\n if (refreshTimer !== null) {\n clearTimeout(refreshTimer)\n refreshTimer = null\n }\n }\n\n function scheduleAutoRefresh(expiresIn: number): void {\n if (refreshTimer !== null) {\n clearTimeout(refreshTimer)\n }\n const delayMs = Math.max(1000, expiresIn * 0.8 * 1000)\n refreshTimer = setTimeout(async () => {\n if (!refreshFn) return\n try {\n const result = await refreshFn()\n setTokens(result.accessToken, result.expiresIn)\n } catch (err) {\n console.error('[TokenManager] auto-refresh failed, clearing session', err)\n clear()\n }\n }, delayMs)\n }\n\n function scheduleRefresh(\n fn: () => Promise<{ accessToken: string; expiresIn: number }>,\n ): void {\n refreshFn = fn\n }\n\n function destroy(): void {\n clear()\n refreshFn = null\n }\n\n return { getAccessToken, setTokens, clear, scheduleRefresh, destroy }\n}\n","import type { CommerceAdapter } from './adapter/commerce-adapter.js'\nimport { FetchClient } from './adapter/fetch-client.js'\nimport { createNymbalApiAdapter } from './adapter/nymbal-api-adapter.js'\nimport { createTokenManager, type TokenManager } from './auth/token-manager.js'\nimport type { CartStore } from './store/cart-store.js'\nimport type { ProductStore } from './store/product-store.js'\nimport type { ProductListStore } from './store/product-list-store.js'\nimport type { SearchStore } from './store/search-store.js'\nimport type { CheckoutStore } from './store/checkout-store.js'\nimport type { AuthStore } from './store/auth-store.js'\nimport type { Store } from './store/create-store.js'\nimport type { CustomerAddress, ShopperOrder, StoreCapabilities } from '@nymbalhq/types'\nimport type { DenormalisedProduct, NewAddress, OrderPage } from './types.js'\n\nexport interface NymbalClientOptions {\n baseUrl: string\n adapter?: CommerceAdapter\n tokenStorage?: 'memory' | 'localStorage'\n credentials?: RequestCredentials\n getCartToken?: () => string | null\n}\n\n/** The signed-in customer's own account; every call sends the session's bearer token. */\nexport interface CustomerAccount {\n addresses: {\n list(): Promise<CustomerAddress[]>\n create(address: NewAddress): Promise<CustomerAddress>\n remove(id: string): Promise<void>\n }\n orders: {\n list(opts?: { cursor?: string }): Promise<OrderPage>\n /** `key`, the order's key from its checkout, reads it with no session; without one, only its owner can. */\n get(orderNumber: string, options?: { key?: string }): Promise<ShopperOrder>\n }\n}\n\n/**\n * A guest's orders, opened with the key from the checkout or the order email. Once the key has expired, the read is\n * refused with `orders.key_expired` and opens with the order's email beside the key; the email travels in a header,\n * never in a URL.\n */\nexport interface GuestOrders {\n get(orderNumber: string, options?: { key?: string; email?: string }): Promise<ShopperOrder>\n /** Asks the store to email the order's link to the order's own email; it answers the same whatever matched. */\n requestLink(request: { orderNumber: string; email: string }): Promise<void>\n}\n\n/** The store's capabilities report, read once per client: what it can do does not change while it runs. */\nexport interface StoreCapabilitiesReader {\n get(): Promise<StoreCapabilities>\n}\n\n/** The shopper's recently viewed products, kept by the store under the signed-in customer or the browser's visitor. */\nexport interface RecentlyViewed {\n /** Records a view of the product; the store ignores a product that is not active. */\n record(productId: string): Promise<void>\n /** The shopper's active products, newest first. */\n list(): Promise<{ items: DenormalisedProduct[] }>\n}\n\nexport interface NymbalClient {\n capabilities: StoreCapabilitiesReader\n recentlyViewed: RecentlyViewed\n cart: CartStore\n product: ProductStore\n productList: ProductListStore\n search: SearchStore\n checkout: CheckoutStore\n auth: AuthStore\n customer: CustomerAccount\n orders: GuestOrders\n adapter: CommerceAdapter\n destroy(): void\n}\n\n/** The client's store namespaces, each built on its first read by the factory its `@nymbalhq/sdk/ns/<module>` registers. */\nexport type StoreNamespace = 'cart' | 'product' | 'productList' | 'search' | 'checkout' | 'auth'\n\nconst STORE_NAMESPACES: StoreNamespace[] = [\n 'cart',\n 'product',\n 'productList',\n 'search',\n 'checkout',\n 'auth',\n]\n\n/** What a namespace's factory builds its store from: the client's adapter and token manager, and the client itself. */\nexport interface NamespaceContext {\n adapter: CommerceAdapter\n tokenManager: TokenManager\n client: NymbalClient\n}\n\ntype NamespaceFactory<K extends StoreNamespace> = (context: NamespaceContext) => NymbalClient[K]\n\ntype Factories = { [K in StoreNamespace]?: NamespaceFactory<K> }\n\n// On globalThis, like the payment drivers: a namespace module of one SDK copy registers for a client of another.\nconst FACTORIES_KEY = Symbol.for('nymbal.sdk.namespaces')\n\nfunction factories(): Factories {\n const holder = globalThis as Record<symbol, Factories | undefined>\n holder[FACTORIES_KEY] ??= {}\n return holder[FACTORIES_KEY]\n}\n\n/** Registers the factory of a store namespace; each `ns` module calls it on import. */\nexport function defineNamespace<K extends StoreNamespace>(\n name: K,\n factory: NamespaceFactory<K>,\n): void {\n factories()[name] = factory as Factories[K]\n}\n\n/** What `onStoreBuilt` hands its listener: any store, a hand-made client's included, reads and subscribes. */\ntype ObservedStore = Pick<Store<unknown>, 'getState' | 'subscribe'>\n\ntype ClientStore = ObservedStore & Pick<Store<unknown>, 'destroy'>\n\ntype StoreListener = (store: ObservedStore) => void\n\n// On a symbol, not in a module's registry, so a client made by another copy of the SDK still reports its stores.\nconst STORE_BUILT = Symbol.for('nymbal.client.onStoreBuilt')\n\ntype StoreBuiltHook = (listener: StoreListener) => () => void\n\nfunction isStore(value: unknown): value is ObservedStore {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as ObservedStore).subscribe === 'function' &&\n typeof (value as ObservedStore).getState === 'function'\n )\n}\n\n/**\n * Calls `listener` with each store namespace the client has built, and with each it builds later; returns the\n * unsubscribe. A client not made by `createNymbalClient` has its stores already: each is reported once, now.\n */\nexport function onStoreBuilt(client: NymbalClient, listener: StoreListener): () => void {\n const hook = (client as unknown as Record<symbol, StoreBuiltHook | undefined>)[STORE_BUILT]\n if (typeof hook === 'function') return hook(listener)\n for (const value of Object.values(client)) {\n if (isStore(value)) listener(value)\n }\n return () => {}\n}\n\nexport function createNymbalClient(options: NymbalClientOptions): NymbalClient {\n const tokenManager = createTokenManager({\n ...(options.tokenStorage !== undefined && { storage: options.tokenStorage }),\n })\n\n let adapter: CommerceAdapter\n\n if (options.adapter) {\n adapter = options.adapter\n } else {\n const fetchClient = new FetchClient({\n baseUrl: options.baseUrl,\n ...(options.credentials !== undefined && { credentials: options.credentials }),\n getAccessToken: () => tokenManager.getAccessToken(),\n ...(options.getCartToken !== undefined && { getCartToken: options.getCartToken }),\n onUnauthorized: async () => {\n try {\n const result = await adapter.customers.refresh()\n tokenManager.setTokens(result.accessToken, result.expiresIn)\n } catch (err) {\n // Token refresh failure is expected when the user session has expired.\n console.warn('[NymbalClient] token refresh on unauthorized failed', err)\n tokenManager.clear()\n }\n },\n })\n adapter = createNymbalApiAdapter(fetchClient)\n }\n\n const customer: CustomerAccount = {\n addresses: {\n list: () => adapter.customers.listAddresses(),\n create: (address) => adapter.customers.addAddress(address),\n remove: (id) => adapter.customers.removeAddress(id),\n },\n orders: {\n list: (opts) => adapter.customers.listOrders(opts),\n get: (orderNumber, options) => adapter.customers.getOrder(orderNumber, options),\n },\n }\n\n // A failed read is not kept, so the next get() asks again.\n let report: Promise<StoreCapabilities> | null = null\n const capabilities: StoreCapabilitiesReader = {\n get() {\n if (report === null) {\n const reader = adapter.capabilities\n report =\n reader === undefined\n ? Promise.reject(new Error('This client’s adapter cannot read the store capabilities.'))\n : reader.get()\n report.catch(() => {\n report = null\n })\n }\n return report\n },\n }\n\n // The adapter is the client's for its whole life, so whether it reads guests' orders is known here.\n const noGuestOrders = () =>\n Promise.reject(new Error('This client’s adapter cannot open a guest’s order.'))\n const orders: GuestOrders = adapter.orders ?? { get: noGuestOrders, requestLink: noGuestOrders }\n\n const recentlyViewedOf = (): NonNullable<CommerceAdapter['recentlyViewed']> => {\n const reader = adapter.recentlyViewed\n if (reader === undefined) {\n throw new Error('This client’s adapter cannot record or list recently viewed products.')\n }\n return reader\n }\n const recentlyViewed: RecentlyViewed = {\n async record(productId) {\n await recentlyViewedOf().record(productId)\n },\n async list() {\n return recentlyViewedOf().list()\n },\n }\n\n tokenManager.scheduleRefresh(() => adapter.customers.refresh())\n\n const built: { [K in StoreNamespace]?: ClientStore } = {}\n const listeners = new Set<StoreListener>()\n let destroyed = false\n\n function destroy(): void {\n destroyed = true\n for (const store of Object.values(built)) store.destroy()\n tokenManager.destroy()\n }\n\n const storeBuilt: StoreBuiltHook = (listener) => {\n for (const store of Object.values(built)) listener(store)\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n }\n const client = Object.defineProperty(\n { capabilities, recentlyViewed, customer, orders, adapter, destroy },\n STORE_BUILT,\n { value: storeBuilt },\n ) as NymbalClient\n const context: NamespaceContext = { adapter, tokenManager, client }\n\n function build(name: StoreNamespace): ClientStore {\n // Building one now would leave a live store on a client whose destroy() has run.\n if (destroyed) throw new Error(`client.${name} was read after the client was destroyed`)\n const factory = factories()[name] as NamespaceFactory<StoreNamespace> | undefined\n if (factory === undefined) {\n const module = name === 'productList' ? 'product-list' : name\n throw new Error(`client.${name} is not in this client: import '@nymbalhq/sdk/ns/${module}'`)\n }\n const store = (built[name] = factory(context))\n for (const listener of listeners) listener(store)\n return store\n }\n\n // Defined properties are not enumerable: walking or spreading the client builds no store.\n for (const name of STORE_NAMESPACES) {\n Object.defineProperty(client, name, { get: () => built[name] ?? build(name) })\n }\n\n return client\n}\n"],"mappings":"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","names":["options"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/store/auth-store.ts","../src/ns/auth.ts"],"sourcesContent":["import type { Customer } from '@nymbalhq/types'\nimport type { CommerceAdapter } from '../adapter/commerce-adapter.js'\nimport type { TokenManager } from '../auth/token-manager.js'\nimport type { AuthState, RegisterParams } from '../types.js'\nimport { createStore } from './create-store.js'\n\nconst INITIAL_STATE: AuthState = {\n customer: null,\n isAuthenticated: false,\n loading: false,\n error: null,\n}\n\nexport interface AuthStore {\n getState(): AuthState\n subscribe(listener: () => void): () => void\n login(email: string, password: string): Promise<void>\n register(params: RegisterParams): Promise<void>\n logout(): Promise<void>\n refreshToken(): Promise<void>\n updateProfile(updates: Partial<Pick<Customer, 'firstName' | 'lastName' | 'phone'>>): Promise<void>\n loadProfile(): Promise<void>\n destroy(): void\n}\n\nexport function createAuthStore(\n adapter: CommerceAdapter,\n tokenManager: TokenManager,\n): AuthStore {\n const store = createStore<AuthState>(INITIAL_STATE)\n\n async function login(email: string, password: string): Promise<void> {\n store.setState({ loading: true, error: null })\n try {\n const result = await adapter.customers.login(email, password)\n tokenManager.setTokens(result.accessToken, result.expiresIn)\n store.setState({\n customer: result.customer,\n isAuthenticated: true,\n loading: false,\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n store.setState({ loading: false, error: message })\n throw err\n }\n }\n\n async function register(params: RegisterParams): Promise<void> {\n store.setState({ loading: true, error: null })\n try {\n const result = await adapter.customers.register(params)\n tokenManager.setTokens(result.accessToken, result.expiresIn)\n store.setState({\n customer: result.customer,\n isAuthenticated: true,\n loading: false,\n })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n store.setState({ loading: false, error: message })\n throw err\n }\n }\n\n async function logout(): Promise<void> {\n store.setState({ loading: true, error: null })\n try {\n await adapter.customers.logout()\n tokenManager.clear()\n store.setState({\n customer: null,\n isAuthenticated: false,\n loading: false,\n })\n } catch (err) {\n tokenManager.clear()\n store.setState({\n customer: null,\n isAuthenticated: false,\n loading: false,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n async function refreshToken(): Promise<void> {\n try {\n const result = await adapter.customers.refresh()\n tokenManager.setTokens(result.accessToken, result.expiresIn)\n } catch (err) {\n console.error('[AuthStore] token refresh failed, logging out', err)\n tokenManager.clear()\n store.setState({\n customer: null,\n isAuthenticated: false,\n })\n }\n }\n\n async function updateProfile(\n updates: Partial<Pick<Customer, 'firstName' | 'lastName' | 'phone'>>,\n ): Promise<void> {\n store.setState({ loading: true, error: null })\n try {\n const customer = await adapter.customers.updateProfile(updates)\n store.setState({ customer, loading: false })\n } catch (err) {\n store.setState({\n loading: false,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n async function loadProfile(): Promise<void> {\n if (!tokenManager.getAccessToken()) return\n store.setState({ loading: true, error: null })\n try {\n const customer = await adapter.customers.getProfile()\n store.setState({\n customer,\n isAuthenticated: true,\n loading: false,\n })\n } catch (err) {\n store.setState({\n loading: false,\n error: err instanceof Error ? err.message : String(err),\n })\n }\n }\n\n return {\n getState: store.getState,\n subscribe: store.subscribe,\n login,\n register,\n logout,\n refreshToken,\n updateProfile,\n loadProfile,\n destroy: store.destroy,\n }\n}\n","import { defineNamespace } from '../client.js'\nimport { createAuthStore } from '../store/auth-store.js'\n\ndefineNamespace('auth', ({ adapter, tokenManager }) => createAuthStore(adapter, tokenManager))\n"],"mappings":";;;;;;;;AAMA,IAAM,gBAA2B;AAAA,EAC/B,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB,SAAS;AAAA,EACT,OAAO;AACT;AAcO,SAAS,gBACd,SACA,cACW;AACX,QAAM,QAAQ,YAAuB,aAAa;AAElD,iBAAe,MAAM,OAAe,UAAiC;AACnE,UAAM,SAAS,EAAE,SAAS,MAAM,OAAO,KAAK,CAAC;AAC7C,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,UAAU,MAAM,OAAO,QAAQ;AAC5D,mBAAa,UAAU,OAAO,aAAa,OAAO,SAAS;AAC3D,YAAM,SAAS;AAAA,QACb,UAAU,OAAO;AAAA,QACjB,iBAAiB;AAAA,QACjB,SAAS;AAAA,MACX,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,YAAM,SAAS,EAAE,SAAS,OAAO,OAAO,QAAQ,CAAC;AACjD,YAAM;AAAA,IACR;AAAA,EACF;AAEA,iBAAe,SAAS,QAAuC;AAC7D,UAAM,SAAS,EAAE,SAAS,MAAM,OAAO,KAAK,CAAC;AAC7C,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,UAAU,SAAS,MAAM;AACtD,mBAAa,UAAU,OAAO,aAAa,OAAO,SAAS;AAC3D,YAAM,SAAS;AAAA,QACb,UAAU,OAAO;AAAA,QACjB,iBAAiB;AAAA,QACjB,SAAS;AAAA,MACX,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,YAAM,SAAS,EAAE,SAAS,OAAO,OAAO,QAAQ,CAAC;AACjD,YAAM;AAAA,IACR;AAAA,EACF;AAEA,iBAAe,SAAwB;AACrC,UAAM,SAAS,EAAE,SAAS,MAAM,OAAO,KAAK,CAAC;AAC7C,QAAI;AACF,YAAM,QAAQ,UAAU,OAAO;AAC/B,mBAAa,MAAM;AACnB,YAAM,SAAS;AAAA,QACb,UAAU;AAAA,QACV,iBAAiB;AAAA,QACjB,SAAS;AAAA,MACX,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,mBAAa,MAAM;AACnB,YAAM,SAAS;AAAA,QACb,UAAU;AAAA,QACV,iBAAiB;AAAA,QACjB,SAAS;AAAA,QACT,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD,CAAC;AAAA,IACH;AAAA,EACF;AAEA,iBAAe,eAA8B;AAC3C,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,UAAU,QAAQ;AAC/C,mBAAa,UAAU,OAAO,aAAa,OAAO,SAAS;AAAA,IAC7D,SAAS,KAAK;AACZ,cAAQ,MAAM,iDAAiD,GAAG;AAClE,mBAAa,MAAM;AACnB,YAAM,SAAS;AAAA,QACb,UAAU;AAAA,QACV,iBAAiB;AAAA,MACnB,CAAC;AAAA,IACH;AAAA,EACF;AAEA,iBAAe,cACb,SACe;AACf,UAAM,SAAS,EAAE,SAAS,MAAM,OAAO,KAAK,CAAC;AAC7C,QAAI;AACF,YAAM,WAAW,MAAM,QAAQ,UAAU,cAAc,OAAO;AAC9D,YAAM,SAAS,EAAE,UAAU,SAAS,MAAM,CAAC;AAAA,IAC7C,SAAS,KAAK;AACZ,YAAM,SAAS;AAAA,QACb,SAAS;AAAA,QACT,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD,CAAC;AAAA,IACH;AAAA,EACF;AAEA,iBAAe,cAA6B;AAC1C,QAAI,CAAC,aAAa,eAAe,EAAG;AACpC,UAAM,SAAS,EAAE,SAAS,MAAM,OAAO,KAAK,CAAC;AAC7C,QAAI;AACF,YAAM,WAAW,MAAM,QAAQ,UAAU,WAAW;AACpD,YAAM,SAAS;AAAA,QACb;AAAA,QACA,iBAAiB;AAAA,QACjB,SAAS;AAAA,MACX,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,SAAS;AAAA,QACb,SAAS;AAAA,QACT,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU,MAAM;AAAA,IAChB,WAAW,MAAM;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,MAAM;AAAA,EACjB;AACF;;;AC7IA,gBAAgB,QAAQ,CAAC,EAAE,SAAS,aAAa,MAAM,gBAAgB,SAAS,YAAY,CAAC;","names":[]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|