@webround/commerce 0.0.0-stage → 0.3.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/README.md +192 -2
- package/dist/account/account.d.ts +54 -0
- package/dist/account/account.js +40 -0
- package/dist/account/index.d.ts +3 -0
- package/dist/account/index.js +2 -0
- package/dist/addresses/api.d.ts +16 -0
- package/dist/addresses/api.js +13 -0
- package/dist/addresses/book.d.ts +40 -0
- package/dist/addresses/book.js +144 -0
- package/dist/addresses/index.d.ts +8 -0
- package/dist/addresses/index.js +5 -0
- package/dist/addresses/rules.d.ts +4 -0
- package/dist/addresses/rules.js +9 -0
- package/dist/addresses/types.d.ts +48 -0
- package/dist/addresses/types.js +6 -0
- package/dist/auth/api.d.ts +43 -0
- package/dist/auth/api.js +22 -0
- package/dist/auth/auth.d.ts +32 -0
- package/dist/auth/auth.js +23 -0
- package/dist/auth/cookies.d.ts +46 -0
- package/dist/auth/cookies.js +156 -0
- package/dist/auth/index.d.ts +14 -0
- package/dist/auth/index.js +8 -0
- package/dist/auth/renewal.d.ts +4 -0
- package/dist/auth/renewal.js +37 -0
- package/dist/auth/store.d.ts +70 -0
- package/dist/auth/store.js +284 -0
- package/dist/auth/types.d.ts +105 -0
- package/dist/auth/types.js +5 -0
- package/dist/auth/wake.d.ts +18 -0
- package/dist/auth/wake.js +23 -0
- package/dist/cache.d.ts +49 -0
- package/dist/cache.js +144 -0
- package/dist/canonical.d.ts +7 -0
- package/dist/canonical.js +32 -0
- package/dist/cart/api.d.ts +25 -0
- package/dist/cart/api.js +16 -0
- package/dist/cart/index.d.ts +10 -0
- package/dist/cart/index.js +6 -0
- package/dist/cart/lines.d.ts +15 -0
- package/dist/cart/lines.js +23 -0
- package/dist/cart/storage.d.ts +30 -0
- package/dist/cart/storage.js +82 -0
- package/dist/cart/store.d.ts +58 -0
- package/dist/cart/store.js +218 -0
- package/dist/cart/types.d.ts +194 -0
- package/dist/cart/types.js +11 -0
- package/dist/catalog/cached.d.ts +47 -0
- package/dist/catalog/cached.js +52 -0
- package/dist/catalog/catalog.d.ts +10 -0
- package/dist/catalog/catalog.js +7 -0
- package/dist/catalog/named.d.ts +3 -0
- package/dist/catalog/named.js +7 -0
- package/dist/catalog/products.d.ts +28 -0
- package/dist/catalog/products.js +12 -0
- package/dist/catalog/store-data.d.ts +13 -0
- package/dist/catalog/store-data.js +6 -0
- package/dist/catalog/types.d.ts +430 -0
- package/dist/catalog/types.js +4 -0
- package/dist/checkout/api.d.ts +14 -0
- package/dist/checkout/api.js +7 -0
- package/dist/checkout/errors.d.ts +23 -0
- package/dist/checkout/errors.js +18 -0
- package/dist/checkout/index.d.ts +10 -0
- package/dist/checkout/index.js +6 -0
- package/dist/checkout/return-urls.d.ts +7 -0
- package/dist/checkout/return-urls.js +11 -0
- package/dist/checkout/store.d.ts +98 -0
- package/dist/checkout/store.js +179 -0
- package/dist/checkout/types.d.ts +225 -0
- package/dist/checkout/types.js +28 -0
- package/dist/commerce.d.ts +55 -0
- package/dist/commerce.js +55 -0
- package/dist/customer-resources.d.ts +18 -0
- package/dist/customer-resources.js +40 -0
- package/dist/errors.d.ts +65 -0
- package/dist/errors.js +96 -0
- package/dist/http.d.ts +49 -0
- package/dist/http.js +94 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +22 -0
- package/dist/orders/api.d.ts +16 -0
- package/dist/orders/api.js +13 -0
- package/dist/orders/index.d.ts +6 -0
- package/dist/orders/index.js +3 -0
- package/dist/orders/orders.d.ts +19 -0
- package/dist/orders/orders.js +18 -0
- package/dist/orders/types.d.ts +88 -0
- package/dist/orders/types.js +4 -0
- package/dist/resource.d.ts +19 -0
- package/dist/resource.js +15 -0
- package/dist/returns/api.d.ts +27 -0
- package/dist/returns/api.js +19 -0
- package/dist/returns/index.d.ts +6 -0
- package/dist/returns/index.js +3 -0
- package/dist/returns/returns.d.ts +30 -0
- package/dist/returns/returns.js +31 -0
- package/dist/returns/types.d.ts +99 -0
- package/dist/returns/types.js +2 -0
- package/dist/reviews/api.d.ts +19 -0
- package/dist/reviews/api.js +15 -0
- package/dist/reviews/index.d.ts +9 -0
- package/dist/reviews/index.js +5 -0
- package/dist/reviews/reviews.d.ts +23 -0
- package/dist/reviews/reviews.js +37 -0
- package/dist/reviews/rules.d.ts +8 -0
- package/dist/reviews/rules.js +7 -0
- package/dist/reviews/types.d.ts +54 -0
- package/dist/reviews/types.js +10 -0
- package/dist/shop/address-store.d.ts +14 -0
- package/dist/shop/address-store.js +28 -0
- package/dist/shop/address.d.ts +12 -0
- package/dist/shop/address.js +95 -0
- package/dist/shop/index.d.ts +16 -0
- package/dist/shop/index.js +9 -0
- package/dist/shop/question.d.ts +15 -0
- package/dist/shop/question.js +12 -0
- package/dist/shop/range.d.ts +7 -0
- package/dist/shop/range.js +38 -0
- package/dist/shop/sort.d.ts +19 -0
- package/dist/shop/sort.js +18 -0
- package/dist/shop/state.d.ts +47 -0
- package/dist/shop/state.js +51 -0
- package/dist/shop/store.d.ts +26 -0
- package/dist/shop/store.js +47 -0
- package/dist/shop/to-search.d.ts +14 -0
- package/dist/shop/to-search.js +53 -0
- package/dist/store-kit.d.ts +39 -0
- package/dist/store-kit.js +81 -0
- package/dist/wishlist/api.d.ts +12 -0
- package/dist/wishlist/api.js +10 -0
- package/dist/wishlist/index.d.ts +7 -0
- package/dist/wishlist/index.js +4 -0
- package/dist/wishlist/types.d.ts +30 -0
- package/dist/wishlist/types.js +4 -0
- package/dist/wishlist/wishlist.d.ts +45 -0
- package/dist/wishlist/wishlist.js +84 -0
- package/package.json +39 -5
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** What an address is made of to be taken (the store's own bounds). */
|
|
2
|
+
export interface AddressInput {
|
|
3
|
+
fullName: string;
|
|
4
|
+
company?: string;
|
|
5
|
+
phone?: string;
|
|
6
|
+
addressLine1: string;
|
|
7
|
+
addressLine2?: string;
|
|
8
|
+
city: string;
|
|
9
|
+
province?: string;
|
|
10
|
+
postalCode: string;
|
|
11
|
+
/** The country's two letters (`IT`). */
|
|
12
|
+
countryCode: string;
|
|
13
|
+
vatCode?: string | null;
|
|
14
|
+
additionalInfo?: string;
|
|
15
|
+
/** The customer's default for shipping / for billing: making one default takes it from the one that was. */
|
|
16
|
+
isDefaultShipping?: boolean;
|
|
17
|
+
isDefaultBilling?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** What is changed of an address: any of it. */
|
|
20
|
+
export type AddressPatch = Partial<AddressInput>;
|
|
21
|
+
/** An address as the store keeps it: what was given, with what it adds, and `null` for what was not given. */
|
|
22
|
+
export interface Address {
|
|
23
|
+
id: string;
|
|
24
|
+
storeId: string;
|
|
25
|
+
customerId: string;
|
|
26
|
+
fullName: string;
|
|
27
|
+
company: string | null;
|
|
28
|
+
phone: string | null;
|
|
29
|
+
addressLine1: string;
|
|
30
|
+
addressLine2: string | null;
|
|
31
|
+
city: string;
|
|
32
|
+
postalCode: string;
|
|
33
|
+
province: string | null;
|
|
34
|
+
countryCode: string;
|
|
35
|
+
additionalInfo: string | null;
|
|
36
|
+
vatCode: string | null;
|
|
37
|
+
isDefaultShipping: boolean;
|
|
38
|
+
isDefaultBilling: boolean;
|
|
39
|
+
createdAt: string | null;
|
|
40
|
+
updatedAt: string | null;
|
|
41
|
+
}
|
|
42
|
+
/** An address as a screen has it, a customer's or a guest's: what it says and whether it is a default (a guest's has an id of its own, `local-…`, and no more than that). */
|
|
43
|
+
export type BookAddress = Pick<Address, 'id' | 'fullName' | 'company' | 'phone' | 'addressLine1' | 'addressLine2' | 'city' | 'postalCode' | 'province' | 'countryCode' | 'additionalInfo' | 'vatCode' | 'isDefaultShipping' | 'isDefaultBilling'>;
|
|
44
|
+
/** The `code` the store gives to what an address call refuses. */
|
|
45
|
+
export declare const ADDRESS_ERROR_CODES: {
|
|
46
|
+
readonly notFound: "ADDRESS_NOT_FOUND";
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// What the address calls of commerce-core take and give, as the store says it. A customer's addresses are kept by the store; a guest's are not kept anywhere (see book.ts).
|
|
2
|
+
/** The `code` the store gives to what an address call refuses. */
|
|
3
|
+
export const ADDRESS_ERROR_CODES = {
|
|
4
|
+
notFound: 'ADDRESS_NOT_FOUND',
|
|
5
|
+
};
|
|
6
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { HttpClient } from '../http.js';
|
|
2
|
+
import type { CallOptions } from '../catalog/types.js';
|
|
3
|
+
import type { EmailOptions, LoginAnswer, LoginInput, LogoutInput, RefreshedSession, RefreshInput, RequestResetInput, SignupInput, AuthSession, NewsletterAnswer, NewsletterInput, TotpConfirmAnswer, TotpConfirmInput, TotpLoginVerifyInput, TotpSetup } from './types.js';
|
|
4
|
+
/** For the calls that need the JWT: give it here and it is sent as `Authorization: Bearer …` (it wins over what the client adds). Without it the client's `getHeaders` is what carries it. */
|
|
5
|
+
export interface AccessTokenOptions {
|
|
6
|
+
accessToken?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface AuthApi {
|
|
9
|
+
/** `POST /store-customers/auth/signup-password`: makes the customer AND logs them in (a session, with the cookie). Their address is not verified yet (`emailVerified: false`): the store sends them the email. */
|
|
10
|
+
signup(input: SignupInput, options?: CallOptions & EmailOptions): Promise<AuthSession>;
|
|
11
|
+
/** `POST /store-customers/auth/login-password`: a session (and the cookie), or, for a customer with two-factor authentication, a challenge to answer with `totpLoginVerify`. */
|
|
12
|
+
login(input: LoginInput, options?: CallOptions): Promise<LoginAnswer>;
|
|
13
|
+
/** `POST /store-customers/auth/totp/login-verify`: the code of the authenticator app, for the challenge a login answered: the session (and the cookie). */
|
|
14
|
+
totpLoginVerify(input: TotpLoginVerifyInput, options?: CallOptions): Promise<AuthSession>;
|
|
15
|
+
/**
|
|
16
|
+
* `POST /store-customers/auth/refresh-token`: a new JWT, from the refresh token (the cookie) and the `csrfToken` the login gave (`x-wr-csrf`), for this device. A refresh token that is not there, or
|
|
17
|
+
* that does not match, or that ended, is an error (403 with no cookie or no CSRF token, 401 with a wrong one, 401 `EXPIRED_TOKEN` when it ended): the customer is not logged in any more.
|
|
18
|
+
*/
|
|
19
|
+
refresh(input: RefreshInput, credentials: {
|
|
20
|
+
csrfToken: string;
|
|
21
|
+
}, options?: CallOptions): Promise<RefreshedSession>;
|
|
22
|
+
/** `POST /store-customers/auth/logout` (needs the JWT): ends the refresh token of this device; the store clears the cookies. Nothing comes back. */
|
|
23
|
+
logout(input: LogoutInput, options?: CallOptions & AccessTokenOptions): Promise<void>;
|
|
24
|
+
/** `POST /store-customers/auth/request-reset`: the store sends an email with a link to a page where the password is chosen. It answers the same for an address it does not know. Nothing comes back. */
|
|
25
|
+
requestReset(input: RequestResetInput, options?: CallOptions & EmailOptions): Promise<void>;
|
|
26
|
+
/** `POST /store-customers/auth/resend-verification` (needs the JWT): the email that verifies the address, again. Nothing comes back. */
|
|
27
|
+
resendVerification(options?: CallOptions & EmailOptions & AccessTokenOptions): Promise<void>;
|
|
28
|
+
/** `DELETE /store-customers/auth/request-delete` (needs the JWT): the store sends an email with a link to a page that confirms the deletion of the account. Nothing comes back. */
|
|
29
|
+
requestDelete(options?: CallOptions & EmailOptions & AccessTokenOptions): Promise<void>;
|
|
30
|
+
/** `POST /store-customers/auth/totp/setup` (needs the JWT): a secret for an authenticator app, to be confirmed with `totpConfirm`. A second request within 15 minutes is `TWO_FA_SETUP_ALREADY_REQUESTED`. */
|
|
31
|
+
totpSetup(options?: CallOptions & AccessTokenOptions): Promise<TotpSetup>;
|
|
32
|
+
/** `POST /store-customers/auth/totp/confirm` (needs the JWT): turns two-factor authentication on with the code the app shows. */
|
|
33
|
+
totpConfirm(input: TotpConfirmInput, options?: CallOptions & AccessTokenOptions): Promise<TotpConfirmAnswer>;
|
|
34
|
+
/**
|
|
35
|
+
* `POST /store-customers/auth/newsletter/subscribe` (with or without the JWT): a logged-in customer with a verified address is subscribed at once (`direct: true`); a guest, or a customer whose address is not verified,
|
|
36
|
+
* is sent an email with a link that confirms it (`direct: false`). With the JWT the store uses the customer's own address and ignores the one in the body.
|
|
37
|
+
*/
|
|
38
|
+
newsletterSubscribe(input: NewsletterInput, options?: CallOptions & EmailOptions & AccessTokenOptions): Promise<NewsletterAnswer>;
|
|
39
|
+
/** `POST /store-customers/auth/newsletter/unsubscribe`: the same two ways (at once for a verified logged-in customer, by an email's link otherwise). */
|
|
40
|
+
newsletterUnsubscribe(input: NewsletterInput, options?: CallOptions & EmailOptions & AccessTokenOptions): Promise<NewsletterAnswer>;
|
|
41
|
+
}
|
|
42
|
+
export declare function createAuthApi(http: HttpClient): AuthApi;
|
|
43
|
+
//# sourceMappingURL=api.d.ts.map
|
package/dist/auth/api.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const bearer = (options) => (options?.accessToken ? { Authorization: `Bearer ${options.accessToken}` } : {});
|
|
2
|
+
const PREFIX = '/store-customers/auth';
|
|
3
|
+
const language = (options) => (options?.language ? { 'Accept-Language': options.language } : undefined);
|
|
4
|
+
export function createAuthApi(http) {
|
|
5
|
+
return {
|
|
6
|
+
signup: (input, options) => http.request({ method: 'POST', path: `${PREFIX}/signup-password`, body: input, credentials: 'include', headers: language(options), signal: options?.signal }),
|
|
7
|
+
login: (input, options) => http.request({ method: 'POST', path: `${PREFIX}/login-password`, body: input, credentials: 'include', signal: options?.signal }),
|
|
8
|
+
totpLoginVerify: (input, options) => http.request({ method: 'POST', path: `${PREFIX}/totp/login-verify`, body: input, credentials: 'include', signal: options?.signal }),
|
|
9
|
+
refresh: (input, credentials, options) => http.request({
|
|
10
|
+
method: 'POST', path: `${PREFIX}/refresh-token`, body: input, credentials: 'include', headers: { 'x-wr-csrf': credentials.csrfToken }, signal: options?.signal,
|
|
11
|
+
}),
|
|
12
|
+
logout: (input, options) => http.request({ method: 'POST', path: `${PREFIX}/logout`, body: input, credentials: 'include', headers: bearer(options), signal: options?.signal }),
|
|
13
|
+
requestReset: (input, options) => http.request({ method: 'POST', path: `${PREFIX}/request-reset`, body: input, headers: language(options), signal: options?.signal }),
|
|
14
|
+
resendVerification: (options) => http.request({ method: 'POST', path: `${PREFIX}/resend-verification`, headers: { ...language(options), ...bearer(options) }, signal: options?.signal }),
|
|
15
|
+
requestDelete: (options) => http.request({ method: 'DELETE', path: `${PREFIX}/request-delete`, headers: { ...language(options), ...bearer(options) }, signal: options?.signal }),
|
|
16
|
+
totpSetup: (options) => http.request({ method: 'POST', path: `${PREFIX}/totp/setup`, headers: bearer(options), signal: options?.signal }),
|
|
17
|
+
totpConfirm: (input, options) => http.request({ method: 'POST', path: `${PREFIX}/totp/confirm`, body: input, credentials: 'include', headers: bearer(options), signal: options?.signal }),
|
|
18
|
+
newsletterSubscribe: (input, options) => http.request({ method: 'POST', path: `${PREFIX}/newsletter/subscribe`, body: input, headers: { ...language(options), ...bearer(options) }, signal: options?.signal }),
|
|
19
|
+
newsletterUnsubscribe: (input, options) => http.request({ method: 'POST', path: `${PREFIX}/newsletter/unsubscribe`, body: input, headers: { ...language(options), ...bearer(options) }, signal: options?.signal }),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type HttpClient, type HttpClientConfig } from '../http.js';
|
|
2
|
+
import { type AuthApi } from './api.js';
|
|
3
|
+
import { type AuthStorage, type CookiePort, type CookieStorageOptions } from './cookies.js';
|
|
4
|
+
import { type WakePort } from './wake.js';
|
|
5
|
+
import { type AuthStore, type AuthStoreOptions } from './store.js';
|
|
6
|
+
export interface AuthConfig extends Pick<HttpClientConfig, 'baseUrl' | 'storeId' | 'timeoutMs' | 'fetch'> {
|
|
7
|
+
/** Where the CSRF token and the device id are kept: the browser's cookies and the page's own `localStorage` by default (a page in a frame of another site is refused its cookies: see `browserSessionPort`). */
|
|
8
|
+
cookies?: CookiePort;
|
|
9
|
+
cookieOptions?: CookieStorageOptions;
|
|
10
|
+
/** What the device says besides its id (`platform: 'web'`). */
|
|
11
|
+
device?: AuthStoreOptions['device'];
|
|
12
|
+
renewBeforeMs?: number;
|
|
13
|
+
now?: AuthStoreOptions['now'];
|
|
14
|
+
timers?: AuthStoreOptions['timers'];
|
|
15
|
+
/** When the page is back (visible, online): the browser's by default. */
|
|
16
|
+
wake?: WakePort;
|
|
17
|
+
}
|
|
18
|
+
export interface Auth {
|
|
19
|
+
/** A client of commerce-core for this store, whose calls carry the customer's JWT: renewed when it is about to end, and once more when the store refuses it (a 401 with no code), asking again after it. */
|
|
20
|
+
http: HttpClient;
|
|
21
|
+
/** A client like `http` for another backend of the same store (the checkout's address), carrying the same JWT. */
|
|
22
|
+
httpFor(baseUrl: string): HttpClient;
|
|
23
|
+
api: AuthApi;
|
|
24
|
+
store: AuthStore;
|
|
25
|
+
storage: AuthStorage;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Everything for the customer's session of ONE store, joined: the client carries the JWT the store gives. The store asks what it asks for itself through another client that carries none (a login, a
|
|
29
|
+
* refresh), so that neither waits for the other. The client and the store need each other, which is why this is one place.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createAuth(config: AuthConfig): Auth;
|
|
32
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createHttpClient } from '../http.js';
|
|
2
|
+
import { createAuthApi } from './api.js';
|
|
3
|
+
import { browserSessionPort, createCookieAuthStorage } from './cookies.js';
|
|
4
|
+
import { withTokenRenewal } from './renewal.js';
|
|
5
|
+
import { browserWakePort } from './wake.js';
|
|
6
|
+
import { createAuthStore } from './store.js';
|
|
7
|
+
/**
|
|
8
|
+
* Everything for the customer's session of ONE store, joined: the client carries the JWT the store gives. The store asks what it asks for itself through another client that carries none (a login, a
|
|
9
|
+
* refresh), so that neither waits for the other. The client and the store need each other, which is why this is one place.
|
|
10
|
+
*/
|
|
11
|
+
export function createAuth(config) {
|
|
12
|
+
const storage = createCookieAuthStorage(config.cookies ?? browserSessionPort(), config.storeId, config.cookieOptions);
|
|
13
|
+
const connection = { baseUrl: config.baseUrl, storeId: config.storeId, timeoutMs: config.timeoutMs, fetch: config.fetch };
|
|
14
|
+
let store;
|
|
15
|
+
// What the store asks for itself (a login, a refresh) goes through a client that carries no JWT: it is asked for when there may be none
|
|
16
|
+
const ownApi = createAuthApi(createHttpClient(connection));
|
|
17
|
+
// What the site asks for (and the calls of the customer's that need the JWT) goes through this one: it carries the store's JWT (renewed when it is about to end) and, when the store refuses it, renews it once and asks again
|
|
18
|
+
const carryingJwt = (baseUrl) => withTokenRenewal(createHttpClient({ ...connection, baseUrl }), { accessToken: () => store.accessToken(), renew: (rejected) => store.renew(rejected) });
|
|
19
|
+
const http = carryingJwt(config.baseUrl);
|
|
20
|
+
store = createAuthStore({ api: ownApi, storage, device: config.device, renewBeforeMs: config.renewBeforeMs, now: config.now, timers: config.timers, wake: config.wake ?? browserWakePort() });
|
|
21
|
+
return { http, httpFor: carryingJwt, api: createAuthApi(http), store, storage };
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface CookiePort {
|
|
2
|
+
/** Every cookie, as `document.cookie` says it: `a=1; b=2`. */
|
|
3
|
+
read(): string;
|
|
4
|
+
/** One cookie, as `document.cookie = …` takes it: `name=value; Path=/; Max-Age=…`. */
|
|
5
|
+
write(cookie: string): void;
|
|
6
|
+
}
|
|
7
|
+
/** What the client keeps, by what it is, not by where: the cookies are one way to keep it. */
|
|
8
|
+
export interface AuthStorage {
|
|
9
|
+
/** The CSRF token of the session, or null when there is none. */
|
|
10
|
+
csrfToken(): string | null;
|
|
11
|
+
saveCsrfToken(token: string): void;
|
|
12
|
+
forgetCsrfToken(): void;
|
|
13
|
+
/** This device's id (5 characters at least): made and kept the first time it is asked for. */
|
|
14
|
+
deviceId(): string;
|
|
15
|
+
}
|
|
16
|
+
export interface CookieStorageOptions {
|
|
17
|
+
/** Written with `Secure` (only over https). Default: when the page is on https. */
|
|
18
|
+
secure?: boolean;
|
|
19
|
+
/** How long the CSRF token is kept: as long as the refresh token (30 days) by default. */
|
|
20
|
+
csrfMaxAgeSeconds?: number;
|
|
21
|
+
/** How long the device id is kept: a year by default. */
|
|
22
|
+
deviceMaxAgeSeconds?: number;
|
|
23
|
+
/** Makes a device id; by default a random UUID. */
|
|
24
|
+
makeDeviceId?: () => string;
|
|
25
|
+
}
|
|
26
|
+
/** The cookies of a header, by name (a value that is not valid escaping is kept as it is). */
|
|
27
|
+
export declare function parseCookies(header: string): Record<string, string>;
|
|
28
|
+
/** A cookie as `document.cookie = …` takes it. `maxAgeSeconds: 0` deletes it. */
|
|
29
|
+
export declare function cookieText(name: string, value: string, options: {
|
|
30
|
+
maxAgeSeconds: number;
|
|
31
|
+
secure: boolean;
|
|
32
|
+
}): string;
|
|
33
|
+
export declare function createCookieAuthStorage(port: CookiePort, storeId: string, options?: CookieStorageOptions): AuthStorage;
|
|
34
|
+
/** The browser's own cookies. Where there is no document (a server render, a worker) it reads nothing and writes nothing: a customer there is signed out until the page itself runs. */
|
|
35
|
+
export declare function browserCookiePort(): CookiePort;
|
|
36
|
+
/** Cookies in memory, behaving as a browser's jar does for what is written here (a `Max-Age=0` deletes). For a test, and for a runtime that has no document and wants to keep a session anyway. */
|
|
37
|
+
export declare function memoryCookiePort(initial?: Record<string, string>): CookiePort & {
|
|
38
|
+
all(): Record<string, string>;
|
|
39
|
+
};
|
|
40
|
+
/** A `Storage` (`localStorage`) as a port of cookies: each cookie is a key of its own, with the time it ends. A write that the browser refuses (blocked storage, no room) is nothing: it never throws. */
|
|
41
|
+
export declare function storageCookiePort(storage: Pick<Storage, 'getItem' | 'setItem' | 'removeItem' | 'key' | 'length'>, now?: () => number): CookiePort;
|
|
42
|
+
/** Two places for the same cookies: a write goes to both, a read takes the first's and, for a name it does not have, the second's. So a place that refuses what is written to it (cookies in a frame of another site) is not missed. */
|
|
43
|
+
export declare function fallbackCookiePort(first: CookiePort, second: CookiePort): CookiePort;
|
|
44
|
+
/** What a session keeps its CSRF token and device id in, in a browser: the cookies, and the page's own `localStorage` besides (see the top of this file). Without `localStorage` (a server render, blocked storage) it is the cookies alone. */
|
|
45
|
+
export declare function browserSessionPort(): CookiePort;
|
|
46
|
+
//# sourceMappingURL=cookies.d.ts.map
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// What the client keeps of a customer's session between page loads, and where. The refresh token is not here: it is a cookie the store sets (HttpOnly, Secure, SameSite=None) that JavaScript never sees; the
|
|
2
|
+
// browser sends it where `credentials: 'include'` says. What the client keeps is two cookies of its own, written by JavaScript (so a server render can read them too):
|
|
3
|
+
// - `wrCsrfToken_<storeId>`: the CSRF token the login gave. It is made with the refresh token and lives as long as it does (30 days); a refresh asks for it IN A HEADER (`x-wr-csrf`), the cookie only keeps it.
|
|
4
|
+
// - `wrDeviceId_<storeId>`: who this device is to the store (the refresh token belongs to it): made once, kept a year.
|
|
5
|
+
// A cookie is read and written through a PORT of two functions, so the browser, a server render and a test are all the same to what uses it.
|
|
6
|
+
//
|
|
7
|
+
// Where a page is shown inside another site's (the editor's preview is an iframe of the editor), the browser refuses the cookies the page writes itself (`SameSite=Lax`, in a frame of another site): the CSRF
|
|
8
|
+
// token and the device id would be lost at the first reload, and the customer would be signed out. So the session's port (`browserSessionPort`) keeps them in two places of the PAGE's own: the cookies, and
|
|
9
|
+
// its `localStorage` (which a frame of another site has, for itself). Nothing of the backend changes: the CSRF token is still sent in a header, and the refresh token is still the store's `HttpOnly` cookie.
|
|
10
|
+
const DAY = 60 * 60 * 24;
|
|
11
|
+
/** The cookies of a header, by name (a value that is not valid escaping is kept as it is). */
|
|
12
|
+
export function parseCookies(header) {
|
|
13
|
+
const out = {};
|
|
14
|
+
for (const part of header.split(';')) {
|
|
15
|
+
const at = part.indexOf('=');
|
|
16
|
+
const name = at < 0 ? '' : part.slice(0, at).trim();
|
|
17
|
+
if (name === '')
|
|
18
|
+
continue;
|
|
19
|
+
const raw = part.slice(at + 1).trim();
|
|
20
|
+
if (name in out)
|
|
21
|
+
continue; // the first one counts, as in a browser
|
|
22
|
+
try {
|
|
23
|
+
out[name] = decodeURIComponent(raw);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
out[name] = raw;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
/** A cookie as `document.cookie = …` takes it. `maxAgeSeconds: 0` deletes it. */
|
|
32
|
+
export function cookieText(name, value, options) {
|
|
33
|
+
return `${name}=${encodeURIComponent(value)}; Path=/; Max-Age=${Math.max(0, Math.floor(options.maxAgeSeconds))}; SameSite=Lax${options.secure ? '; Secure' : ''}`;
|
|
34
|
+
}
|
|
35
|
+
const newDeviceId = () => globalThis.crypto.randomUUID();
|
|
36
|
+
export function createCookieAuthStorage(port, storeId, options = {}) {
|
|
37
|
+
const secure = options.secure ?? globalThis.location?.protocol === 'https:';
|
|
38
|
+
const csrfName = `wrCsrfToken_${storeId}`;
|
|
39
|
+
const deviceName = `wrDeviceId_${storeId}`;
|
|
40
|
+
const read = (name) => {
|
|
41
|
+
const value = parseCookies(port.read())[name];
|
|
42
|
+
return value === undefined || value === '' ? null : value;
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
csrfToken: () => read(csrfName),
|
|
46
|
+
saveCsrfToken: (token) => port.write(cookieText(csrfName, token, { maxAgeSeconds: options.csrfMaxAgeSeconds ?? 30 * DAY, secure })),
|
|
47
|
+
forgetCsrfToken: () => port.write(cookieText(csrfName, '', { maxAgeSeconds: 0, secure })),
|
|
48
|
+
deviceId() {
|
|
49
|
+
const known = read(deviceName);
|
|
50
|
+
if (known !== null && known.length >= 5)
|
|
51
|
+
return known;
|
|
52
|
+
const made = (options.makeDeviceId ?? newDeviceId)();
|
|
53
|
+
port.write(cookieText(deviceName, made, { maxAgeSeconds: options.deviceMaxAgeSeconds ?? 365 * DAY, secure }));
|
|
54
|
+
return made;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** The browser's own cookies. Where there is no document (a server render, a worker) it reads nothing and writes nothing: a customer there is signed out until the page itself runs. */
|
|
59
|
+
export function browserCookiePort() {
|
|
60
|
+
const doc = globalThis.document;
|
|
61
|
+
return {
|
|
62
|
+
read: () => doc?.cookie ?? '',
|
|
63
|
+
write: (cookie) => { if (doc)
|
|
64
|
+
doc.cookie = cookie; },
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Cookies in memory, behaving as a browser's jar does for what is written here (a `Max-Age=0` deletes). For a test, and for a runtime that has no document and wants to keep a session anyway. */
|
|
68
|
+
export function memoryCookiePort(initial = {}) {
|
|
69
|
+
const jar = new Map(Object.entries(initial));
|
|
70
|
+
return {
|
|
71
|
+
read: () => [...jar].map(([name, value]) => `${name}=${encodeURIComponent(value)}`).join('; '),
|
|
72
|
+
write(cookie) {
|
|
73
|
+
const [pair = '', ...attributes] = cookie.split(';');
|
|
74
|
+
const at = pair.indexOf('=');
|
|
75
|
+
if (at < 1)
|
|
76
|
+
return;
|
|
77
|
+
const name = pair.slice(0, at).trim();
|
|
78
|
+
const value = pair.slice(at + 1);
|
|
79
|
+
const maxAge = attributes.map((a) => /^\s*max-age\s*=\s*(-?\d+)/i.exec(a)?.[1]).find((v) => v !== undefined);
|
|
80
|
+
if (value === '' || (maxAge !== undefined && Number(maxAge) <= 0))
|
|
81
|
+
jar.delete(name);
|
|
82
|
+
else
|
|
83
|
+
jar.set(name, decodeURIComponent(value));
|
|
84
|
+
},
|
|
85
|
+
all: () => Object.fromEntries(jar),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** A `Storage` (`localStorage`) as a port of cookies: each cookie is a key of its own, with the time it ends. A write that the browser refuses (blocked storage, no room) is nothing: it never throws. */
|
|
89
|
+
export function storageCookiePort(storage, now = Date.now) {
|
|
90
|
+
const PREFIX = 'wr-cookie:';
|
|
91
|
+
const safely = (call, otherwise) => { try {
|
|
92
|
+
return call();
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return otherwise;
|
|
96
|
+
} };
|
|
97
|
+
return {
|
|
98
|
+
read() {
|
|
99
|
+
const parts = [];
|
|
100
|
+
const names = safely(() => Array.from({ length: storage.length }, (_, i) => storage.key(i)).filter((key) => key !== null && key.startsWith(PREFIX)), []);
|
|
101
|
+
for (const key of names) {
|
|
102
|
+
const kept = safely(() => JSON.parse(storage.getItem(key) ?? 'null'), null);
|
|
103
|
+
if (kept === null || typeof kept.v !== 'string')
|
|
104
|
+
continue;
|
|
105
|
+
if (typeof kept.e === 'number' && kept.e <= now()) {
|
|
106
|
+
safely(() => storage.removeItem(key), undefined);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
parts.push(`${key.slice(PREFIX.length)}=${encodeURIComponent(kept.v)}`);
|
|
110
|
+
}
|
|
111
|
+
return parts.join('; ');
|
|
112
|
+
},
|
|
113
|
+
write(cookie) {
|
|
114
|
+
const [pair = '', ...attributes] = cookie.split(';');
|
|
115
|
+
const at = pair.indexOf('=');
|
|
116
|
+
if (at < 1)
|
|
117
|
+
return;
|
|
118
|
+
const name = pair.slice(0, at).trim();
|
|
119
|
+
const value = safely(() => decodeURIComponent(pair.slice(at + 1)), pair.slice(at + 1));
|
|
120
|
+
const maxAge = attributes.map((a) => /^\s*max-age\s*=\s*(-?\d+)/i.exec(a)?.[1]).find((v) => v !== undefined);
|
|
121
|
+
if (value === '' || (maxAge !== undefined && Number(maxAge) <= 0)) {
|
|
122
|
+
safely(() => storage.removeItem(PREFIX + name), undefined);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const kept = { v: value, ...(maxAge !== undefined ? { e: now() + Number(maxAge) * 1000 } : {}) };
|
|
126
|
+
safely(() => storage.setItem(PREFIX + name, JSON.stringify(kept)), undefined);
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** Two places for the same cookies: a write goes to both, a read takes the first's and, for a name it does not have, the second's. So a place that refuses what is written to it (cookies in a frame of another site) is not missed. */
|
|
131
|
+
export function fallbackCookiePort(first, second) {
|
|
132
|
+
return {
|
|
133
|
+
read() {
|
|
134
|
+
const merged = { ...parseCookies(second.read()), ...parseCookies(first.read()) };
|
|
135
|
+
return Object.entries(merged).map(([name, value]) => `${name}=${encodeURIComponent(value)}`).join('; ');
|
|
136
|
+
},
|
|
137
|
+
write(cookie) {
|
|
138
|
+
first.write(cookie);
|
|
139
|
+
second.write(cookie);
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/** What a session keeps its CSRF token and device id in, in a browser: the cookies, and the page's own `localStorage` besides (see the top of this file). Without `localStorage` (a server render, blocked storage) it is the cookies alone. */
|
|
144
|
+
export function browserSessionPort() {
|
|
145
|
+
const cookies = browserCookiePort();
|
|
146
|
+
let storage;
|
|
147
|
+
try {
|
|
148
|
+
storage = globalThis.localStorage;
|
|
149
|
+
void storage?.length;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
storage = undefined;
|
|
153
|
+
}
|
|
154
|
+
return storage ? fallbackCookiePort(cookies, storageCookiePort(storage)) : cookies;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=cookies.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { createAuthApi } from './api.js';
|
|
2
|
+
export type { AccessTokenOptions, AuthApi } from './api.js';
|
|
3
|
+
export { requiresTwoFactor } from './types.js';
|
|
4
|
+
export type { AuthSession, DeviceInput, EmailOptions, LoginAnswer, LoginInput, LogoutInput, NewsletterAnswer, NewsletterInput, RefreshedSession, RefreshInput, RequestResetInput, SignupInput, TotpConfirmAnswer, TotpConfirmInput, TotpLoginVerifyInput, TotpSetup, TwoFactorChallenge, } from './types.js';
|
|
5
|
+
export { createAuth } from './auth.js';
|
|
6
|
+
export type { Auth, AuthConfig } from './auth.js';
|
|
7
|
+
export { browserCookiePort, browserSessionPort, cookieText, createCookieAuthStorage, fallbackCookiePort, memoryCookiePort, parseCookies, storageCookiePort } from './cookies.js';
|
|
8
|
+
export type { AuthStorage, CookiePort, CookieStorageOptions } from './cookies.js';
|
|
9
|
+
export { createAuthStore } from './store.js';
|
|
10
|
+
export type { AuthState, AuthStatus, AuthStore, AuthStoreOptions, Customer } from './store.js';
|
|
11
|
+
export { withTokenRenewal } from './renewal.js';
|
|
12
|
+
export { browserWakePort } from './wake.js';
|
|
13
|
+
export type { WakePort } from './wake.js';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createAuthApi } from './api.js';
|
|
2
|
+
export { requiresTwoFactor } from './types.js';
|
|
3
|
+
export { createAuth } from './auth.js';
|
|
4
|
+
export { browserCookiePort, browserSessionPort, cookieText, createCookieAuthStorage, fallbackCookiePort, memoryCookiePort, parseCookies, storageCookiePort } from './cookies.js';
|
|
5
|
+
export { createAuthStore } from './store.js';
|
|
6
|
+
export { withTokenRenewal } from './renewal.js';
|
|
7
|
+
export { browserWakePort } from './wake.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { isCommerceError } from '../errors.js';
|
|
2
|
+
// A client that carries the customer's JWT, and when the store says the JWT is not good renews it ONCE and asks again. The store answers a JWT that has ended the way it answers one that was never one: a 401
|
|
3
|
+
// with no `code` (seen against a real commerce-core; the guard cannot tell them apart). So the rule is the same for both: a 401 with no code, for a request that carried the JWT, is "get a new one".
|
|
4
|
+
// - a 401 that has a code is about something else (`INVALID_CREDENTIALS` of a wrong password): it is not a JWT's, and nothing is renewed;
|
|
5
|
+
// - a 403 is not renewed either: right after a signup or a login the store may answer 403 "Customer not found" for a moment (it answers before it has saved), and that is not "log out";
|
|
6
|
+
// - one renewal however many requests were refused at once (the store's `renew` is one at a time), and a request refused with a JWT that is already not the current one just asks again with the current;
|
|
7
|
+
// - a request is asked again ONCE: if the new JWT is refused too, that 401 is the answer (the session is not ended for it: something else is wrong);
|
|
8
|
+
// - if the renewal fails the caller gets the 401 it got, not the renewal's error: the request was refused, and the session says why (signedOut, or the store not reached);
|
|
9
|
+
// - a caller that sets `Authorization` itself is left alone: it is not asking for the customer's JWT.
|
|
10
|
+
/** What the paths of the refresh token itself do: they are never renewed (they are asked for with the cookie and the CSRF token). */
|
|
11
|
+
const NOT_RENEWED = /\/store-customers\/auth\/refresh-token$/;
|
|
12
|
+
export function withTokenRenewal(http, store) {
|
|
13
|
+
return {
|
|
14
|
+
get baseUrl() { return http.baseUrl; },
|
|
15
|
+
get storeId() { return http.storeId; },
|
|
16
|
+
async request(options) {
|
|
17
|
+
const own = Object.keys(options.headers ?? {}).some((name) => name.toLowerCase() === 'authorization');
|
|
18
|
+
if (own)
|
|
19
|
+
return http.request(options);
|
|
20
|
+
const token = await store.accessToken();
|
|
21
|
+
const withToken = (jwt) => ({ ...options, headers: { ...options.headers, ...(jwt === null ? {} : { Authorization: `Bearer ${jwt}` }) } });
|
|
22
|
+
try {
|
|
23
|
+
return await http.request(withToken(token));
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
const refused = isCommerceError(error) && error.kind === 'http' && error.status === 401 && error.code === null;
|
|
27
|
+
if (!refused || token === null || NOT_RENEWED.test(options.path) || options.signal?.aborted)
|
|
28
|
+
throw error;
|
|
29
|
+
const renewed = await store.renew(token);
|
|
30
|
+
if (renewed === null)
|
|
31
|
+
throw error;
|
|
32
|
+
return http.request(withToken(renewed));
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=renewal.js.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { CommerceError } from '../errors.js';
|
|
2
|
+
import type { AuthApi } from './api.js';
|
|
3
|
+
import type { AuthStorage } from './cookies.js';
|
|
4
|
+
import type { WakePort } from './wake.js';
|
|
5
|
+
import { type AuthSession, type LoginInput, type SignupInput, type TwoFactorChallenge } from './types.js';
|
|
6
|
+
export type AuthStatus = 'unknown' | 'signedOut' | 'signingIn' | 'challenge' | 'signedIn';
|
|
7
|
+
/** The customer, as the store says: the session without what is the session's (the JWT, the CSRF token). */
|
|
8
|
+
export type Customer = Omit<AuthSession, 'token' | 'csrfToken' | 'expiresIn' | 'extraData'>;
|
|
9
|
+
export interface AuthState {
|
|
10
|
+
status: AuthStatus;
|
|
11
|
+
customer: Customer | null;
|
|
12
|
+
/** The code of the authenticator app is wanted for this (status `challenge`). */
|
|
13
|
+
challenge: TwoFactorChallenge | null;
|
|
14
|
+
/** What stopped the session from being renewed or restored without ending it (the store not reached): it goes when it works again. An action that fails does not put its error here: it throws it. */
|
|
15
|
+
error: CommerceError | null;
|
|
16
|
+
}
|
|
17
|
+
export interface AuthStore {
|
|
18
|
+
getState(): AuthState;
|
|
19
|
+
subscribe(listener: () => void): () => void;
|
|
20
|
+
/** Takes the customer back if a session was kept: asks for a new JWT. Once; the same promise for whoever asks while it is under way. */
|
|
21
|
+
restore(): Promise<void>;
|
|
22
|
+
signup(input: Omit<SignupInput, 'deviceId'>, options?: {
|
|
23
|
+
language?: string;
|
|
24
|
+
}): Promise<void>;
|
|
25
|
+
/** Logs in: `signedIn`, or `challenge` when the customer has two-factor authentication on (then `verifyTwoFactor`). A refused login throws what the store said and leaves the state as it was. */
|
|
26
|
+
login(input: Omit<LoginInput, 'deviceId'>): Promise<void>;
|
|
27
|
+
/** The code of the app, for the challenge: `signedIn`. A wrong code throws and leaves the challenge to try again; a challenge that ended is `signedOut`. */
|
|
28
|
+
verifyTwoFactor(totpCode: string): Promise<void>;
|
|
29
|
+
/** Gives up on a challenge. */
|
|
30
|
+
cancelChallenge(): void;
|
|
31
|
+
/** Ends the session here (always) and at the store (if it can be reached). Never throws. */
|
|
32
|
+
logout(): Promise<void>;
|
|
33
|
+
/** A new JWT from the refresh token. Once at a time. True when there is a session after it. */
|
|
34
|
+
refresh(): Promise<boolean>;
|
|
35
|
+
/** The JWT to send, renewed first if it is about to end; null when nobody is logged in. */
|
|
36
|
+
accessToken(): Promise<string | null>;
|
|
37
|
+
/** The store did not accept this JWT: the one to ask again with. The current one if it is not the one that was refused (another request already renewed it); else a new one (one renewal at a time); null when there is none (not logged in any more, or the renewal did not work). */
|
|
38
|
+
renew(rejected: string): Promise<string | null>;
|
|
39
|
+
/** `{ Authorization: 'Bearer …' }` or `{}`: what `getHeaders` of a client gives. */
|
|
40
|
+
authHeaders(): Promise<Record<string, string>>;
|
|
41
|
+
/** Takes a session that was made elsewhere (a confirmed two-factor setup answers one): the same as a login. */
|
|
42
|
+
accept(session: AuthSession): void;
|
|
43
|
+
/** Stops what runs by itself (the timer, the wake port). The store still answers what it is asked. */
|
|
44
|
+
dispose(): void;
|
|
45
|
+
}
|
|
46
|
+
export interface AuthStoreOptions {
|
|
47
|
+
/**
|
|
48
|
+
* What the store asks of commerce-core. It must NOT be made over a client that gets its JWT from this store (`authHeaders`): a refresh and a login are asked for while there may be no JWT, and a client that
|
|
49
|
+
* waits for the store's JWT in order to ask the store for a JWT waits for itself. `createAuth` makes it right; the JWT of the one call that needs it here (logout) is given explicitly.
|
|
50
|
+
*/
|
|
51
|
+
api: AuthApi;
|
|
52
|
+
storage: AuthStorage;
|
|
53
|
+
/** What the device says besides its id, in each signup and login (`platform: 'web'`). */
|
|
54
|
+
device?: {
|
|
55
|
+
platform?: string;
|
|
56
|
+
region?: string;
|
|
57
|
+
};
|
|
58
|
+
/** A JWT with less than this left (ms) is renewed before it is used, and by the timer. Default a minute. */
|
|
59
|
+
renewBeforeMs?: number;
|
|
60
|
+
/** The time, in ms. */
|
|
61
|
+
now?: () => number;
|
|
62
|
+
timers?: {
|
|
63
|
+
setTimeout(fn: () => void, ms: number): unknown;
|
|
64
|
+
clearTimeout(handle: unknown): void;
|
|
65
|
+
};
|
|
66
|
+
/** Says when the page is back (visible again, online again, restored from a sleep): the store then looks at its JWT at once, since a timer does not run while a machine sleeps. None by default. */
|
|
67
|
+
wake?: WakePort;
|
|
68
|
+
}
|
|
69
|
+
export declare function createAuthStore(options: AuthStoreOptions): AuthStore;
|
|
70
|
+
//# sourceMappingURL=store.d.ts.map
|