@moonbase.sh/storefront-api 3.6.0 → 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/dist/index.cjs +152 -24
- package/dist/index.d.cts +45 -1
- package/dist/index.d.ts +45 -1
- package/dist/index.js +151 -24
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -51,6 +51,7 @@ __export(index_exports, {
|
|
|
51
51
|
SubscriptionCancellationReason: () => SubscriptionCancellationReason,
|
|
52
52
|
SubscriptionStatus: () => SubscriptionStatus,
|
|
53
53
|
TokenStore: () => TokenStore,
|
|
54
|
+
accessTokenLifetime: () => accessTokenLifetime,
|
|
54
55
|
imageWidths: () => imageWidths,
|
|
55
56
|
isLocalStorageAvailable: () => isLocalStorageAvailable,
|
|
56
57
|
isPromotionDisplay: () => isPromotionDisplay,
|
|
@@ -460,7 +461,10 @@ var activationRequestSchema = import_zod4.z.object({
|
|
|
460
461
|
requiresAccount: import_zod4.z.boolean(),
|
|
461
462
|
requiresNewsletterOptIn: import_zod4.z.boolean(),
|
|
462
463
|
numberOfDaysPerProduct: import_zod4.z.number(),
|
|
463
|
-
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)
|
|
464
468
|
}).optional(),
|
|
465
469
|
licenseEligibility: import_zod4.z.object({
|
|
466
470
|
eligible: import_zod4.z.boolean()
|
|
@@ -874,8 +878,13 @@ var IdentityEndpoints = class {
|
|
|
874
878
|
this.tokenStore.setUser(identity);
|
|
875
879
|
return { status: "signed-in", user: userSchema.parse(identity) };
|
|
876
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
|
+
*/
|
|
877
886
|
async signOut() {
|
|
878
|
-
this.tokenStore.
|
|
887
|
+
await this.tokenStore.signOut();
|
|
879
888
|
}
|
|
880
889
|
async update(name, email, emailConfirmationToken, communicationPreferences) {
|
|
881
890
|
const response = await this.api.authenticatedFetch("/api/customer/identity", userSchema, {
|
|
@@ -1108,7 +1117,7 @@ var ProductEndpoints = class {
|
|
|
1108
1117
|
var import_zod14 = require("zod");
|
|
1109
1118
|
|
|
1110
1119
|
// package.json
|
|
1111
|
-
var version = "3.
|
|
1120
|
+
var version = "3.7.0";
|
|
1112
1121
|
|
|
1113
1122
|
// src/utils/client.ts
|
|
1114
1123
|
function buildClientHeader(sdk) {
|
|
@@ -1787,24 +1796,36 @@ var InMemoryStore = class {
|
|
|
1787
1796
|
};
|
|
1788
1797
|
|
|
1789
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
|
+
}
|
|
1790
1814
|
var _TokenStore = class _TokenStore {
|
|
1791
1815
|
constructor(configuration) {
|
|
1792
1816
|
this.configuration = configuration;
|
|
1793
1817
|
this.tokens = null;
|
|
1794
1818
|
this.refreshTimeoutId = null;
|
|
1795
1819
|
this.refreshPromise = null;
|
|
1820
|
+
this.waitingForVisibility = false;
|
|
1821
|
+
this.sessionEndedListeners = [];
|
|
1796
1822
|
var _a, _b;
|
|
1797
1823
|
this.logger = (_a = configuration.logger) != null ? _a : new ConsoleLogger();
|
|
1798
1824
|
this.store = (_b = configuration.store) != null ? _b : isLocalStorageAvailable() ? new LocalStorageStore((err) => this.warnAboutStorage(err)) : new InMemoryStore();
|
|
1799
1825
|
if (typeof window !== "undefined" && !configuration.store && !(this.store instanceof LocalStorageStore))
|
|
1800
1826
|
this.warnAboutStorage();
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
this.tokens = {
|
|
1804
|
-
...storedToken,
|
|
1805
|
-
expiresAt: new Date(storedToken.expiresAt)
|
|
1806
|
-
};
|
|
1807
|
-
}
|
|
1827
|
+
this.tokens = this.readStoredTokens();
|
|
1828
|
+
this.scheduleRefresh();
|
|
1808
1829
|
this.store.listen(_TokenStore.storageKey, (updatedTokens) => this.handleStorageUpdate(updatedTokens));
|
|
1809
1830
|
}
|
|
1810
1831
|
get user() {
|
|
@@ -1838,29 +1859,91 @@ var _TokenStore = class _TokenStore {
|
|
|
1838
1859
|
} else {
|
|
1839
1860
|
this.tokens = {
|
|
1840
1861
|
...identity,
|
|
1841
|
-
|
|
1842
|
-
expiresAt: new Date((/* @__PURE__ */ new Date()).getTime() + 15 * 60 * 1e3)
|
|
1862
|
+
expiresAt: new Date(Date.now() + accessTokenLifetime(identity.accessToken))
|
|
1843
1863
|
};
|
|
1844
1864
|
}
|
|
1845
1865
|
this.store.set(_TokenStore.storageKey, this.tokens);
|
|
1846
|
-
|
|
1847
|
-
if (this.refreshTimeoutId != null)
|
|
1848
|
-
window.clearTimeout(this.refreshTimeoutId);
|
|
1849
|
-
this.refreshTimeoutId = window.setTimeout(() => {
|
|
1850
|
-
this.startRefresh().catch((err) => this.logger.warn("Could not refresh access token", err));
|
|
1851
|
-
}, 10 * 60 * 1e3);
|
|
1852
|
-
}
|
|
1866
|
+
this.scheduleRefresh();
|
|
1853
1867
|
return this.tokens;
|
|
1854
1868
|
}
|
|
1855
1869
|
clear() {
|
|
1856
1870
|
this.tokens = null;
|
|
1857
1871
|
this.store.remove(_TokenStore.storageKey);
|
|
1858
|
-
|
|
1859
|
-
window.clearTimeout(this.refreshTimeoutId);
|
|
1872
|
+
this.scheduleRefresh();
|
|
1860
1873
|
return null;
|
|
1861
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
|
+
}
|
|
1862
1899
|
get isExpired() {
|
|
1863
|
-
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));
|
|
1864
1947
|
}
|
|
1865
1948
|
/**
|
|
1866
1949
|
* Start a refresh and hold it so concurrent callers join it rather than firing
|
|
@@ -1877,7 +1960,45 @@ var _TokenStore = class _TokenStore {
|
|
|
1877
1960
|
this.refreshPromise = pending;
|
|
1878
1961
|
return pending;
|
|
1879
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
|
+
*/
|
|
1880
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
|
+
}
|
|
1881
2002
|
if (!this.tokens)
|
|
1882
2003
|
throw new Error("No tokens found to refresh");
|
|
1883
2004
|
const response = await fetch(`${this.configuration.endpoint}/api/customer/identity/refresh?token=${encodeURIComponent(this.tokens.refreshToken)}`, {
|
|
@@ -1893,6 +2014,13 @@ var _TokenStore = class _TokenStore {
|
|
|
1893
2014
|
if (response.status !== 200) {
|
|
1894
2015
|
if (response.status === 403 || response.status === 404) {
|
|
1895
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
|
+
}
|
|
1896
2024
|
return null;
|
|
1897
2025
|
}
|
|
1898
2026
|
throw new MoonbaseError("Unexpected result", `Could not refresh access token, status code ${response.status}`, response.status);
|
|
@@ -1903,11 +2031,10 @@ var _TokenStore = class _TokenStore {
|
|
|
1903
2031
|
}
|
|
1904
2032
|
handleStorageUpdate(updatedTokens) {
|
|
1905
2033
|
this.tokens = updatedTokens;
|
|
1906
|
-
if (this.refreshTimeoutId != null && typeof window !== "undefined")
|
|
1907
|
-
window.clearTimeout(this.refreshTimeoutId);
|
|
1908
2034
|
if (this.tokens) {
|
|
1909
2035
|
this.tokens.expiresAt = new Date(this.tokens.expiresAt);
|
|
1910
2036
|
}
|
|
2037
|
+
this.scheduleRefresh();
|
|
1911
2038
|
}
|
|
1912
2039
|
warnAboutStorage(err) {
|
|
1913
2040
|
this.logger.warn(
|
|
@@ -2663,6 +2790,7 @@ var MoonbaseClient = class {
|
|
|
2663
2790
|
SubscriptionCancellationReason,
|
|
2664
2791
|
SubscriptionStatus,
|
|
2665
2792
|
TokenStore,
|
|
2793
|
+
accessTokenLifetime,
|
|
2666
2794
|
imageWidths,
|
|
2667
2795
|
isLocalStorageAvailable,
|
|
2668
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;
|
|
@@ -1696,6 +1735,11 @@ declare class IdentityEndpoints {
|
|
|
1696
1735
|
get(): Promise<User>;
|
|
1697
1736
|
signIn(email: string, password: string): Promise<User>;
|
|
1698
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
|
+
*/
|
|
1699
1743
|
signOut(): Promise<void>;
|
|
1700
1744
|
update(name: string | null, email: string, emailConfirmationToken?: string, communicationPreferences?: CommunicationPreferences): Promise<{
|
|
1701
1745
|
needsEmailConfirmationToken: boolean;
|
|
@@ -47310,4 +47354,4 @@ declare class MoonbaseClient {
|
|
|
47310
47354
|
orders: OrderEndpoints;
|
|
47311
47355
|
}
|
|
47312
47356
|
|
|
47313
|
-
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, 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;
|
|
@@ -1696,6 +1735,11 @@ declare class IdentityEndpoints {
|
|
|
1696
1735
|
get(): Promise<User>;
|
|
1697
1736
|
signIn(email: string, password: string): Promise<User>;
|
|
1698
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
|
+
*/
|
|
1699
1743
|
signOut(): Promise<void>;
|
|
1700
1744
|
update(name: string | null, email: string, emailConfirmationToken?: string, communicationPreferences?: CommunicationPreferences): Promise<{
|
|
1701
1745
|
needsEmailConfirmationToken: boolean;
|
|
@@ -47310,4 +47354,4 @@ declare class MoonbaseClient {
|
|
|
47310
47354
|
orders: OrderEndpoints;
|
|
47311
47355
|
}
|
|
47312
47356
|
|
|
47313
|
-
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, 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()
|
|
@@ -811,8 +814,13 @@ var IdentityEndpoints = class {
|
|
|
811
814
|
this.tokenStore.setUser(identity);
|
|
812
815
|
return { status: "signed-in", user: userSchema.parse(identity) };
|
|
813
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
|
+
*/
|
|
814
822
|
async signOut() {
|
|
815
|
-
this.tokenStore.
|
|
823
|
+
await this.tokenStore.signOut();
|
|
816
824
|
}
|
|
817
825
|
async update(name, email, emailConfirmationToken, communicationPreferences) {
|
|
818
826
|
const response = await this.api.authenticatedFetch("/api/customer/identity", userSchema, {
|
|
@@ -1045,7 +1053,7 @@ var ProductEndpoints = class {
|
|
|
1045
1053
|
import { z as z14 } from "zod";
|
|
1046
1054
|
|
|
1047
1055
|
// package.json
|
|
1048
|
-
var version = "3.
|
|
1056
|
+
var version = "3.7.0";
|
|
1049
1057
|
|
|
1050
1058
|
// src/utils/client.ts
|
|
1051
1059
|
function buildClientHeader(sdk) {
|
|
@@ -1724,24 +1732,36 @@ var InMemoryStore = class {
|
|
|
1724
1732
|
};
|
|
1725
1733
|
|
|
1726
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
|
+
}
|
|
1727
1750
|
var _TokenStore = class _TokenStore {
|
|
1728
1751
|
constructor(configuration) {
|
|
1729
1752
|
this.configuration = configuration;
|
|
1730
1753
|
this.tokens = null;
|
|
1731
1754
|
this.refreshTimeoutId = null;
|
|
1732
1755
|
this.refreshPromise = null;
|
|
1756
|
+
this.waitingForVisibility = false;
|
|
1757
|
+
this.sessionEndedListeners = [];
|
|
1733
1758
|
var _a, _b;
|
|
1734
1759
|
this.logger = (_a = configuration.logger) != null ? _a : new ConsoleLogger();
|
|
1735
1760
|
this.store = (_b = configuration.store) != null ? _b : isLocalStorageAvailable() ? new LocalStorageStore((err) => this.warnAboutStorage(err)) : new InMemoryStore();
|
|
1736
1761
|
if (typeof window !== "undefined" && !configuration.store && !(this.store instanceof LocalStorageStore))
|
|
1737
1762
|
this.warnAboutStorage();
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
this.tokens = {
|
|
1741
|
-
...storedToken,
|
|
1742
|
-
expiresAt: new Date(storedToken.expiresAt)
|
|
1743
|
-
};
|
|
1744
|
-
}
|
|
1763
|
+
this.tokens = this.readStoredTokens();
|
|
1764
|
+
this.scheduleRefresh();
|
|
1745
1765
|
this.store.listen(_TokenStore.storageKey, (updatedTokens) => this.handleStorageUpdate(updatedTokens));
|
|
1746
1766
|
}
|
|
1747
1767
|
get user() {
|
|
@@ -1775,29 +1795,91 @@ var _TokenStore = class _TokenStore {
|
|
|
1775
1795
|
} else {
|
|
1776
1796
|
this.tokens = {
|
|
1777
1797
|
...identity,
|
|
1778
|
-
|
|
1779
|
-
expiresAt: new Date((/* @__PURE__ */ new Date()).getTime() + 15 * 60 * 1e3)
|
|
1798
|
+
expiresAt: new Date(Date.now() + accessTokenLifetime(identity.accessToken))
|
|
1780
1799
|
};
|
|
1781
1800
|
}
|
|
1782
1801
|
this.store.set(_TokenStore.storageKey, this.tokens);
|
|
1783
|
-
|
|
1784
|
-
if (this.refreshTimeoutId != null)
|
|
1785
|
-
window.clearTimeout(this.refreshTimeoutId);
|
|
1786
|
-
this.refreshTimeoutId = window.setTimeout(() => {
|
|
1787
|
-
this.startRefresh().catch((err) => this.logger.warn("Could not refresh access token", err));
|
|
1788
|
-
}, 10 * 60 * 1e3);
|
|
1789
|
-
}
|
|
1802
|
+
this.scheduleRefresh();
|
|
1790
1803
|
return this.tokens;
|
|
1791
1804
|
}
|
|
1792
1805
|
clear() {
|
|
1793
1806
|
this.tokens = null;
|
|
1794
1807
|
this.store.remove(_TokenStore.storageKey);
|
|
1795
|
-
|
|
1796
|
-
window.clearTimeout(this.refreshTimeoutId);
|
|
1808
|
+
this.scheduleRefresh();
|
|
1797
1809
|
return null;
|
|
1798
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
|
+
}
|
|
1799
1835
|
get isExpired() {
|
|
1800
|
-
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));
|
|
1801
1883
|
}
|
|
1802
1884
|
/**
|
|
1803
1885
|
* Start a refresh and hold it so concurrent callers join it rather than firing
|
|
@@ -1814,7 +1896,45 @@ var _TokenStore = class _TokenStore {
|
|
|
1814
1896
|
this.refreshPromise = pending;
|
|
1815
1897
|
return pending;
|
|
1816
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
|
+
*/
|
|
1817
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
|
+
}
|
|
1818
1938
|
if (!this.tokens)
|
|
1819
1939
|
throw new Error("No tokens found to refresh");
|
|
1820
1940
|
const response = await fetch(`${this.configuration.endpoint}/api/customer/identity/refresh?token=${encodeURIComponent(this.tokens.refreshToken)}`, {
|
|
@@ -1830,6 +1950,13 @@ var _TokenStore = class _TokenStore {
|
|
|
1830
1950
|
if (response.status !== 200) {
|
|
1831
1951
|
if (response.status === 403 || response.status === 404) {
|
|
1832
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
|
+
}
|
|
1833
1960
|
return null;
|
|
1834
1961
|
}
|
|
1835
1962
|
throw new MoonbaseError("Unexpected result", `Could not refresh access token, status code ${response.status}`, response.status);
|
|
@@ -1840,11 +1967,10 @@ var _TokenStore = class _TokenStore {
|
|
|
1840
1967
|
}
|
|
1841
1968
|
handleStorageUpdate(updatedTokens) {
|
|
1842
1969
|
this.tokens = updatedTokens;
|
|
1843
|
-
if (this.refreshTimeoutId != null && typeof window !== "undefined")
|
|
1844
|
-
window.clearTimeout(this.refreshTimeoutId);
|
|
1845
1970
|
if (this.tokens) {
|
|
1846
1971
|
this.tokens.expiresAt = new Date(this.tokens.expiresAt);
|
|
1847
1972
|
}
|
|
1973
|
+
this.scheduleRefresh();
|
|
1848
1974
|
}
|
|
1849
1975
|
warnAboutStorage(err) {
|
|
1850
1976
|
this.logger.warn(
|
|
@@ -2599,6 +2725,7 @@ export {
|
|
|
2599
2725
|
SubscriptionCancellationReason,
|
|
2600
2726
|
SubscriptionStatus,
|
|
2601
2727
|
TokenStore,
|
|
2728
|
+
accessTokenLifetime,
|
|
2602
2729
|
imageWidths,
|
|
2603
2730
|
isLocalStorageAvailable,
|
|
2604
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",
|