@moonbase.sh/storefront-api 3.5.1 → 3.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/README.md +62 -0
- package/dist/index.cjs +178 -26
- package/dist/index.d.cts +150 -2
- package/dist/index.d.ts +150 -2
- package/dist/index.js +176 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -110,6 +110,68 @@ another tenant's) arrives as the same `NotFoundError`, so that a signed-in
|
|
|
110
110
|
customer cannot use it to enumerate a merchant's group IDs. Joining twice
|
|
111
111
|
succeeds and changes nothing.
|
|
112
112
|
|
|
113
|
+
### Captchas on sign-up
|
|
114
|
+
|
|
115
|
+
A merchant can ask customers to solve a captcha before they sign up. The vendor
|
|
116
|
+
says whether this store does:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
const vendor = await client.vendor.get()
|
|
120
|
+
const captcha = vendor.features?.captcha // { url } when it does, null or undefined when not
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`captcha.url` is a page on the store's own `<account-id>.moonbase.sh` that draws
|
|
124
|
+
the challenge. Embed it in an iframe with `action=signup`, and it hands you the
|
|
125
|
+
solution with `postMessage`:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const src = new URL(captcha.url)
|
|
129
|
+
src.searchParams.set('action', 'signup') // optional: theme=light|dark|auto, size=normal|compact
|
|
130
|
+
|
|
131
|
+
const frame = document.createElement('iframe')
|
|
132
|
+
frame.src = src.href
|
|
133
|
+
frame.title = 'Captcha'
|
|
134
|
+
frame.style.cssText = 'border: none; color-scheme: normal; width: 300px; height: 65px'
|
|
135
|
+
form.append(frame)
|
|
136
|
+
|
|
137
|
+
let captchaToken: string | undefined
|
|
138
|
+
window.addEventListener('message', (event) => {
|
|
139
|
+
if (event.source !== frame.contentWindow || event.origin !== src.origin || event.data?.source !== 'moonbase-captcha')
|
|
140
|
+
return
|
|
141
|
+
if (event.data.event === 'resize')
|
|
142
|
+
Object.assign(frame.style, { width: `${event.data.width}px`, height: `${event.data.height}px` })
|
|
143
|
+
else if (event.data.event === 'token')
|
|
144
|
+
captchaToken = event.data.token
|
|
145
|
+
else // 'expired', or 'error' with a `code` ('load-failed' when the challenge could not load)
|
|
146
|
+
captchaToken = undefined
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Then pass the solution along, and reset the frame after every attempt, since a
|
|
151
|
+
solution is good for one:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
try {
|
|
155
|
+
await client.identity.signUp('Jane', 'jane@example.com', 'password', null, true, true, true, {
|
|
156
|
+
captchaToken,
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
if (err instanceof CaptchaRequiredError)
|
|
161
|
+
showError(err.message) // the API's own copy, safe to show
|
|
162
|
+
throw err
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
captchaToken = undefined
|
|
166
|
+
frame.contentWindow?.postMessage({ source: 'moonbase-captcha', command: 'reset' }, src.origin)
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Without a solution, or with one that is expired or already used, the sign-up is
|
|
171
|
+
refused with `CaptchaRequiredError` (a `MoonbaseError`). A site with a
|
|
172
|
+
Content-Security-Policy has to allow the store's `https://<account-id>.moonbase.sh`
|
|
173
|
+
in `frame-src`.
|
|
174
|
+
|
|
113
175
|
## Inventory flow
|
|
114
176
|
|
|
115
177
|
Most inventory endpoints require an authenticated user:
|
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
ActivationRequestStatus: () => ActivationRequestStatus,
|
|
26
26
|
ActivationStatus: () => ActivationStatus,
|
|
27
27
|
Architecture: () => Architecture,
|
|
28
|
+
CaptchaRequiredError: () => CaptchaRequiredError,
|
|
28
29
|
CartUtils: () => CartUtils,
|
|
29
30
|
ConnectableAccountProvider: () => ConnectableAccountProvider,
|
|
30
31
|
ConsoleLogger: () => ConsoleLogger,
|
|
@@ -50,6 +51,7 @@ __export(index_exports, {
|
|
|
50
51
|
SubscriptionCancellationReason: () => SubscriptionCancellationReason,
|
|
51
52
|
SubscriptionStatus: () => SubscriptionStatus,
|
|
52
53
|
TokenStore: () => TokenStore,
|
|
54
|
+
accessTokenLifetime: () => accessTokenLifetime,
|
|
53
55
|
imageWidths: () => imageWidths,
|
|
54
56
|
isLocalStorageAvailable: () => isLocalStorageAvailable,
|
|
55
57
|
isPromotionDisplay: () => isPromotionDisplay,
|
|
@@ -459,7 +461,10 @@ var activationRequestSchema = import_zod4.z.object({
|
|
|
459
461
|
requiresAccount: import_zod4.z.boolean(),
|
|
460
462
|
requiresNewsletterOptIn: import_zod4.z.boolean(),
|
|
461
463
|
numberOfDaysPerProduct: import_zod4.z.number(),
|
|
462
|
-
numberOfDaysRemaining: import_zod4.z.number()
|
|
464
|
+
numberOfDaysRemaining: import_zod4.z.number(),
|
|
465
|
+
// The trial never expires, so the day counts are not worth showing. Absent from a backend that
|
|
466
|
+
// predates endless trials.
|
|
467
|
+
endless: import_zod4.z.boolean().default(false)
|
|
463
468
|
}).optional(),
|
|
464
469
|
licenseEligibility: import_zod4.z.object({
|
|
465
470
|
eligible: import_zod4.z.boolean()
|
|
@@ -657,6 +662,12 @@ var MoonbaseError = class extends Error {
|
|
|
657
662
|
}
|
|
658
663
|
}
|
|
659
664
|
};
|
|
665
|
+
var CaptchaRequiredError = class extends MoonbaseError {
|
|
666
|
+
constructor(detail, title) {
|
|
667
|
+
super(title != null ? title : "Captcha required", detail != null ? detail : "Please confirm you're not a robot.", 400);
|
|
668
|
+
this.name = "CaptchaRequiredError";
|
|
669
|
+
}
|
|
670
|
+
};
|
|
660
671
|
|
|
661
672
|
// src/utils/problemDetails.ts
|
|
662
673
|
var import_zod7 = require("zod");
|
|
@@ -699,6 +710,8 @@ async function handleResponseProblem(response, logger) {
|
|
|
699
710
|
logger.debug("The response indicates a problem", problemDetails);
|
|
700
711
|
if (problemDetails.errorType === "StoreClosed")
|
|
701
712
|
throw new StoreClosedError(problemDetails.detail);
|
|
713
|
+
if (problemDetails.errorType === "CaptchaRequired")
|
|
714
|
+
throw new CaptchaRequiredError(problemDetails.detail, problemDetails.title);
|
|
702
715
|
throw new MoonbaseError(
|
|
703
716
|
problemDetails.title,
|
|
704
717
|
problemDetails.detail,
|
|
@@ -835,7 +848,8 @@ var IdentityEndpoints = class {
|
|
|
835
848
|
"Accept": "application/json",
|
|
836
849
|
"Content-Type": "application/json",
|
|
837
850
|
"x-mb-cors": "1",
|
|
838
|
-
"x-mb-client": this.api.clientHeader
|
|
851
|
+
"x-mb-client": this.api.clientHeader,
|
|
852
|
+
...(options == null ? void 0 : options.captchaToken) ? { "X-MB-CAPTCHA": options.captchaToken } : {}
|
|
839
853
|
},
|
|
840
854
|
body: JSON.stringify({
|
|
841
855
|
name,
|
|
@@ -864,8 +878,13 @@ var IdentityEndpoints = class {
|
|
|
864
878
|
this.tokenStore.setUser(identity);
|
|
865
879
|
return { status: "signed-in", user: userSchema.parse(identity) };
|
|
866
880
|
}
|
|
881
|
+
/**
|
|
882
|
+
* Signs the buyer out here at once, and ends the session on the server so its refresh
|
|
883
|
+
* token stops working too. Resolves once the server has answered, and never rejects:
|
|
884
|
+
* the buyer is signed out in this browser whatever happens to that request.
|
|
885
|
+
*/
|
|
867
886
|
async signOut() {
|
|
868
|
-
this.tokenStore.
|
|
887
|
+
await this.tokenStore.signOut();
|
|
869
888
|
}
|
|
870
889
|
async update(name, email, emailConfirmationToken, communicationPreferences) {
|
|
871
890
|
const response = await this.api.authenticatedFetch("/api/customer/identity", userSchema, {
|
|
@@ -1098,7 +1117,7 @@ var ProductEndpoints = class {
|
|
|
1098
1117
|
var import_zod14 = require("zod");
|
|
1099
1118
|
|
|
1100
1119
|
// package.json
|
|
1101
|
-
var version = "3.
|
|
1120
|
+
var version = "3.7.0";
|
|
1102
1121
|
|
|
1103
1122
|
// src/utils/client.ts
|
|
1104
1123
|
function buildClientHeader(sdk) {
|
|
@@ -1777,24 +1796,36 @@ var InMemoryStore = class {
|
|
|
1777
1796
|
};
|
|
1778
1797
|
|
|
1779
1798
|
// src/utils/tokenStore.ts
|
|
1799
|
+
var defaultAccessTokenLifetime = 15 * 60 * 1e3;
|
|
1800
|
+
var renewBeforeExpiry = 5 * 60 * 1e3;
|
|
1801
|
+
var expiryMargin = 30 * 1e3;
|
|
1802
|
+
var refreshLockName = "moonbase_auth_refresh";
|
|
1803
|
+
function accessTokenLifetime(accessToken) {
|
|
1804
|
+
try {
|
|
1805
|
+
const payload = accessToken.split(".")[1].replaceAll("-", "+").replaceAll("_", "/");
|
|
1806
|
+
const claims = JSON.parse(atob(payload.padEnd(Math.ceil(payload.length / 4) * 4, "=")));
|
|
1807
|
+
const lifetime = (Number(claims.exp) - Number(claims.iat)) * 1e3;
|
|
1808
|
+
if (Number.isFinite(lifetime) && lifetime > 0 && lifetime <= 24 * 60 * 60 * 1e3)
|
|
1809
|
+
return lifetime;
|
|
1810
|
+
} catch (e) {
|
|
1811
|
+
}
|
|
1812
|
+
return defaultAccessTokenLifetime;
|
|
1813
|
+
}
|
|
1780
1814
|
var _TokenStore = class _TokenStore {
|
|
1781
1815
|
constructor(configuration) {
|
|
1782
1816
|
this.configuration = configuration;
|
|
1783
1817
|
this.tokens = null;
|
|
1784
1818
|
this.refreshTimeoutId = null;
|
|
1785
1819
|
this.refreshPromise = null;
|
|
1820
|
+
this.waitingForVisibility = false;
|
|
1821
|
+
this.sessionEndedListeners = [];
|
|
1786
1822
|
var _a, _b;
|
|
1787
1823
|
this.logger = (_a = configuration.logger) != null ? _a : new ConsoleLogger();
|
|
1788
1824
|
this.store = (_b = configuration.store) != null ? _b : isLocalStorageAvailable() ? new LocalStorageStore((err) => this.warnAboutStorage(err)) : new InMemoryStore();
|
|
1789
1825
|
if (typeof window !== "undefined" && !configuration.store && !(this.store instanceof LocalStorageStore))
|
|
1790
1826
|
this.warnAboutStorage();
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
this.tokens = {
|
|
1794
|
-
...storedToken,
|
|
1795
|
-
expiresAt: new Date(storedToken.expiresAt)
|
|
1796
|
-
};
|
|
1797
|
-
}
|
|
1827
|
+
this.tokens = this.readStoredTokens();
|
|
1828
|
+
this.scheduleRefresh();
|
|
1798
1829
|
this.store.listen(_TokenStore.storageKey, (updatedTokens) => this.handleStorageUpdate(updatedTokens));
|
|
1799
1830
|
}
|
|
1800
1831
|
get user() {
|
|
@@ -1828,29 +1859,91 @@ var _TokenStore = class _TokenStore {
|
|
|
1828
1859
|
} else {
|
|
1829
1860
|
this.tokens = {
|
|
1830
1861
|
...identity,
|
|
1831
|
-
|
|
1832
|
-
expiresAt: new Date((/* @__PURE__ */ new Date()).getTime() + 15 * 60 * 1e3)
|
|
1862
|
+
expiresAt: new Date(Date.now() + accessTokenLifetime(identity.accessToken))
|
|
1833
1863
|
};
|
|
1834
1864
|
}
|
|
1835
1865
|
this.store.set(_TokenStore.storageKey, this.tokens);
|
|
1836
|
-
|
|
1837
|
-
if (this.refreshTimeoutId != null)
|
|
1838
|
-
window.clearTimeout(this.refreshTimeoutId);
|
|
1839
|
-
this.refreshTimeoutId = window.setTimeout(() => {
|
|
1840
|
-
this.startRefresh().catch((err) => this.logger.warn("Could not refresh access token", err));
|
|
1841
|
-
}, 10 * 60 * 1e3);
|
|
1842
|
-
}
|
|
1866
|
+
this.scheduleRefresh();
|
|
1843
1867
|
return this.tokens;
|
|
1844
1868
|
}
|
|
1845
1869
|
clear() {
|
|
1846
1870
|
this.tokens = null;
|
|
1847
1871
|
this.store.remove(_TokenStore.storageKey);
|
|
1848
|
-
|
|
1849
|
-
window.clearTimeout(this.refreshTimeoutId);
|
|
1872
|
+
this.scheduleRefresh();
|
|
1850
1873
|
return null;
|
|
1851
1874
|
}
|
|
1875
|
+
async signOut() {
|
|
1876
|
+
const tokens = this.tokens;
|
|
1877
|
+
this.clear();
|
|
1878
|
+
if (!tokens)
|
|
1879
|
+
return;
|
|
1880
|
+
try {
|
|
1881
|
+
await fetch(`${this.configuration.endpoint}/api/customer/identity/revoke?token=${encodeURIComponent(tokens.refreshToken)}`, {
|
|
1882
|
+
method: "POST",
|
|
1883
|
+
keepalive: true,
|
|
1884
|
+
headers: {
|
|
1885
|
+
"Accept": "application/json",
|
|
1886
|
+
"Content-Type": "text/plain",
|
|
1887
|
+
"x-mb-cors": "1",
|
|
1888
|
+
"x-mb-client": buildClientHeader(this.configuration.sdk)
|
|
1889
|
+
},
|
|
1890
|
+
body: tokens.accessToken
|
|
1891
|
+
});
|
|
1892
|
+
} catch (err) {
|
|
1893
|
+
this.logger.warn("Could not end the session on the server", err);
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
onSessionEnded(listener) {
|
|
1897
|
+
this.sessionEndedListeners.push(listener);
|
|
1898
|
+
}
|
|
1852
1899
|
get isExpired() {
|
|
1853
|
-
return this.tokens != null && this.tokens.expiresAt
|
|
1900
|
+
return this.tokens != null && this.tokens.expiresAt.getTime() - expiryMargin < Date.now();
|
|
1901
|
+
}
|
|
1902
|
+
readStoredTokens() {
|
|
1903
|
+
const stored = this.store.get(_TokenStore.storageKey);
|
|
1904
|
+
return stored ? { ...stored, expiresAt: new Date(stored.expiresAt) } : null;
|
|
1905
|
+
}
|
|
1906
|
+
/**
|
|
1907
|
+
* (Re)arms the timer that renews the access token shortly before it runs out, or disarms
|
|
1908
|
+
* it when there is nothing to renew. Every tab arms one, and `refreshTokens` makes sure
|
|
1909
|
+
* only one of them actually renews.
|
|
1910
|
+
*/
|
|
1911
|
+
scheduleRefresh() {
|
|
1912
|
+
if (typeof window === "undefined")
|
|
1913
|
+
return;
|
|
1914
|
+
if (this.refreshTimeoutId != null)
|
|
1915
|
+
window.clearTimeout(this.refreshTimeoutId);
|
|
1916
|
+
this.refreshTimeoutId = null;
|
|
1917
|
+
if (!this.tokens)
|
|
1918
|
+
return;
|
|
1919
|
+
const delay = Math.max(0, this.renewalDueAt(this.tokens) - Date.now());
|
|
1920
|
+
this.refreshTimeoutId = window.setTimeout(() => this.renewInBackground(), delay);
|
|
1921
|
+
}
|
|
1922
|
+
renewalDueAt(tokens) {
|
|
1923
|
+
const lead = Math.min(renewBeforeExpiry, accessTokenLifetime(tokens.accessToken) / 3);
|
|
1924
|
+
return tokens.expiresAt.getTime() - lead;
|
|
1925
|
+
}
|
|
1926
|
+
renewInBackground() {
|
|
1927
|
+
var _a;
|
|
1928
|
+
this.refreshTimeoutId = null;
|
|
1929
|
+
if (!this.tokens)
|
|
1930
|
+
return;
|
|
1931
|
+
if (this.renewalDueAt(this.tokens) > Date.now()) {
|
|
1932
|
+
this.scheduleRefresh();
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
1936
|
+
if (!this.waitingForVisibility) {
|
|
1937
|
+
this.waitingForVisibility = true;
|
|
1938
|
+
document.addEventListener("visibilitychange", () => {
|
|
1939
|
+
this.waitingForVisibility = false;
|
|
1940
|
+
this.renewInBackground();
|
|
1941
|
+
}, { once: true });
|
|
1942
|
+
}
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
const pending = (_a = this.refreshPromise) != null ? _a : this.startRefresh();
|
|
1946
|
+
pending.catch((err) => this.logger.warn("Could not refresh access token", err));
|
|
1854
1947
|
}
|
|
1855
1948
|
/**
|
|
1856
1949
|
* Start a refresh and hold it so concurrent callers join it rather than firing
|
|
@@ -1867,7 +1960,45 @@ var _TokenStore = class _TokenStore {
|
|
|
1867
1960
|
this.refreshPromise = pending;
|
|
1868
1961
|
return pending;
|
|
1869
1962
|
}
|
|
1963
|
+
/**
|
|
1964
|
+
* A refresh token can be used once: the server answers a second use by revoking the
|
|
1965
|
+
* session, taking it for a stolen token. So refreshes are serialised across every tab of
|
|
1966
|
+
* the origin, and whoever gets the lock second takes up what the first one stored rather
|
|
1967
|
+
* than spending the same refresh token again. The stored copy has to be read, not waited
|
|
1968
|
+
* for: storage events never reach the document that wrote them, so a second client on
|
|
1969
|
+
* the same page would not hear about it.
|
|
1970
|
+
*/
|
|
1870
1971
|
async refreshTokens() {
|
|
1972
|
+
var _a;
|
|
1973
|
+
const spending = (_a = this.tokens) == null ? void 0 : _a.refreshToken;
|
|
1974
|
+
const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
|
|
1975
|
+
if (locks == null ? void 0 : locks.request) {
|
|
1976
|
+
let held = false;
|
|
1977
|
+
try {
|
|
1978
|
+
return await locks.request(refreshLockName, () => {
|
|
1979
|
+
held = true;
|
|
1980
|
+
return this.refreshHoldingLock(spending);
|
|
1981
|
+
});
|
|
1982
|
+
} catch (err) {
|
|
1983
|
+
if (held)
|
|
1984
|
+
throw err;
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
return await this.refreshHoldingLock(spending);
|
|
1988
|
+
}
|
|
1989
|
+
/**
|
|
1990
|
+
* @param spending The refresh token this refresh set out to use. If the session has moved
|
|
1991
|
+
* on from it while we waited for the lock, someone else has renewed already.
|
|
1992
|
+
*/
|
|
1993
|
+
async refreshHoldingLock(spending) {
|
|
1994
|
+
var _a;
|
|
1995
|
+
const stored = this.readStoredTokens();
|
|
1996
|
+
if (stored && stored.refreshToken !== ((_a = this.tokens) == null ? void 0 : _a.refreshToken))
|
|
1997
|
+
this.tokens = stored;
|
|
1998
|
+
if (this.tokens && this.tokens.refreshToken !== spending && !this.isExpired) {
|
|
1999
|
+
this.scheduleRefresh();
|
|
2000
|
+
return this.tokens;
|
|
2001
|
+
}
|
|
1871
2002
|
if (!this.tokens)
|
|
1872
2003
|
throw new Error("No tokens found to refresh");
|
|
1873
2004
|
const response = await fetch(`${this.configuration.endpoint}/api/customer/identity/refresh?token=${encodeURIComponent(this.tokens.refreshToken)}`, {
|
|
@@ -1883,6 +2014,13 @@ var _TokenStore = class _TokenStore {
|
|
|
1883
2014
|
if (response.status !== 200) {
|
|
1884
2015
|
if (response.status === 403 || response.status === 404) {
|
|
1885
2016
|
this.clear();
|
|
2017
|
+
for (const listener of this.sessionEndedListeners) {
|
|
2018
|
+
try {
|
|
2019
|
+
listener();
|
|
2020
|
+
} catch (err) {
|
|
2021
|
+
this.logger.warn("A listener for the end of the session failed", err);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
1886
2024
|
return null;
|
|
1887
2025
|
}
|
|
1888
2026
|
throw new MoonbaseError("Unexpected result", `Could not refresh access token, status code ${response.status}`, response.status);
|
|
@@ -1893,11 +2031,10 @@ var _TokenStore = class _TokenStore {
|
|
|
1893
2031
|
}
|
|
1894
2032
|
handleStorageUpdate(updatedTokens) {
|
|
1895
2033
|
this.tokens = updatedTokens;
|
|
1896
|
-
if (this.refreshTimeoutId != null && typeof window !== "undefined")
|
|
1897
|
-
window.clearTimeout(this.refreshTimeoutId);
|
|
1898
2034
|
if (this.tokens) {
|
|
1899
2035
|
this.tokens.expiresAt = new Date(this.tokens.expiresAt);
|
|
1900
2036
|
}
|
|
2037
|
+
this.scheduleRefresh();
|
|
1901
2038
|
}
|
|
1902
2039
|
warnAboutStorage(err) {
|
|
1903
2040
|
this.logger.warn(
|
|
@@ -1912,13 +2049,26 @@ var TokenStore = _TokenStore;
|
|
|
1912
2049
|
// src/vendor/schemas.ts
|
|
1913
2050
|
var schemas_exports9 = {};
|
|
1914
2051
|
__export(schemas_exports9, {
|
|
2052
|
+
vendorCaptchaSchema: () => vendorCaptchaSchema,
|
|
2053
|
+
vendorFeaturesSchema: () => vendorFeaturesSchema,
|
|
1915
2054
|
vendorSchema: () => vendorSchema
|
|
1916
2055
|
});
|
|
1917
2056
|
var import_zod15 = require("zod");
|
|
2057
|
+
var vendorCaptchaSchema = import_zod15.z.object({
|
|
2058
|
+
url: import_zod15.z.string()
|
|
2059
|
+
});
|
|
2060
|
+
var vendorFeaturesSchema = import_zod15.z.object({
|
|
2061
|
+
customerPortal: import_zod15.z.boolean().optional(),
|
|
2062
|
+
phoneNumber: import_zod15.z.boolean().optional(),
|
|
2063
|
+
marketingConsent: import_zod15.z.nativeEnum(MarketingConsentType).optional().catch(void 0),
|
|
2064
|
+
// Absent on backends from before captchas, which never ask for one.
|
|
2065
|
+
captcha: vendorCaptchaSchema.nullish()
|
|
2066
|
+
});
|
|
1918
2067
|
var vendorSchema = import_zod15.z.object({
|
|
1919
2068
|
id: import_zod15.z.string(),
|
|
1920
2069
|
name: import_zod15.z.string(),
|
|
1921
|
-
logoUrl: import_zod15.z.string().nullable()
|
|
2070
|
+
logoUrl: import_zod15.z.string().nullable(),
|
|
2071
|
+
features: vendorFeaturesSchema.optional()
|
|
1922
2072
|
});
|
|
1923
2073
|
|
|
1924
2074
|
// src/vendor/endpoints.ts
|
|
@@ -2614,6 +2764,7 @@ var MoonbaseClient = class {
|
|
|
2614
2764
|
ActivationRequestStatus,
|
|
2615
2765
|
ActivationStatus,
|
|
2616
2766
|
Architecture,
|
|
2767
|
+
CaptchaRequiredError,
|
|
2617
2768
|
CartUtils,
|
|
2618
2769
|
ConnectableAccountProvider,
|
|
2619
2770
|
ConsoleLogger,
|
|
@@ -2639,6 +2790,7 @@ var MoonbaseClient = class {
|
|
|
2639
2790
|
SubscriptionCancellationReason,
|
|
2640
2791
|
SubscriptionStatus,
|
|
2641
2792
|
TokenStore,
|
|
2793
|
+
accessTokenLifetime,
|
|
2642
2794
|
imageWidths,
|
|
2643
2795
|
isLocalStorageAvailable,
|
|
2644
2796
|
isPromotionDisplay,
|
package/dist/index.d.cts
CHANGED
|
@@ -80,18 +80,28 @@ declare class InMemoryStore implements IStore {
|
|
|
80
80
|
type IdentityWithExpiry = Identity & {
|
|
81
81
|
expiresAt: Date;
|
|
82
82
|
};
|
|
83
|
+
/**
|
|
84
|
+
* How long the server issued an access token for, from its `iat` and `exp`. The lifetime rather than
|
|
85
|
+
* `exp` itself, measured against this browser's clock from the moment the token arrives: a clock that is
|
|
86
|
+
* minutes off would otherwise make every token look expired, or none.
|
|
87
|
+
*/
|
|
88
|
+
declare function accessTokenLifetime(accessToken: string): number;
|
|
83
89
|
interface ITokenStore {
|
|
84
90
|
get user(): User | null;
|
|
85
91
|
get isPersistent(): boolean;
|
|
86
92
|
getAccessToken: () => Promise<string | null>;
|
|
87
93
|
setUser: (user: Identity | User) => User | null;
|
|
88
94
|
clear: () => void;
|
|
95
|
+
signOut: () => Promise<void>;
|
|
96
|
+
onSessionEnded: (listener: () => void) => void;
|
|
89
97
|
}
|
|
90
98
|
declare class TokenStore implements ITokenStore {
|
|
91
99
|
private configuration;
|
|
92
100
|
private tokens;
|
|
93
101
|
private refreshTimeoutId;
|
|
94
102
|
private refreshPromise;
|
|
103
|
+
private waitingForVisibility;
|
|
104
|
+
private readonly sessionEndedListeners;
|
|
95
105
|
private readonly store;
|
|
96
106
|
private readonly logger;
|
|
97
107
|
static readonly storageKey = "moonbase_auth";
|
|
@@ -101,7 +111,18 @@ declare class TokenStore implements ITokenStore {
|
|
|
101
111
|
getAccessToken(): Promise<string | null>;
|
|
102
112
|
setUser(user: Identity | User): IdentityWithExpiry | null;
|
|
103
113
|
clear(): null;
|
|
114
|
+
signOut(): Promise<void>;
|
|
115
|
+
onSessionEnded(listener: () => void): void;
|
|
104
116
|
private get isExpired();
|
|
117
|
+
private readStoredTokens;
|
|
118
|
+
/**
|
|
119
|
+
* (Re)arms the timer that renews the access token shortly before it runs out, or disarms
|
|
120
|
+
* it when there is nothing to renew. Every tab arms one, and `refreshTokens` makes sure
|
|
121
|
+
* only one of them actually renews.
|
|
122
|
+
*/
|
|
123
|
+
private scheduleRefresh;
|
|
124
|
+
private renewalDueAt;
|
|
125
|
+
private renewInBackground;
|
|
105
126
|
/**
|
|
106
127
|
* Start a refresh and hold it so concurrent callers join it rather than firing
|
|
107
128
|
* their own. The slot is cleared as soon as it settles: a retained promise
|
|
@@ -110,7 +131,20 @@ declare class TokenStore implements ITokenStore {
|
|
|
110
131
|
* anonymous ones) for the rest of the session.
|
|
111
132
|
*/
|
|
112
133
|
private startRefresh;
|
|
134
|
+
/**
|
|
135
|
+
* A refresh token can be used once: the server answers a second use by revoking the
|
|
136
|
+
* session, taking it for a stolen token. So refreshes are serialised across every tab of
|
|
137
|
+
* the origin, and whoever gets the lock second takes up what the first one stored rather
|
|
138
|
+
* than spending the same refresh token again. The stored copy has to be read, not waited
|
|
139
|
+
* for: storage events never reach the document that wrote them, so a second client on
|
|
140
|
+
* the same page would not hear about it.
|
|
141
|
+
*/
|
|
113
142
|
private refreshTokens;
|
|
143
|
+
/**
|
|
144
|
+
* @param spending The refresh token this refresh set out to use. If the session has moved
|
|
145
|
+
* on from it while we waited for the lock, someone else has renewed already.
|
|
146
|
+
*/
|
|
147
|
+
private refreshHoldingLock;
|
|
114
148
|
private handleStorageUpdate;
|
|
115
149
|
private warnAboutStorage;
|
|
116
150
|
}
|
|
@@ -797,6 +831,7 @@ declare const activationRequestSchema: z.ZodObject<{
|
|
|
797
831
|
requiresNewsletterOptIn: z.ZodBoolean;
|
|
798
832
|
numberOfDaysPerProduct: z.ZodNumber;
|
|
799
833
|
numberOfDaysRemaining: z.ZodNumber;
|
|
834
|
+
endless: z.ZodDefault<z.ZodBoolean>;
|
|
800
835
|
}, "strip", z.ZodTypeAny, {
|
|
801
836
|
eligible: boolean;
|
|
802
837
|
existing: boolean;
|
|
@@ -804,6 +839,7 @@ declare const activationRequestSchema: z.ZodObject<{
|
|
|
804
839
|
requiresNewsletterOptIn: boolean;
|
|
805
840
|
numberOfDaysPerProduct: number;
|
|
806
841
|
numberOfDaysRemaining: number;
|
|
842
|
+
endless: boolean;
|
|
807
843
|
}, {
|
|
808
844
|
eligible: boolean;
|
|
809
845
|
existing: boolean;
|
|
@@ -811,6 +847,7 @@ declare const activationRequestSchema: z.ZodObject<{
|
|
|
811
847
|
requiresNewsletterOptIn: boolean;
|
|
812
848
|
numberOfDaysPerProduct: number;
|
|
813
849
|
numberOfDaysRemaining: number;
|
|
850
|
+
endless?: boolean | undefined;
|
|
814
851
|
}>>;
|
|
815
852
|
licenseEligibility: z.ZodOptional<z.ZodObject<{
|
|
816
853
|
eligible: z.ZodBoolean;
|
|
@@ -937,6 +974,7 @@ declare const activationRequestSchema: z.ZodObject<{
|
|
|
937
974
|
requiresNewsletterOptIn: boolean;
|
|
938
975
|
numberOfDaysPerProduct: number;
|
|
939
976
|
numberOfDaysRemaining: number;
|
|
977
|
+
endless: boolean;
|
|
940
978
|
} | undefined;
|
|
941
979
|
licenseEligibility?: {
|
|
942
980
|
eligible: boolean;
|
|
@@ -1059,6 +1097,7 @@ declare const activationRequestSchema: z.ZodObject<{
|
|
|
1059
1097
|
requiresNewsletterOptIn: boolean;
|
|
1060
1098
|
numberOfDaysPerProduct: number;
|
|
1061
1099
|
numberOfDaysRemaining: number;
|
|
1100
|
+
endless?: boolean | undefined;
|
|
1062
1101
|
} | undefined;
|
|
1063
1102
|
licenseEligibility?: {
|
|
1064
1103
|
eligible: boolean;
|
|
@@ -1677,6 +1716,15 @@ interface SignUpOptions {
|
|
|
1677
1716
|
* ID left in a form can never fail a registration.
|
|
1678
1717
|
*/
|
|
1679
1718
|
groupIds?: string[];
|
|
1719
|
+
/**
|
|
1720
|
+
* The customer's solution to the store's captcha, for stores that ask for one
|
|
1721
|
+
* (`vendor.features.captcha`). Sent in the `X-MB-CAPTCHA` header.
|
|
1722
|
+
*
|
|
1723
|
+
* Without it (or with one that does not check out) such a store refuses the
|
|
1724
|
+
* sign-up with a `CaptchaRequiredError`. Solutions are single-use and expire
|
|
1725
|
+
* after about two minutes, so reset the captcha after every attempt.
|
|
1726
|
+
*/
|
|
1727
|
+
captchaToken?: string;
|
|
1680
1728
|
}
|
|
1681
1729
|
|
|
1682
1730
|
declare class IdentityEndpoints {
|
|
@@ -1687,6 +1735,11 @@ declare class IdentityEndpoints {
|
|
|
1687
1735
|
get(): Promise<User>;
|
|
1688
1736
|
signIn(email: string, password: string): Promise<User>;
|
|
1689
1737
|
signUp(name: string, email: string, password: string, address: Address | null | undefined, acceptedPrivacyPolicy: boolean, acceptedTermsAndConditions: boolean, communicationOptIn?: boolean, options?: SignUpOptions): Promise<SignUpResult>;
|
|
1738
|
+
/**
|
|
1739
|
+
* Signs the buyer out here at once, and ends the session on the server so its refresh
|
|
1740
|
+
* token stops working too. Resolves once the server has answered, and never rejects:
|
|
1741
|
+
* the buyer is signed out in this browser whatever happens to that request.
|
|
1742
|
+
*/
|
|
1690
1743
|
signOut(): Promise<void>;
|
|
1691
1744
|
update(name: string | null, email: string, emailConfirmationToken?: string, communicationPreferences?: CommunicationPreferences): Promise<{
|
|
1692
1745
|
needsEmailConfirmationToken: boolean;
|
|
@@ -24989,26 +25042,110 @@ declare class StorefrontEndpoints {
|
|
|
24989
25042
|
}>;
|
|
24990
25043
|
}
|
|
24991
25044
|
|
|
25045
|
+
/**
|
|
25046
|
+
* The captcha a store asks customers to solve before signing up and checking out. `url` is a
|
|
25047
|
+
* page on the store's own `<account-id>.moonbase.sh` that draws the challenge: embed it in an
|
|
25048
|
+
* iframe, and it hands the solution to its parent with `postMessage`.
|
|
25049
|
+
*/
|
|
25050
|
+
declare const vendorCaptchaSchema: z.ZodObject<{
|
|
25051
|
+
url: z.ZodString;
|
|
25052
|
+
}, "strip", z.ZodTypeAny, {
|
|
25053
|
+
url: string;
|
|
25054
|
+
}, {
|
|
25055
|
+
url: string;
|
|
25056
|
+
}>;
|
|
25057
|
+
declare const vendorFeaturesSchema: z.ZodObject<{
|
|
25058
|
+
customerPortal: z.ZodOptional<z.ZodBoolean>;
|
|
25059
|
+
phoneNumber: z.ZodOptional<z.ZodBoolean>;
|
|
25060
|
+
marketingConsent: z.ZodCatch<z.ZodOptional<z.ZodNativeEnum<typeof MarketingConsentType>>>;
|
|
25061
|
+
captcha: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
25062
|
+
url: z.ZodString;
|
|
25063
|
+
}, "strip", z.ZodTypeAny, {
|
|
25064
|
+
url: string;
|
|
25065
|
+
}, {
|
|
25066
|
+
url: string;
|
|
25067
|
+
}>>>;
|
|
25068
|
+
}, "strip", z.ZodTypeAny, {
|
|
25069
|
+
customerPortal?: boolean | undefined;
|
|
25070
|
+
phoneNumber?: boolean | undefined;
|
|
25071
|
+
marketingConsent?: MarketingConsentType | undefined;
|
|
25072
|
+
captcha?: {
|
|
25073
|
+
url: string;
|
|
25074
|
+
} | null | undefined;
|
|
25075
|
+
}, {
|
|
25076
|
+
customerPortal?: boolean | undefined;
|
|
25077
|
+
phoneNumber?: boolean | undefined;
|
|
25078
|
+
marketingConsent?: unknown;
|
|
25079
|
+
captcha?: {
|
|
25080
|
+
url: string;
|
|
25081
|
+
} | null | undefined;
|
|
25082
|
+
}>;
|
|
24992
25083
|
declare const vendorSchema: z.ZodObject<{
|
|
24993
25084
|
id: z.ZodString;
|
|
24994
25085
|
name: z.ZodString;
|
|
24995
25086
|
logoUrl: z.ZodNullable<z.ZodString>;
|
|
25087
|
+
features: z.ZodOptional<z.ZodObject<{
|
|
25088
|
+
customerPortal: z.ZodOptional<z.ZodBoolean>;
|
|
25089
|
+
phoneNumber: z.ZodOptional<z.ZodBoolean>;
|
|
25090
|
+
marketingConsent: z.ZodCatch<z.ZodOptional<z.ZodNativeEnum<typeof MarketingConsentType>>>;
|
|
25091
|
+
captcha: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
25092
|
+
url: z.ZodString;
|
|
25093
|
+
}, "strip", z.ZodTypeAny, {
|
|
25094
|
+
url: string;
|
|
25095
|
+
}, {
|
|
25096
|
+
url: string;
|
|
25097
|
+
}>>>;
|
|
25098
|
+
}, "strip", z.ZodTypeAny, {
|
|
25099
|
+
customerPortal?: boolean | undefined;
|
|
25100
|
+
phoneNumber?: boolean | undefined;
|
|
25101
|
+
marketingConsent?: MarketingConsentType | undefined;
|
|
25102
|
+
captcha?: {
|
|
25103
|
+
url: string;
|
|
25104
|
+
} | null | undefined;
|
|
25105
|
+
}, {
|
|
25106
|
+
customerPortal?: boolean | undefined;
|
|
25107
|
+
phoneNumber?: boolean | undefined;
|
|
25108
|
+
marketingConsent?: unknown;
|
|
25109
|
+
captcha?: {
|
|
25110
|
+
url: string;
|
|
25111
|
+
} | null | undefined;
|
|
25112
|
+
}>>;
|
|
24996
25113
|
}, "strip", z.ZodTypeAny, {
|
|
24997
25114
|
id: string;
|
|
24998
25115
|
name: string;
|
|
24999
25116
|
logoUrl: string | null;
|
|
25117
|
+
features?: {
|
|
25118
|
+
customerPortal?: boolean | undefined;
|
|
25119
|
+
phoneNumber?: boolean | undefined;
|
|
25120
|
+
marketingConsent?: MarketingConsentType | undefined;
|
|
25121
|
+
captcha?: {
|
|
25122
|
+
url: string;
|
|
25123
|
+
} | null | undefined;
|
|
25124
|
+
} | undefined;
|
|
25000
25125
|
}, {
|
|
25001
25126
|
id: string;
|
|
25002
25127
|
name: string;
|
|
25003
25128
|
logoUrl: string | null;
|
|
25129
|
+
features?: {
|
|
25130
|
+
customerPortal?: boolean | undefined;
|
|
25131
|
+
phoneNumber?: boolean | undefined;
|
|
25132
|
+
marketingConsent?: unknown;
|
|
25133
|
+
captcha?: {
|
|
25134
|
+
url: string;
|
|
25135
|
+
} | null | undefined;
|
|
25136
|
+
} | undefined;
|
|
25004
25137
|
}>;
|
|
25005
25138
|
|
|
25139
|
+
declare const schemas$4_vendorCaptchaSchema: typeof vendorCaptchaSchema;
|
|
25140
|
+
declare const schemas$4_vendorFeaturesSchema: typeof vendorFeaturesSchema;
|
|
25006
25141
|
declare const schemas$4_vendorSchema: typeof vendorSchema;
|
|
25007
25142
|
declare namespace schemas$4 {
|
|
25008
|
-
export { schemas$4_vendorSchema as vendorSchema };
|
|
25143
|
+
export { schemas$4_vendorCaptchaSchema as vendorCaptchaSchema, schemas$4_vendorFeaturesSchema as vendorFeaturesSchema, schemas$4_vendorSchema as vendorSchema };
|
|
25009
25144
|
}
|
|
25010
25145
|
|
|
25011
25146
|
type Vendor = z.infer<typeof vendorSchema>;
|
|
25147
|
+
type VendorFeatures = z.infer<typeof vendorFeaturesSchema>;
|
|
25148
|
+
type VendorCaptcha = z.infer<typeof vendorCaptchaSchema>;
|
|
25012
25149
|
|
|
25013
25150
|
declare class VendorEndpoints {
|
|
25014
25151
|
private api;
|
|
@@ -47026,6 +47163,17 @@ declare class MoonbaseError extends Error {
|
|
|
47026
47163
|
readonly errors?: Record<string, string> | undefined;
|
|
47027
47164
|
constructor(title: string, detail: string | undefined, status?: number | undefined, errors?: Record<string, string> | undefined);
|
|
47028
47165
|
}
|
|
47166
|
+
/**
|
|
47167
|
+
* The store asks customers to solve a captcha (see `Vendor.features.captcha`), and the request
|
|
47168
|
+
* carried none, or one that did not check out: unsolved, expired, already used, or solved on a
|
|
47169
|
+
* site that is not one of the store's. `message` is the API's own copy for the customer, safe to
|
|
47170
|
+
* show as is. Solutions are single-use, so reset the captcha and let them try again.
|
|
47171
|
+
*
|
|
47172
|
+
* A `MoonbaseError` like any other refusal, so code that already handles those keeps working.
|
|
47173
|
+
*/
|
|
47174
|
+
declare class CaptchaRequiredError extends MoonbaseError {
|
|
47175
|
+
constructor(detail?: string, title?: string);
|
|
47176
|
+
}
|
|
47029
47177
|
|
|
47030
47178
|
/**
|
|
47031
47179
|
* Cleans up merchant-authored group IDs before they go on the wire.
|
|
@@ -47206,4 +47354,4 @@ declare class MoonbaseClient {
|
|
|
47206
47354
|
orders: OrderEndpoints;
|
|
47207
47355
|
}
|
|
47208
47356
|
|
|
47209
|
-
export { type Activation, ActivationMethod, type ActivationRequest, ActivationRequestFulfillmentType, ActivationRequestStatus, ActivationStatus, type Address, Architecture, type BundleLineItem, type CartCatalog, type CartOrder, type CartTotal, type CartTotalCondition, CartUtils, type CommunicationPreferences, type CommunicationPreferencesInput, type CommunicationPreferencesView, type CompletedOrder, ConnectableAccountProvider, type ConnectedAccount, ConsoleLogger, CycleLength, type Discount, DiscountUtils, type Download, type DownloadManifest, type ILogger, type IRecurrence, type IStore, type ITokenStore, type Identity, type ImageFit, type ImageFormat, type ImageTransform, ImageUtils, InMemoryStore, type License, LicenseStatus, type LineItem, type LineItemTotal, LocalStorageStore, LogLevel, MarketingConsentType, type MilestoneProgress, type Money, type MoneyCollection, MoneyCollectionUtils, MoonbaseApi, MoonbaseClient, type MoonbaseConfiguration, MoonbaseError, NotAuthenticatedError, NotAuthorizedError, NotFoundError, type OfferCondition, type OfferConditionContext, type OfferProgress, type OfferScope, OfferUtils, type OpenOrder, type Order, OrderStatus, type OrderTotal, type OwnedProduct, type Page, Platform, type PricingTier, type PricingVariation, type ProblemDetails, type ProductLineItem, type PromotionDisplay, type Quantifiable, type RawPropertyArray, type RawPropertyObject, type RawPropertyValue, type SignUpOptions, type SignUpResult, type SingleOrMultiple, StoreClosedError, type Storefront, type StorefrontBundle, type StorefrontOffer, type StorefrontOfferTarget, type StorefrontProduct, type StorefrontPromotion, type StorefrontPromotionCta, type StorefrontPromotionTarget, type SubscribeInput, type SubscribeResponse, type Subscription, type SubscriptionCancellationFeedback, SubscriptionCancellationReason, SubscriptionStatus, type TaxEstimate, TokenStore, type UrchinTrackingModule, type User, type UserAccountConfirmed, type UserAccountConfirmedStatus, type Vendor, type Voucher, imageWidths, isLocalStorageAvailable, isPromotionDisplay, isTransformableImageUrl, maxImageDimension, normalizeGroupIds, objectToQuery, parseOffSiteReferrer, problemDetailsSchema, promotionDisplays, resolveUtm, schemas, utmToObject };
|
|
47357
|
+
export { type Activation, ActivationMethod, type ActivationRequest, ActivationRequestFulfillmentType, ActivationRequestStatus, ActivationStatus, type Address, Architecture, type BundleLineItem, CaptchaRequiredError, type CartCatalog, type CartOrder, type CartTotal, type CartTotalCondition, CartUtils, type CommunicationPreferences, type CommunicationPreferencesInput, type CommunicationPreferencesView, type CompletedOrder, ConnectableAccountProvider, type ConnectedAccount, ConsoleLogger, CycleLength, type Discount, DiscountUtils, type Download, type DownloadManifest, type ILogger, type IRecurrence, type IStore, type ITokenStore, type Identity, type ImageFit, type ImageFormat, type ImageTransform, ImageUtils, InMemoryStore, type License, LicenseStatus, type LineItem, type LineItemTotal, LocalStorageStore, LogLevel, MarketingConsentType, type MilestoneProgress, type Money, type MoneyCollection, MoneyCollectionUtils, MoonbaseApi, MoonbaseClient, type MoonbaseConfiguration, MoonbaseError, NotAuthenticatedError, NotAuthorizedError, NotFoundError, type OfferCondition, type OfferConditionContext, type OfferProgress, type OfferScope, OfferUtils, type OpenOrder, type Order, OrderStatus, type OrderTotal, type OwnedProduct, type Page, Platform, type PricingTier, type PricingVariation, type ProblemDetails, type ProductLineItem, type PromotionDisplay, type Quantifiable, type RawPropertyArray, type RawPropertyObject, type RawPropertyValue, type SignUpOptions, type SignUpResult, type SingleOrMultiple, StoreClosedError, type Storefront, type StorefrontBundle, type StorefrontOffer, type StorefrontOfferTarget, type StorefrontProduct, type StorefrontPromotion, type StorefrontPromotionCta, type StorefrontPromotionTarget, type SubscribeInput, type SubscribeResponse, type Subscription, type SubscriptionCancellationFeedback, SubscriptionCancellationReason, SubscriptionStatus, type TaxEstimate, TokenStore, type UrchinTrackingModule, type User, type UserAccountConfirmed, type UserAccountConfirmedStatus, type Vendor, type VendorCaptcha, type VendorFeatures, type Voucher, accessTokenLifetime, imageWidths, isLocalStorageAvailable, isPromotionDisplay, isTransformableImageUrl, maxImageDimension, normalizeGroupIds, objectToQuery, parseOffSiteReferrer, problemDetailsSchema, promotionDisplays, resolveUtm, schemas, utmToObject };
|
package/dist/index.d.ts
CHANGED
|
@@ -80,18 +80,28 @@ declare class InMemoryStore implements IStore {
|
|
|
80
80
|
type IdentityWithExpiry = Identity & {
|
|
81
81
|
expiresAt: Date;
|
|
82
82
|
};
|
|
83
|
+
/**
|
|
84
|
+
* How long the server issued an access token for, from its `iat` and `exp`. The lifetime rather than
|
|
85
|
+
* `exp` itself, measured against this browser's clock from the moment the token arrives: a clock that is
|
|
86
|
+
* minutes off would otherwise make every token look expired, or none.
|
|
87
|
+
*/
|
|
88
|
+
declare function accessTokenLifetime(accessToken: string): number;
|
|
83
89
|
interface ITokenStore {
|
|
84
90
|
get user(): User | null;
|
|
85
91
|
get isPersistent(): boolean;
|
|
86
92
|
getAccessToken: () => Promise<string | null>;
|
|
87
93
|
setUser: (user: Identity | User) => User | null;
|
|
88
94
|
clear: () => void;
|
|
95
|
+
signOut: () => Promise<void>;
|
|
96
|
+
onSessionEnded: (listener: () => void) => void;
|
|
89
97
|
}
|
|
90
98
|
declare class TokenStore implements ITokenStore {
|
|
91
99
|
private configuration;
|
|
92
100
|
private tokens;
|
|
93
101
|
private refreshTimeoutId;
|
|
94
102
|
private refreshPromise;
|
|
103
|
+
private waitingForVisibility;
|
|
104
|
+
private readonly sessionEndedListeners;
|
|
95
105
|
private readonly store;
|
|
96
106
|
private readonly logger;
|
|
97
107
|
static readonly storageKey = "moonbase_auth";
|
|
@@ -101,7 +111,18 @@ declare class TokenStore implements ITokenStore {
|
|
|
101
111
|
getAccessToken(): Promise<string | null>;
|
|
102
112
|
setUser(user: Identity | User): IdentityWithExpiry | null;
|
|
103
113
|
clear(): null;
|
|
114
|
+
signOut(): Promise<void>;
|
|
115
|
+
onSessionEnded(listener: () => void): void;
|
|
104
116
|
private get isExpired();
|
|
117
|
+
private readStoredTokens;
|
|
118
|
+
/**
|
|
119
|
+
* (Re)arms the timer that renews the access token shortly before it runs out, or disarms
|
|
120
|
+
* it when there is nothing to renew. Every tab arms one, and `refreshTokens` makes sure
|
|
121
|
+
* only one of them actually renews.
|
|
122
|
+
*/
|
|
123
|
+
private scheduleRefresh;
|
|
124
|
+
private renewalDueAt;
|
|
125
|
+
private renewInBackground;
|
|
105
126
|
/**
|
|
106
127
|
* Start a refresh and hold it so concurrent callers join it rather than firing
|
|
107
128
|
* their own. The slot is cleared as soon as it settles: a retained promise
|
|
@@ -110,7 +131,20 @@ declare class TokenStore implements ITokenStore {
|
|
|
110
131
|
* anonymous ones) for the rest of the session.
|
|
111
132
|
*/
|
|
112
133
|
private startRefresh;
|
|
134
|
+
/**
|
|
135
|
+
* A refresh token can be used once: the server answers a second use by revoking the
|
|
136
|
+
* session, taking it for a stolen token. So refreshes are serialised across every tab of
|
|
137
|
+
* the origin, and whoever gets the lock second takes up what the first one stored rather
|
|
138
|
+
* than spending the same refresh token again. The stored copy has to be read, not waited
|
|
139
|
+
* for: storage events never reach the document that wrote them, so a second client on
|
|
140
|
+
* the same page would not hear about it.
|
|
141
|
+
*/
|
|
113
142
|
private refreshTokens;
|
|
143
|
+
/**
|
|
144
|
+
* @param spending The refresh token this refresh set out to use. If the session has moved
|
|
145
|
+
* on from it while we waited for the lock, someone else has renewed already.
|
|
146
|
+
*/
|
|
147
|
+
private refreshHoldingLock;
|
|
114
148
|
private handleStorageUpdate;
|
|
115
149
|
private warnAboutStorage;
|
|
116
150
|
}
|
|
@@ -797,6 +831,7 @@ declare const activationRequestSchema: z.ZodObject<{
|
|
|
797
831
|
requiresNewsletterOptIn: z.ZodBoolean;
|
|
798
832
|
numberOfDaysPerProduct: z.ZodNumber;
|
|
799
833
|
numberOfDaysRemaining: z.ZodNumber;
|
|
834
|
+
endless: z.ZodDefault<z.ZodBoolean>;
|
|
800
835
|
}, "strip", z.ZodTypeAny, {
|
|
801
836
|
eligible: boolean;
|
|
802
837
|
existing: boolean;
|
|
@@ -804,6 +839,7 @@ declare const activationRequestSchema: z.ZodObject<{
|
|
|
804
839
|
requiresNewsletterOptIn: boolean;
|
|
805
840
|
numberOfDaysPerProduct: number;
|
|
806
841
|
numberOfDaysRemaining: number;
|
|
842
|
+
endless: boolean;
|
|
807
843
|
}, {
|
|
808
844
|
eligible: boolean;
|
|
809
845
|
existing: boolean;
|
|
@@ -811,6 +847,7 @@ declare const activationRequestSchema: z.ZodObject<{
|
|
|
811
847
|
requiresNewsletterOptIn: boolean;
|
|
812
848
|
numberOfDaysPerProduct: number;
|
|
813
849
|
numberOfDaysRemaining: number;
|
|
850
|
+
endless?: boolean | undefined;
|
|
814
851
|
}>>;
|
|
815
852
|
licenseEligibility: z.ZodOptional<z.ZodObject<{
|
|
816
853
|
eligible: z.ZodBoolean;
|
|
@@ -937,6 +974,7 @@ declare const activationRequestSchema: z.ZodObject<{
|
|
|
937
974
|
requiresNewsletterOptIn: boolean;
|
|
938
975
|
numberOfDaysPerProduct: number;
|
|
939
976
|
numberOfDaysRemaining: number;
|
|
977
|
+
endless: boolean;
|
|
940
978
|
} | undefined;
|
|
941
979
|
licenseEligibility?: {
|
|
942
980
|
eligible: boolean;
|
|
@@ -1059,6 +1097,7 @@ declare const activationRequestSchema: z.ZodObject<{
|
|
|
1059
1097
|
requiresNewsletterOptIn: boolean;
|
|
1060
1098
|
numberOfDaysPerProduct: number;
|
|
1061
1099
|
numberOfDaysRemaining: number;
|
|
1100
|
+
endless?: boolean | undefined;
|
|
1062
1101
|
} | undefined;
|
|
1063
1102
|
licenseEligibility?: {
|
|
1064
1103
|
eligible: boolean;
|
|
@@ -1677,6 +1716,15 @@ interface SignUpOptions {
|
|
|
1677
1716
|
* ID left in a form can never fail a registration.
|
|
1678
1717
|
*/
|
|
1679
1718
|
groupIds?: string[];
|
|
1719
|
+
/**
|
|
1720
|
+
* The customer's solution to the store's captcha, for stores that ask for one
|
|
1721
|
+
* (`vendor.features.captcha`). Sent in the `X-MB-CAPTCHA` header.
|
|
1722
|
+
*
|
|
1723
|
+
* Without it (or with one that does not check out) such a store refuses the
|
|
1724
|
+
* sign-up with a `CaptchaRequiredError`. Solutions are single-use and expire
|
|
1725
|
+
* after about two minutes, so reset the captcha after every attempt.
|
|
1726
|
+
*/
|
|
1727
|
+
captchaToken?: string;
|
|
1680
1728
|
}
|
|
1681
1729
|
|
|
1682
1730
|
declare class IdentityEndpoints {
|
|
@@ -1687,6 +1735,11 @@ declare class IdentityEndpoints {
|
|
|
1687
1735
|
get(): Promise<User>;
|
|
1688
1736
|
signIn(email: string, password: string): Promise<User>;
|
|
1689
1737
|
signUp(name: string, email: string, password: string, address: Address | null | undefined, acceptedPrivacyPolicy: boolean, acceptedTermsAndConditions: boolean, communicationOptIn?: boolean, options?: SignUpOptions): Promise<SignUpResult>;
|
|
1738
|
+
/**
|
|
1739
|
+
* Signs the buyer out here at once, and ends the session on the server so its refresh
|
|
1740
|
+
* token stops working too. Resolves once the server has answered, and never rejects:
|
|
1741
|
+
* the buyer is signed out in this browser whatever happens to that request.
|
|
1742
|
+
*/
|
|
1690
1743
|
signOut(): Promise<void>;
|
|
1691
1744
|
update(name: string | null, email: string, emailConfirmationToken?: string, communicationPreferences?: CommunicationPreferences): Promise<{
|
|
1692
1745
|
needsEmailConfirmationToken: boolean;
|
|
@@ -24989,26 +25042,110 @@ declare class StorefrontEndpoints {
|
|
|
24989
25042
|
}>;
|
|
24990
25043
|
}
|
|
24991
25044
|
|
|
25045
|
+
/**
|
|
25046
|
+
* The captcha a store asks customers to solve before signing up and checking out. `url` is a
|
|
25047
|
+
* page on the store's own `<account-id>.moonbase.sh` that draws the challenge: embed it in an
|
|
25048
|
+
* iframe, and it hands the solution to its parent with `postMessage`.
|
|
25049
|
+
*/
|
|
25050
|
+
declare const vendorCaptchaSchema: z.ZodObject<{
|
|
25051
|
+
url: z.ZodString;
|
|
25052
|
+
}, "strip", z.ZodTypeAny, {
|
|
25053
|
+
url: string;
|
|
25054
|
+
}, {
|
|
25055
|
+
url: string;
|
|
25056
|
+
}>;
|
|
25057
|
+
declare const vendorFeaturesSchema: z.ZodObject<{
|
|
25058
|
+
customerPortal: z.ZodOptional<z.ZodBoolean>;
|
|
25059
|
+
phoneNumber: z.ZodOptional<z.ZodBoolean>;
|
|
25060
|
+
marketingConsent: z.ZodCatch<z.ZodOptional<z.ZodNativeEnum<typeof MarketingConsentType>>>;
|
|
25061
|
+
captcha: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
25062
|
+
url: z.ZodString;
|
|
25063
|
+
}, "strip", z.ZodTypeAny, {
|
|
25064
|
+
url: string;
|
|
25065
|
+
}, {
|
|
25066
|
+
url: string;
|
|
25067
|
+
}>>>;
|
|
25068
|
+
}, "strip", z.ZodTypeAny, {
|
|
25069
|
+
customerPortal?: boolean | undefined;
|
|
25070
|
+
phoneNumber?: boolean | undefined;
|
|
25071
|
+
marketingConsent?: MarketingConsentType | undefined;
|
|
25072
|
+
captcha?: {
|
|
25073
|
+
url: string;
|
|
25074
|
+
} | null | undefined;
|
|
25075
|
+
}, {
|
|
25076
|
+
customerPortal?: boolean | undefined;
|
|
25077
|
+
phoneNumber?: boolean | undefined;
|
|
25078
|
+
marketingConsent?: unknown;
|
|
25079
|
+
captcha?: {
|
|
25080
|
+
url: string;
|
|
25081
|
+
} | null | undefined;
|
|
25082
|
+
}>;
|
|
24992
25083
|
declare const vendorSchema: z.ZodObject<{
|
|
24993
25084
|
id: z.ZodString;
|
|
24994
25085
|
name: z.ZodString;
|
|
24995
25086
|
logoUrl: z.ZodNullable<z.ZodString>;
|
|
25087
|
+
features: z.ZodOptional<z.ZodObject<{
|
|
25088
|
+
customerPortal: z.ZodOptional<z.ZodBoolean>;
|
|
25089
|
+
phoneNumber: z.ZodOptional<z.ZodBoolean>;
|
|
25090
|
+
marketingConsent: z.ZodCatch<z.ZodOptional<z.ZodNativeEnum<typeof MarketingConsentType>>>;
|
|
25091
|
+
captcha: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
25092
|
+
url: z.ZodString;
|
|
25093
|
+
}, "strip", z.ZodTypeAny, {
|
|
25094
|
+
url: string;
|
|
25095
|
+
}, {
|
|
25096
|
+
url: string;
|
|
25097
|
+
}>>>;
|
|
25098
|
+
}, "strip", z.ZodTypeAny, {
|
|
25099
|
+
customerPortal?: boolean | undefined;
|
|
25100
|
+
phoneNumber?: boolean | undefined;
|
|
25101
|
+
marketingConsent?: MarketingConsentType | undefined;
|
|
25102
|
+
captcha?: {
|
|
25103
|
+
url: string;
|
|
25104
|
+
} | null | undefined;
|
|
25105
|
+
}, {
|
|
25106
|
+
customerPortal?: boolean | undefined;
|
|
25107
|
+
phoneNumber?: boolean | undefined;
|
|
25108
|
+
marketingConsent?: unknown;
|
|
25109
|
+
captcha?: {
|
|
25110
|
+
url: string;
|
|
25111
|
+
} | null | undefined;
|
|
25112
|
+
}>>;
|
|
24996
25113
|
}, "strip", z.ZodTypeAny, {
|
|
24997
25114
|
id: string;
|
|
24998
25115
|
name: string;
|
|
24999
25116
|
logoUrl: string | null;
|
|
25117
|
+
features?: {
|
|
25118
|
+
customerPortal?: boolean | undefined;
|
|
25119
|
+
phoneNumber?: boolean | undefined;
|
|
25120
|
+
marketingConsent?: MarketingConsentType | undefined;
|
|
25121
|
+
captcha?: {
|
|
25122
|
+
url: string;
|
|
25123
|
+
} | null | undefined;
|
|
25124
|
+
} | undefined;
|
|
25000
25125
|
}, {
|
|
25001
25126
|
id: string;
|
|
25002
25127
|
name: string;
|
|
25003
25128
|
logoUrl: string | null;
|
|
25129
|
+
features?: {
|
|
25130
|
+
customerPortal?: boolean | undefined;
|
|
25131
|
+
phoneNumber?: boolean | undefined;
|
|
25132
|
+
marketingConsent?: unknown;
|
|
25133
|
+
captcha?: {
|
|
25134
|
+
url: string;
|
|
25135
|
+
} | null | undefined;
|
|
25136
|
+
} | undefined;
|
|
25004
25137
|
}>;
|
|
25005
25138
|
|
|
25139
|
+
declare const schemas$4_vendorCaptchaSchema: typeof vendorCaptchaSchema;
|
|
25140
|
+
declare const schemas$4_vendorFeaturesSchema: typeof vendorFeaturesSchema;
|
|
25006
25141
|
declare const schemas$4_vendorSchema: typeof vendorSchema;
|
|
25007
25142
|
declare namespace schemas$4 {
|
|
25008
|
-
export { schemas$4_vendorSchema as vendorSchema };
|
|
25143
|
+
export { schemas$4_vendorCaptchaSchema as vendorCaptchaSchema, schemas$4_vendorFeaturesSchema as vendorFeaturesSchema, schemas$4_vendorSchema as vendorSchema };
|
|
25009
25144
|
}
|
|
25010
25145
|
|
|
25011
25146
|
type Vendor = z.infer<typeof vendorSchema>;
|
|
25147
|
+
type VendorFeatures = z.infer<typeof vendorFeaturesSchema>;
|
|
25148
|
+
type VendorCaptcha = z.infer<typeof vendorCaptchaSchema>;
|
|
25012
25149
|
|
|
25013
25150
|
declare class VendorEndpoints {
|
|
25014
25151
|
private api;
|
|
@@ -47026,6 +47163,17 @@ declare class MoonbaseError extends Error {
|
|
|
47026
47163
|
readonly errors?: Record<string, string> | undefined;
|
|
47027
47164
|
constructor(title: string, detail: string | undefined, status?: number | undefined, errors?: Record<string, string> | undefined);
|
|
47028
47165
|
}
|
|
47166
|
+
/**
|
|
47167
|
+
* The store asks customers to solve a captcha (see `Vendor.features.captcha`), and the request
|
|
47168
|
+
* carried none, or one that did not check out: unsolved, expired, already used, or solved on a
|
|
47169
|
+
* site that is not one of the store's. `message` is the API's own copy for the customer, safe to
|
|
47170
|
+
* show as is. Solutions are single-use, so reset the captcha and let them try again.
|
|
47171
|
+
*
|
|
47172
|
+
* A `MoonbaseError` like any other refusal, so code that already handles those keeps working.
|
|
47173
|
+
*/
|
|
47174
|
+
declare class CaptchaRequiredError extends MoonbaseError {
|
|
47175
|
+
constructor(detail?: string, title?: string);
|
|
47176
|
+
}
|
|
47029
47177
|
|
|
47030
47178
|
/**
|
|
47031
47179
|
* Cleans up merchant-authored group IDs before they go on the wire.
|
|
@@ -47206,4 +47354,4 @@ declare class MoonbaseClient {
|
|
|
47206
47354
|
orders: OrderEndpoints;
|
|
47207
47355
|
}
|
|
47208
47356
|
|
|
47209
|
-
export { type Activation, ActivationMethod, type ActivationRequest, ActivationRequestFulfillmentType, ActivationRequestStatus, ActivationStatus, type Address, Architecture, type BundleLineItem, type CartCatalog, type CartOrder, type CartTotal, type CartTotalCondition, CartUtils, type CommunicationPreferences, type CommunicationPreferencesInput, type CommunicationPreferencesView, type CompletedOrder, ConnectableAccountProvider, type ConnectedAccount, ConsoleLogger, CycleLength, type Discount, DiscountUtils, type Download, type DownloadManifest, type ILogger, type IRecurrence, type IStore, type ITokenStore, type Identity, type ImageFit, type ImageFormat, type ImageTransform, ImageUtils, InMemoryStore, type License, LicenseStatus, type LineItem, type LineItemTotal, LocalStorageStore, LogLevel, MarketingConsentType, type MilestoneProgress, type Money, type MoneyCollection, MoneyCollectionUtils, MoonbaseApi, MoonbaseClient, type MoonbaseConfiguration, MoonbaseError, NotAuthenticatedError, NotAuthorizedError, NotFoundError, type OfferCondition, type OfferConditionContext, type OfferProgress, type OfferScope, OfferUtils, type OpenOrder, type Order, OrderStatus, type OrderTotal, type OwnedProduct, type Page, Platform, type PricingTier, type PricingVariation, type ProblemDetails, type ProductLineItem, type PromotionDisplay, type Quantifiable, type RawPropertyArray, type RawPropertyObject, type RawPropertyValue, type SignUpOptions, type SignUpResult, type SingleOrMultiple, StoreClosedError, type Storefront, type StorefrontBundle, type StorefrontOffer, type StorefrontOfferTarget, type StorefrontProduct, type StorefrontPromotion, type StorefrontPromotionCta, type StorefrontPromotionTarget, type SubscribeInput, type SubscribeResponse, type Subscription, type SubscriptionCancellationFeedback, SubscriptionCancellationReason, SubscriptionStatus, type TaxEstimate, TokenStore, type UrchinTrackingModule, type User, type UserAccountConfirmed, type UserAccountConfirmedStatus, type Vendor, type Voucher, imageWidths, isLocalStorageAvailable, isPromotionDisplay, isTransformableImageUrl, maxImageDimension, normalizeGroupIds, objectToQuery, parseOffSiteReferrer, problemDetailsSchema, promotionDisplays, resolveUtm, schemas, utmToObject };
|
|
47357
|
+
export { type Activation, ActivationMethod, type ActivationRequest, ActivationRequestFulfillmentType, ActivationRequestStatus, ActivationStatus, type Address, Architecture, type BundleLineItem, CaptchaRequiredError, type CartCatalog, type CartOrder, type CartTotal, type CartTotalCondition, CartUtils, type CommunicationPreferences, type CommunicationPreferencesInput, type CommunicationPreferencesView, type CompletedOrder, ConnectableAccountProvider, type ConnectedAccount, ConsoleLogger, CycleLength, type Discount, DiscountUtils, type Download, type DownloadManifest, type ILogger, type IRecurrence, type IStore, type ITokenStore, type Identity, type ImageFit, type ImageFormat, type ImageTransform, ImageUtils, InMemoryStore, type License, LicenseStatus, type LineItem, type LineItemTotal, LocalStorageStore, LogLevel, MarketingConsentType, type MilestoneProgress, type Money, type MoneyCollection, MoneyCollectionUtils, MoonbaseApi, MoonbaseClient, type MoonbaseConfiguration, MoonbaseError, NotAuthenticatedError, NotAuthorizedError, NotFoundError, type OfferCondition, type OfferConditionContext, type OfferProgress, type OfferScope, OfferUtils, type OpenOrder, type Order, OrderStatus, type OrderTotal, type OwnedProduct, type Page, Platform, type PricingTier, type PricingVariation, type ProblemDetails, type ProductLineItem, type PromotionDisplay, type Quantifiable, type RawPropertyArray, type RawPropertyObject, type RawPropertyValue, type SignUpOptions, type SignUpResult, type SingleOrMultiple, StoreClosedError, type Storefront, type StorefrontBundle, type StorefrontOffer, type StorefrontOfferTarget, type StorefrontProduct, type StorefrontPromotion, type StorefrontPromotionCta, type StorefrontPromotionTarget, type SubscribeInput, type SubscribeResponse, type Subscription, type SubscriptionCancellationFeedback, SubscriptionCancellationReason, SubscriptionStatus, type TaxEstimate, TokenStore, type UrchinTrackingModule, type User, type UserAccountConfirmed, type UserAccountConfirmedStatus, type Vendor, type VendorCaptcha, type VendorFeatures, type Voucher, accessTokenLifetime, imageWidths, isLocalStorageAvailable, isPromotionDisplay, isTransformableImageUrl, maxImageDimension, normalizeGroupIds, objectToQuery, parseOffSiteReferrer, problemDetailsSchema, promotionDisplays, resolveUtm, schemas, utmToObject };
|
package/dist/index.js
CHANGED
|
@@ -397,7 +397,10 @@ var activationRequestSchema = z4.object({
|
|
|
397
397
|
requiresAccount: z4.boolean(),
|
|
398
398
|
requiresNewsletterOptIn: z4.boolean(),
|
|
399
399
|
numberOfDaysPerProduct: z4.number(),
|
|
400
|
-
numberOfDaysRemaining: z4.number()
|
|
400
|
+
numberOfDaysRemaining: z4.number(),
|
|
401
|
+
// The trial never expires, so the day counts are not worth showing. Absent from a backend that
|
|
402
|
+
// predates endless trials.
|
|
403
|
+
endless: z4.boolean().default(false)
|
|
401
404
|
}).optional(),
|
|
402
405
|
licenseEligibility: z4.object({
|
|
403
406
|
eligible: z4.boolean()
|
|
@@ -595,6 +598,12 @@ var MoonbaseError = class extends Error {
|
|
|
595
598
|
}
|
|
596
599
|
}
|
|
597
600
|
};
|
|
601
|
+
var CaptchaRequiredError = class extends MoonbaseError {
|
|
602
|
+
constructor(detail, title) {
|
|
603
|
+
super(title != null ? title : "Captcha required", detail != null ? detail : "Please confirm you're not a robot.", 400);
|
|
604
|
+
this.name = "CaptchaRequiredError";
|
|
605
|
+
}
|
|
606
|
+
};
|
|
598
607
|
|
|
599
608
|
// src/utils/problemDetails.ts
|
|
600
609
|
import { z as z7 } from "zod";
|
|
@@ -637,6 +646,8 @@ async function handleResponseProblem(response, logger) {
|
|
|
637
646
|
logger.debug("The response indicates a problem", problemDetails);
|
|
638
647
|
if (problemDetails.errorType === "StoreClosed")
|
|
639
648
|
throw new StoreClosedError(problemDetails.detail);
|
|
649
|
+
if (problemDetails.errorType === "CaptchaRequired")
|
|
650
|
+
throw new CaptchaRequiredError(problemDetails.detail, problemDetails.title);
|
|
640
651
|
throw new MoonbaseError(
|
|
641
652
|
problemDetails.title,
|
|
642
653
|
problemDetails.detail,
|
|
@@ -773,7 +784,8 @@ var IdentityEndpoints = class {
|
|
|
773
784
|
"Accept": "application/json",
|
|
774
785
|
"Content-Type": "application/json",
|
|
775
786
|
"x-mb-cors": "1",
|
|
776
|
-
"x-mb-client": this.api.clientHeader
|
|
787
|
+
"x-mb-client": this.api.clientHeader,
|
|
788
|
+
...(options == null ? void 0 : options.captchaToken) ? { "X-MB-CAPTCHA": options.captchaToken } : {}
|
|
777
789
|
},
|
|
778
790
|
body: JSON.stringify({
|
|
779
791
|
name,
|
|
@@ -802,8 +814,13 @@ var IdentityEndpoints = class {
|
|
|
802
814
|
this.tokenStore.setUser(identity);
|
|
803
815
|
return { status: "signed-in", user: userSchema.parse(identity) };
|
|
804
816
|
}
|
|
817
|
+
/**
|
|
818
|
+
* Signs the buyer out here at once, and ends the session on the server so its refresh
|
|
819
|
+
* token stops working too. Resolves once the server has answered, and never rejects:
|
|
820
|
+
* the buyer is signed out in this browser whatever happens to that request.
|
|
821
|
+
*/
|
|
805
822
|
async signOut() {
|
|
806
|
-
this.tokenStore.
|
|
823
|
+
await this.tokenStore.signOut();
|
|
807
824
|
}
|
|
808
825
|
async update(name, email, emailConfirmationToken, communicationPreferences) {
|
|
809
826
|
const response = await this.api.authenticatedFetch("/api/customer/identity", userSchema, {
|
|
@@ -1036,7 +1053,7 @@ var ProductEndpoints = class {
|
|
|
1036
1053
|
import { z as z14 } from "zod";
|
|
1037
1054
|
|
|
1038
1055
|
// package.json
|
|
1039
|
-
var version = "3.
|
|
1056
|
+
var version = "3.7.0";
|
|
1040
1057
|
|
|
1041
1058
|
// src/utils/client.ts
|
|
1042
1059
|
function buildClientHeader(sdk) {
|
|
@@ -1715,24 +1732,36 @@ var InMemoryStore = class {
|
|
|
1715
1732
|
};
|
|
1716
1733
|
|
|
1717
1734
|
// src/utils/tokenStore.ts
|
|
1735
|
+
var defaultAccessTokenLifetime = 15 * 60 * 1e3;
|
|
1736
|
+
var renewBeforeExpiry = 5 * 60 * 1e3;
|
|
1737
|
+
var expiryMargin = 30 * 1e3;
|
|
1738
|
+
var refreshLockName = "moonbase_auth_refresh";
|
|
1739
|
+
function accessTokenLifetime(accessToken) {
|
|
1740
|
+
try {
|
|
1741
|
+
const payload = accessToken.split(".")[1].replaceAll("-", "+").replaceAll("_", "/");
|
|
1742
|
+
const claims = JSON.parse(atob(payload.padEnd(Math.ceil(payload.length / 4) * 4, "=")));
|
|
1743
|
+
const lifetime = (Number(claims.exp) - Number(claims.iat)) * 1e3;
|
|
1744
|
+
if (Number.isFinite(lifetime) && lifetime > 0 && lifetime <= 24 * 60 * 60 * 1e3)
|
|
1745
|
+
return lifetime;
|
|
1746
|
+
} catch (e) {
|
|
1747
|
+
}
|
|
1748
|
+
return defaultAccessTokenLifetime;
|
|
1749
|
+
}
|
|
1718
1750
|
var _TokenStore = class _TokenStore {
|
|
1719
1751
|
constructor(configuration) {
|
|
1720
1752
|
this.configuration = configuration;
|
|
1721
1753
|
this.tokens = null;
|
|
1722
1754
|
this.refreshTimeoutId = null;
|
|
1723
1755
|
this.refreshPromise = null;
|
|
1756
|
+
this.waitingForVisibility = false;
|
|
1757
|
+
this.sessionEndedListeners = [];
|
|
1724
1758
|
var _a, _b;
|
|
1725
1759
|
this.logger = (_a = configuration.logger) != null ? _a : new ConsoleLogger();
|
|
1726
1760
|
this.store = (_b = configuration.store) != null ? _b : isLocalStorageAvailable() ? new LocalStorageStore((err) => this.warnAboutStorage(err)) : new InMemoryStore();
|
|
1727
1761
|
if (typeof window !== "undefined" && !configuration.store && !(this.store instanceof LocalStorageStore))
|
|
1728
1762
|
this.warnAboutStorage();
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
this.tokens = {
|
|
1732
|
-
...storedToken,
|
|
1733
|
-
expiresAt: new Date(storedToken.expiresAt)
|
|
1734
|
-
};
|
|
1735
|
-
}
|
|
1763
|
+
this.tokens = this.readStoredTokens();
|
|
1764
|
+
this.scheduleRefresh();
|
|
1736
1765
|
this.store.listen(_TokenStore.storageKey, (updatedTokens) => this.handleStorageUpdate(updatedTokens));
|
|
1737
1766
|
}
|
|
1738
1767
|
get user() {
|
|
@@ -1766,29 +1795,91 @@ var _TokenStore = class _TokenStore {
|
|
|
1766
1795
|
} else {
|
|
1767
1796
|
this.tokens = {
|
|
1768
1797
|
...identity,
|
|
1769
|
-
|
|
1770
|
-
expiresAt: new Date((/* @__PURE__ */ new Date()).getTime() + 15 * 60 * 1e3)
|
|
1798
|
+
expiresAt: new Date(Date.now() + accessTokenLifetime(identity.accessToken))
|
|
1771
1799
|
};
|
|
1772
1800
|
}
|
|
1773
1801
|
this.store.set(_TokenStore.storageKey, this.tokens);
|
|
1774
|
-
|
|
1775
|
-
if (this.refreshTimeoutId != null)
|
|
1776
|
-
window.clearTimeout(this.refreshTimeoutId);
|
|
1777
|
-
this.refreshTimeoutId = window.setTimeout(() => {
|
|
1778
|
-
this.startRefresh().catch((err) => this.logger.warn("Could not refresh access token", err));
|
|
1779
|
-
}, 10 * 60 * 1e3);
|
|
1780
|
-
}
|
|
1802
|
+
this.scheduleRefresh();
|
|
1781
1803
|
return this.tokens;
|
|
1782
1804
|
}
|
|
1783
1805
|
clear() {
|
|
1784
1806
|
this.tokens = null;
|
|
1785
1807
|
this.store.remove(_TokenStore.storageKey);
|
|
1786
|
-
|
|
1787
|
-
window.clearTimeout(this.refreshTimeoutId);
|
|
1808
|
+
this.scheduleRefresh();
|
|
1788
1809
|
return null;
|
|
1789
1810
|
}
|
|
1811
|
+
async signOut() {
|
|
1812
|
+
const tokens = this.tokens;
|
|
1813
|
+
this.clear();
|
|
1814
|
+
if (!tokens)
|
|
1815
|
+
return;
|
|
1816
|
+
try {
|
|
1817
|
+
await fetch(`${this.configuration.endpoint}/api/customer/identity/revoke?token=${encodeURIComponent(tokens.refreshToken)}`, {
|
|
1818
|
+
method: "POST",
|
|
1819
|
+
keepalive: true,
|
|
1820
|
+
headers: {
|
|
1821
|
+
"Accept": "application/json",
|
|
1822
|
+
"Content-Type": "text/plain",
|
|
1823
|
+
"x-mb-cors": "1",
|
|
1824
|
+
"x-mb-client": buildClientHeader(this.configuration.sdk)
|
|
1825
|
+
},
|
|
1826
|
+
body: tokens.accessToken
|
|
1827
|
+
});
|
|
1828
|
+
} catch (err) {
|
|
1829
|
+
this.logger.warn("Could not end the session on the server", err);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
onSessionEnded(listener) {
|
|
1833
|
+
this.sessionEndedListeners.push(listener);
|
|
1834
|
+
}
|
|
1790
1835
|
get isExpired() {
|
|
1791
|
-
return this.tokens != null && this.tokens.expiresAt
|
|
1836
|
+
return this.tokens != null && this.tokens.expiresAt.getTime() - expiryMargin < Date.now();
|
|
1837
|
+
}
|
|
1838
|
+
readStoredTokens() {
|
|
1839
|
+
const stored = this.store.get(_TokenStore.storageKey);
|
|
1840
|
+
return stored ? { ...stored, expiresAt: new Date(stored.expiresAt) } : null;
|
|
1841
|
+
}
|
|
1842
|
+
/**
|
|
1843
|
+
* (Re)arms the timer that renews the access token shortly before it runs out, or disarms
|
|
1844
|
+
* it when there is nothing to renew. Every tab arms one, and `refreshTokens` makes sure
|
|
1845
|
+
* only one of them actually renews.
|
|
1846
|
+
*/
|
|
1847
|
+
scheduleRefresh() {
|
|
1848
|
+
if (typeof window === "undefined")
|
|
1849
|
+
return;
|
|
1850
|
+
if (this.refreshTimeoutId != null)
|
|
1851
|
+
window.clearTimeout(this.refreshTimeoutId);
|
|
1852
|
+
this.refreshTimeoutId = null;
|
|
1853
|
+
if (!this.tokens)
|
|
1854
|
+
return;
|
|
1855
|
+
const delay = Math.max(0, this.renewalDueAt(this.tokens) - Date.now());
|
|
1856
|
+
this.refreshTimeoutId = window.setTimeout(() => this.renewInBackground(), delay);
|
|
1857
|
+
}
|
|
1858
|
+
renewalDueAt(tokens) {
|
|
1859
|
+
const lead = Math.min(renewBeforeExpiry, accessTokenLifetime(tokens.accessToken) / 3);
|
|
1860
|
+
return tokens.expiresAt.getTime() - lead;
|
|
1861
|
+
}
|
|
1862
|
+
renewInBackground() {
|
|
1863
|
+
var _a;
|
|
1864
|
+
this.refreshTimeoutId = null;
|
|
1865
|
+
if (!this.tokens)
|
|
1866
|
+
return;
|
|
1867
|
+
if (this.renewalDueAt(this.tokens) > Date.now()) {
|
|
1868
|
+
this.scheduleRefresh();
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
1872
|
+
if (!this.waitingForVisibility) {
|
|
1873
|
+
this.waitingForVisibility = true;
|
|
1874
|
+
document.addEventListener("visibilitychange", () => {
|
|
1875
|
+
this.waitingForVisibility = false;
|
|
1876
|
+
this.renewInBackground();
|
|
1877
|
+
}, { once: true });
|
|
1878
|
+
}
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
const pending = (_a = this.refreshPromise) != null ? _a : this.startRefresh();
|
|
1882
|
+
pending.catch((err) => this.logger.warn("Could not refresh access token", err));
|
|
1792
1883
|
}
|
|
1793
1884
|
/**
|
|
1794
1885
|
* Start a refresh and hold it so concurrent callers join it rather than firing
|
|
@@ -1805,7 +1896,45 @@ var _TokenStore = class _TokenStore {
|
|
|
1805
1896
|
this.refreshPromise = pending;
|
|
1806
1897
|
return pending;
|
|
1807
1898
|
}
|
|
1899
|
+
/**
|
|
1900
|
+
* A refresh token can be used once: the server answers a second use by revoking the
|
|
1901
|
+
* session, taking it for a stolen token. So refreshes are serialised across every tab of
|
|
1902
|
+
* the origin, and whoever gets the lock second takes up what the first one stored rather
|
|
1903
|
+
* than spending the same refresh token again. The stored copy has to be read, not waited
|
|
1904
|
+
* for: storage events never reach the document that wrote them, so a second client on
|
|
1905
|
+
* the same page would not hear about it.
|
|
1906
|
+
*/
|
|
1808
1907
|
async refreshTokens() {
|
|
1908
|
+
var _a;
|
|
1909
|
+
const spending = (_a = this.tokens) == null ? void 0 : _a.refreshToken;
|
|
1910
|
+
const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
|
|
1911
|
+
if (locks == null ? void 0 : locks.request) {
|
|
1912
|
+
let held = false;
|
|
1913
|
+
try {
|
|
1914
|
+
return await locks.request(refreshLockName, () => {
|
|
1915
|
+
held = true;
|
|
1916
|
+
return this.refreshHoldingLock(spending);
|
|
1917
|
+
});
|
|
1918
|
+
} catch (err) {
|
|
1919
|
+
if (held)
|
|
1920
|
+
throw err;
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
return await this.refreshHoldingLock(spending);
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* @param spending The refresh token this refresh set out to use. If the session has moved
|
|
1927
|
+
* on from it while we waited for the lock, someone else has renewed already.
|
|
1928
|
+
*/
|
|
1929
|
+
async refreshHoldingLock(spending) {
|
|
1930
|
+
var _a;
|
|
1931
|
+
const stored = this.readStoredTokens();
|
|
1932
|
+
if (stored && stored.refreshToken !== ((_a = this.tokens) == null ? void 0 : _a.refreshToken))
|
|
1933
|
+
this.tokens = stored;
|
|
1934
|
+
if (this.tokens && this.tokens.refreshToken !== spending && !this.isExpired) {
|
|
1935
|
+
this.scheduleRefresh();
|
|
1936
|
+
return this.tokens;
|
|
1937
|
+
}
|
|
1809
1938
|
if (!this.tokens)
|
|
1810
1939
|
throw new Error("No tokens found to refresh");
|
|
1811
1940
|
const response = await fetch(`${this.configuration.endpoint}/api/customer/identity/refresh?token=${encodeURIComponent(this.tokens.refreshToken)}`, {
|
|
@@ -1821,6 +1950,13 @@ var _TokenStore = class _TokenStore {
|
|
|
1821
1950
|
if (response.status !== 200) {
|
|
1822
1951
|
if (response.status === 403 || response.status === 404) {
|
|
1823
1952
|
this.clear();
|
|
1953
|
+
for (const listener of this.sessionEndedListeners) {
|
|
1954
|
+
try {
|
|
1955
|
+
listener();
|
|
1956
|
+
} catch (err) {
|
|
1957
|
+
this.logger.warn("A listener for the end of the session failed", err);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1824
1960
|
return null;
|
|
1825
1961
|
}
|
|
1826
1962
|
throw new MoonbaseError("Unexpected result", `Could not refresh access token, status code ${response.status}`, response.status);
|
|
@@ -1831,11 +1967,10 @@ var _TokenStore = class _TokenStore {
|
|
|
1831
1967
|
}
|
|
1832
1968
|
handleStorageUpdate(updatedTokens) {
|
|
1833
1969
|
this.tokens = updatedTokens;
|
|
1834
|
-
if (this.refreshTimeoutId != null && typeof window !== "undefined")
|
|
1835
|
-
window.clearTimeout(this.refreshTimeoutId);
|
|
1836
1970
|
if (this.tokens) {
|
|
1837
1971
|
this.tokens.expiresAt = new Date(this.tokens.expiresAt);
|
|
1838
1972
|
}
|
|
1973
|
+
this.scheduleRefresh();
|
|
1839
1974
|
}
|
|
1840
1975
|
warnAboutStorage(err) {
|
|
1841
1976
|
this.logger.warn(
|
|
@@ -1850,13 +1985,26 @@ var TokenStore = _TokenStore;
|
|
|
1850
1985
|
// src/vendor/schemas.ts
|
|
1851
1986
|
var schemas_exports9 = {};
|
|
1852
1987
|
__export(schemas_exports9, {
|
|
1988
|
+
vendorCaptchaSchema: () => vendorCaptchaSchema,
|
|
1989
|
+
vendorFeaturesSchema: () => vendorFeaturesSchema,
|
|
1853
1990
|
vendorSchema: () => vendorSchema
|
|
1854
1991
|
});
|
|
1855
1992
|
import { z as z15 } from "zod";
|
|
1993
|
+
var vendorCaptchaSchema = z15.object({
|
|
1994
|
+
url: z15.string()
|
|
1995
|
+
});
|
|
1996
|
+
var vendorFeaturesSchema = z15.object({
|
|
1997
|
+
customerPortal: z15.boolean().optional(),
|
|
1998
|
+
phoneNumber: z15.boolean().optional(),
|
|
1999
|
+
marketingConsent: z15.nativeEnum(MarketingConsentType).optional().catch(void 0),
|
|
2000
|
+
// Absent on backends from before captchas, which never ask for one.
|
|
2001
|
+
captcha: vendorCaptchaSchema.nullish()
|
|
2002
|
+
});
|
|
1856
2003
|
var vendorSchema = z15.object({
|
|
1857
2004
|
id: z15.string(),
|
|
1858
2005
|
name: z15.string(),
|
|
1859
|
-
logoUrl: z15.string().nullable()
|
|
2006
|
+
logoUrl: z15.string().nullable(),
|
|
2007
|
+
features: vendorFeaturesSchema.optional()
|
|
1860
2008
|
});
|
|
1861
2009
|
|
|
1862
2010
|
// src/vendor/endpoints.ts
|
|
@@ -2551,6 +2699,7 @@ export {
|
|
|
2551
2699
|
ActivationRequestStatus,
|
|
2552
2700
|
ActivationStatus,
|
|
2553
2701
|
Architecture,
|
|
2702
|
+
CaptchaRequiredError,
|
|
2554
2703
|
CartUtils,
|
|
2555
2704
|
ConnectableAccountProvider,
|
|
2556
2705
|
ConsoleLogger,
|
|
@@ -2576,6 +2725,7 @@ export {
|
|
|
2576
2725
|
SubscriptionCancellationReason,
|
|
2577
2726
|
SubscriptionStatus,
|
|
2578
2727
|
TokenStore,
|
|
2728
|
+
accessTokenLifetime,
|
|
2579
2729
|
imageWidths,
|
|
2580
2730
|
isLocalStorageAvailable,
|
|
2581
2731
|
isPromotionDisplay,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moonbase.sh/storefront-api",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.7.0",
|
|
5
5
|
"description": "Package to let you build storefronts with Moonbase.sh as payment and delivery provider",
|
|
6
6
|
"author": "Tobias Lønnerød Madsen <m@dsen.tv>",
|
|
7
7
|
"license": "MIT",
|