@moonbase.sh/storefront-api 3.6.0 → 3.8.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 +41 -1
- package/dist/index.cjs +188 -29
- package/dist/index.d.cts +1762 -1571
- package/dist/index.d.ts +1762 -1571
- package/dist/index.js +187 -29
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -56,7 +56,13 @@ function utmToObject(utm) {
|
|
|
56
56
|
utm_campaign: utm == null ? void 0 : utm.campaign,
|
|
57
57
|
utm_term: utm == null ? void 0 : utm.term,
|
|
58
58
|
utm_content: utm == null ? void 0 : utm.content,
|
|
59
|
-
utm_referrer: utm == null ? void 0 : utm.referrer
|
|
59
|
+
utm_referrer: utm == null ? void 0 : utm.referrer,
|
|
60
|
+
gclid: utm == null ? void 0 : utm.gclid,
|
|
61
|
+
gbraid: utm == null ? void 0 : utm.gbraid,
|
|
62
|
+
wbraid: utm == null ? void 0 : utm.wbraid,
|
|
63
|
+
fbclid: utm == null ? void 0 : utm.fbclid,
|
|
64
|
+
msclkid: utm == null ? void 0 : utm.msclkid,
|
|
65
|
+
ttclid: utm == null ? void 0 : utm.ttclid
|
|
60
66
|
}).filter(([_, value]) => value !== void 0).reduce((obj, [key, value]) => ({
|
|
61
67
|
...obj,
|
|
62
68
|
[key]: value
|
|
@@ -148,6 +154,7 @@ var schemas_exports = {};
|
|
|
148
154
|
__export(schemas_exports, {
|
|
149
155
|
downloadSchema: () => downloadSchema,
|
|
150
156
|
manifestSchema: () => manifestSchema,
|
|
157
|
+
productReleaseSchema: () => productReleaseSchema,
|
|
151
158
|
productSummarySchema: () => productSummarySchema
|
|
152
159
|
});
|
|
153
160
|
import { z as z2 } from "zod";
|
|
@@ -210,10 +217,20 @@ var productSummarySchema = z2.object({
|
|
|
210
217
|
downloadsNeedsOwnership: z2.boolean(),
|
|
211
218
|
downloadsNeedsGroupMembership: z2.boolean(),
|
|
212
219
|
// Whether this caller clears every requirement above, including the group membership the client
|
|
213
|
-
// can't see for itself.
|
|
220
|
+
// can't see for itself. False with none of them set means the merchant closed the release to everyone.
|
|
214
221
|
downloadsAllowed: z2.boolean(),
|
|
215
222
|
downloads: z2.array(downloadSchema).optional()
|
|
216
223
|
});
|
|
224
|
+
var productReleaseSchema = z2.object({
|
|
225
|
+
version: z2.string(),
|
|
226
|
+
description: z2.string().nullish(),
|
|
227
|
+
publishedAt: z2.coerce.date(),
|
|
228
|
+
// Whether this caller may download the release, resolved against its own access control: a
|
|
229
|
+
// merchant can keep past releases to owners while the current one is open to anyone.
|
|
230
|
+
downloadsAllowed: z2.boolean(),
|
|
231
|
+
downloads: z2.array(downloadSchema),
|
|
232
|
+
properties: z2.record(rawPropertyValueSchema).nullish()
|
|
233
|
+
});
|
|
217
234
|
|
|
218
235
|
// src/storefront/schemas.ts
|
|
219
236
|
var storefrontProductSchema = z3.object({
|
|
@@ -397,7 +414,10 @@ var activationRequestSchema = z4.object({
|
|
|
397
414
|
requiresAccount: z4.boolean(),
|
|
398
415
|
requiresNewsletterOptIn: z4.boolean(),
|
|
399
416
|
numberOfDaysPerProduct: z4.number(),
|
|
400
|
-
numberOfDaysRemaining: z4.number()
|
|
417
|
+
numberOfDaysRemaining: z4.number(),
|
|
418
|
+
// The trial never expires, so the day counts are not worth showing. Absent from a backend that
|
|
419
|
+
// predates endless trials.
|
|
420
|
+
endless: z4.boolean().default(false)
|
|
401
421
|
}).optional(),
|
|
402
422
|
licenseEligibility: z4.object({
|
|
403
423
|
eligible: z4.boolean()
|
|
@@ -774,8 +794,12 @@ var IdentityEndpoints = class {
|
|
|
774
794
|
return userSchema.parse(identity);
|
|
775
795
|
}
|
|
776
796
|
async signUp(name, email, password, address, acceptedPrivacyPolicy, acceptedTermsAndConditions, communicationOptIn, options) {
|
|
777
|
-
const
|
|
778
|
-
|
|
797
|
+
const query = new URLSearchParams({
|
|
798
|
+
scheme: "JWT",
|
|
799
|
+
communicationOptIn: communicationOptIn ? "true" : "false",
|
|
800
|
+
...utmToObject(options == null ? void 0 : options.utm)
|
|
801
|
+
});
|
|
802
|
+
const response = await fetch(`${this.api.baseUrl}/api/customer/identity/sign-up?${query.toString()}`, {
|
|
779
803
|
method: "POST",
|
|
780
804
|
headers: {
|
|
781
805
|
"Accept": "application/json",
|
|
@@ -811,8 +835,13 @@ var IdentityEndpoints = class {
|
|
|
811
835
|
this.tokenStore.setUser(identity);
|
|
812
836
|
return { status: "signed-in", user: userSchema.parse(identity) };
|
|
813
837
|
}
|
|
838
|
+
/**
|
|
839
|
+
* Signs the buyer out here at once, and ends the session on the server so its refresh
|
|
840
|
+
* token stops working too. Resolves once the server has answered, and never rejects:
|
|
841
|
+
* the buyer is signed out in this browser whatever happens to that request.
|
|
842
|
+
*/
|
|
814
843
|
async signOut() {
|
|
815
|
-
this.tokenStore.
|
|
844
|
+
await this.tokenStore.signOut();
|
|
816
845
|
}
|
|
817
846
|
async update(name, email, emailConfirmationToken, communicationPreferences) {
|
|
818
847
|
const response = await this.api.authenticatedFetch("/api/customer/identity", userSchema, {
|
|
@@ -1019,6 +1048,15 @@ var ProductEndpoints = class {
|
|
|
1019
1048
|
const response = await this.api.fetch(`/api/customer/inventory/products/${productId}${version2 ? `?version=${version2}${this.configuration.includeManifests ? "&includeManifests=true" : ""}` : this.configuration.includeManifests ? "?includeManifests=true" : ""}`, productSummarySchema);
|
|
1020
1049
|
return response.data;
|
|
1021
1050
|
}
|
|
1051
|
+
/**
|
|
1052
|
+
* The product's published releases, highest version first, with their release notes and files,
|
|
1053
|
+
* and whether the caller may download each. Open to anyone who can see the product, signed in or
|
|
1054
|
+
* not. A page can hold fewer releases than its size, so keep following `next` while `hasMore` is set.
|
|
1055
|
+
*/
|
|
1056
|
+
async getReleases(productId, nextUrl) {
|
|
1057
|
+
const response = await this.api.fetch(nextUrl || `/api/customer/inventory/products/${productId}/releases${this.configuration.includeManifests ? "?includeManifests=true" : ""}`, paged(productReleaseSchema));
|
|
1058
|
+
return response.data;
|
|
1059
|
+
}
|
|
1022
1060
|
async getOwned(nextUrl) {
|
|
1023
1061
|
const response = await this.api.authenticatedFetch(nextUrl || `/api/customer/inventory/products${this.configuration.includeManifests ? "?includeManifests=true" : ""}`, paged(productSummarySchema));
|
|
1024
1062
|
return response.data;
|
|
@@ -1045,7 +1083,7 @@ var ProductEndpoints = class {
|
|
|
1045
1083
|
import { z as z14 } from "zod";
|
|
1046
1084
|
|
|
1047
1085
|
// package.json
|
|
1048
|
-
var version = "3.
|
|
1086
|
+
var version = "3.8.0";
|
|
1049
1087
|
|
|
1050
1088
|
// src/utils/client.ts
|
|
1051
1089
|
function buildClientHeader(sdk) {
|
|
@@ -1724,24 +1762,36 @@ var InMemoryStore = class {
|
|
|
1724
1762
|
};
|
|
1725
1763
|
|
|
1726
1764
|
// src/utils/tokenStore.ts
|
|
1765
|
+
var defaultAccessTokenLifetime = 15 * 60 * 1e3;
|
|
1766
|
+
var renewBeforeExpiry = 5 * 60 * 1e3;
|
|
1767
|
+
var expiryMargin = 30 * 1e3;
|
|
1768
|
+
var refreshLockName = "moonbase_auth_refresh";
|
|
1769
|
+
function accessTokenLifetime(accessToken) {
|
|
1770
|
+
try {
|
|
1771
|
+
const payload = accessToken.split(".")[1].replaceAll("-", "+").replaceAll("_", "/");
|
|
1772
|
+
const claims = JSON.parse(atob(payload.padEnd(Math.ceil(payload.length / 4) * 4, "=")));
|
|
1773
|
+
const lifetime = (Number(claims.exp) - Number(claims.iat)) * 1e3;
|
|
1774
|
+
if (Number.isFinite(lifetime) && lifetime > 0 && lifetime <= 24 * 60 * 60 * 1e3)
|
|
1775
|
+
return lifetime;
|
|
1776
|
+
} catch (e) {
|
|
1777
|
+
}
|
|
1778
|
+
return defaultAccessTokenLifetime;
|
|
1779
|
+
}
|
|
1727
1780
|
var _TokenStore = class _TokenStore {
|
|
1728
1781
|
constructor(configuration) {
|
|
1729
1782
|
this.configuration = configuration;
|
|
1730
1783
|
this.tokens = null;
|
|
1731
1784
|
this.refreshTimeoutId = null;
|
|
1732
1785
|
this.refreshPromise = null;
|
|
1786
|
+
this.waitingForVisibility = false;
|
|
1787
|
+
this.sessionEndedListeners = [];
|
|
1733
1788
|
var _a, _b;
|
|
1734
1789
|
this.logger = (_a = configuration.logger) != null ? _a : new ConsoleLogger();
|
|
1735
1790
|
this.store = (_b = configuration.store) != null ? _b : isLocalStorageAvailable() ? new LocalStorageStore((err) => this.warnAboutStorage(err)) : new InMemoryStore();
|
|
1736
1791
|
if (typeof window !== "undefined" && !configuration.store && !(this.store instanceof LocalStorageStore))
|
|
1737
1792
|
this.warnAboutStorage();
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
this.tokens = {
|
|
1741
|
-
...storedToken,
|
|
1742
|
-
expiresAt: new Date(storedToken.expiresAt)
|
|
1743
|
-
};
|
|
1744
|
-
}
|
|
1793
|
+
this.tokens = this.readStoredTokens();
|
|
1794
|
+
this.scheduleRefresh();
|
|
1745
1795
|
this.store.listen(_TokenStore.storageKey, (updatedTokens) => this.handleStorageUpdate(updatedTokens));
|
|
1746
1796
|
}
|
|
1747
1797
|
get user() {
|
|
@@ -1775,29 +1825,91 @@ var _TokenStore = class _TokenStore {
|
|
|
1775
1825
|
} else {
|
|
1776
1826
|
this.tokens = {
|
|
1777
1827
|
...identity,
|
|
1778
|
-
|
|
1779
|
-
expiresAt: new Date((/* @__PURE__ */ new Date()).getTime() + 15 * 60 * 1e3)
|
|
1828
|
+
expiresAt: new Date(Date.now() + accessTokenLifetime(identity.accessToken))
|
|
1780
1829
|
};
|
|
1781
1830
|
}
|
|
1782
1831
|
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
|
-
}
|
|
1832
|
+
this.scheduleRefresh();
|
|
1790
1833
|
return this.tokens;
|
|
1791
1834
|
}
|
|
1792
1835
|
clear() {
|
|
1793
1836
|
this.tokens = null;
|
|
1794
1837
|
this.store.remove(_TokenStore.storageKey);
|
|
1795
|
-
|
|
1796
|
-
window.clearTimeout(this.refreshTimeoutId);
|
|
1838
|
+
this.scheduleRefresh();
|
|
1797
1839
|
return null;
|
|
1798
1840
|
}
|
|
1841
|
+
async signOut() {
|
|
1842
|
+
const tokens = this.tokens;
|
|
1843
|
+
this.clear();
|
|
1844
|
+
if (!tokens)
|
|
1845
|
+
return;
|
|
1846
|
+
try {
|
|
1847
|
+
await fetch(`${this.configuration.endpoint}/api/customer/identity/revoke?token=${encodeURIComponent(tokens.refreshToken)}`, {
|
|
1848
|
+
method: "POST",
|
|
1849
|
+
keepalive: true,
|
|
1850
|
+
headers: {
|
|
1851
|
+
"Accept": "application/json",
|
|
1852
|
+
"Content-Type": "text/plain",
|
|
1853
|
+
"x-mb-cors": "1",
|
|
1854
|
+
"x-mb-client": buildClientHeader(this.configuration.sdk)
|
|
1855
|
+
},
|
|
1856
|
+
body: tokens.accessToken
|
|
1857
|
+
});
|
|
1858
|
+
} catch (err) {
|
|
1859
|
+
this.logger.warn("Could not end the session on the server", err);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
onSessionEnded(listener) {
|
|
1863
|
+
this.sessionEndedListeners.push(listener);
|
|
1864
|
+
}
|
|
1799
1865
|
get isExpired() {
|
|
1800
|
-
return this.tokens != null && this.tokens.expiresAt
|
|
1866
|
+
return this.tokens != null && this.tokens.expiresAt.getTime() - expiryMargin < Date.now();
|
|
1867
|
+
}
|
|
1868
|
+
readStoredTokens() {
|
|
1869
|
+
const stored = this.store.get(_TokenStore.storageKey);
|
|
1870
|
+
return stored ? { ...stored, expiresAt: new Date(stored.expiresAt) } : null;
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* (Re)arms the timer that renews the access token shortly before it runs out, or disarms
|
|
1874
|
+
* it when there is nothing to renew. Every tab arms one, and `refreshTokens` makes sure
|
|
1875
|
+
* only one of them actually renews.
|
|
1876
|
+
*/
|
|
1877
|
+
scheduleRefresh() {
|
|
1878
|
+
if (typeof window === "undefined")
|
|
1879
|
+
return;
|
|
1880
|
+
if (this.refreshTimeoutId != null)
|
|
1881
|
+
window.clearTimeout(this.refreshTimeoutId);
|
|
1882
|
+
this.refreshTimeoutId = null;
|
|
1883
|
+
if (!this.tokens)
|
|
1884
|
+
return;
|
|
1885
|
+
const delay = Math.max(0, this.renewalDueAt(this.tokens) - Date.now());
|
|
1886
|
+
this.refreshTimeoutId = window.setTimeout(() => this.renewInBackground(), delay);
|
|
1887
|
+
}
|
|
1888
|
+
renewalDueAt(tokens) {
|
|
1889
|
+
const lead = Math.min(renewBeforeExpiry, accessTokenLifetime(tokens.accessToken) / 3);
|
|
1890
|
+
return tokens.expiresAt.getTime() - lead;
|
|
1891
|
+
}
|
|
1892
|
+
renewInBackground() {
|
|
1893
|
+
var _a;
|
|
1894
|
+
this.refreshTimeoutId = null;
|
|
1895
|
+
if (!this.tokens)
|
|
1896
|
+
return;
|
|
1897
|
+
if (this.renewalDueAt(this.tokens) > Date.now()) {
|
|
1898
|
+
this.scheduleRefresh();
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
1902
|
+
if (!this.waitingForVisibility) {
|
|
1903
|
+
this.waitingForVisibility = true;
|
|
1904
|
+
document.addEventListener("visibilitychange", () => {
|
|
1905
|
+
this.waitingForVisibility = false;
|
|
1906
|
+
this.renewInBackground();
|
|
1907
|
+
}, { once: true });
|
|
1908
|
+
}
|
|
1909
|
+
return;
|
|
1910
|
+
}
|
|
1911
|
+
const pending = (_a = this.refreshPromise) != null ? _a : this.startRefresh();
|
|
1912
|
+
pending.catch((err) => this.logger.warn("Could not refresh access token", err));
|
|
1801
1913
|
}
|
|
1802
1914
|
/**
|
|
1803
1915
|
* Start a refresh and hold it so concurrent callers join it rather than firing
|
|
@@ -1814,7 +1926,45 @@ var _TokenStore = class _TokenStore {
|
|
|
1814
1926
|
this.refreshPromise = pending;
|
|
1815
1927
|
return pending;
|
|
1816
1928
|
}
|
|
1929
|
+
/**
|
|
1930
|
+
* A refresh token can be used once: the server answers a second use by revoking the
|
|
1931
|
+
* session, taking it for a stolen token. So refreshes are serialised across every tab of
|
|
1932
|
+
* the origin, and whoever gets the lock second takes up what the first one stored rather
|
|
1933
|
+
* than spending the same refresh token again. The stored copy has to be read, not waited
|
|
1934
|
+
* for: storage events never reach the document that wrote them, so a second client on
|
|
1935
|
+
* the same page would not hear about it.
|
|
1936
|
+
*/
|
|
1817
1937
|
async refreshTokens() {
|
|
1938
|
+
var _a;
|
|
1939
|
+
const spending = (_a = this.tokens) == null ? void 0 : _a.refreshToken;
|
|
1940
|
+
const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
|
|
1941
|
+
if (locks == null ? void 0 : locks.request) {
|
|
1942
|
+
let held = false;
|
|
1943
|
+
try {
|
|
1944
|
+
return await locks.request(refreshLockName, () => {
|
|
1945
|
+
held = true;
|
|
1946
|
+
return this.refreshHoldingLock(spending);
|
|
1947
|
+
});
|
|
1948
|
+
} catch (err) {
|
|
1949
|
+
if (held)
|
|
1950
|
+
throw err;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
return await this.refreshHoldingLock(spending);
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* @param spending The refresh token this refresh set out to use. If the session has moved
|
|
1957
|
+
* on from it while we waited for the lock, someone else has renewed already.
|
|
1958
|
+
*/
|
|
1959
|
+
async refreshHoldingLock(spending) {
|
|
1960
|
+
var _a;
|
|
1961
|
+
const stored = this.readStoredTokens();
|
|
1962
|
+
if (stored && stored.refreshToken !== ((_a = this.tokens) == null ? void 0 : _a.refreshToken))
|
|
1963
|
+
this.tokens = stored;
|
|
1964
|
+
if (this.tokens && this.tokens.refreshToken !== spending && !this.isExpired) {
|
|
1965
|
+
this.scheduleRefresh();
|
|
1966
|
+
return this.tokens;
|
|
1967
|
+
}
|
|
1818
1968
|
if (!this.tokens)
|
|
1819
1969
|
throw new Error("No tokens found to refresh");
|
|
1820
1970
|
const response = await fetch(`${this.configuration.endpoint}/api/customer/identity/refresh?token=${encodeURIComponent(this.tokens.refreshToken)}`, {
|
|
@@ -1830,6 +1980,13 @@ var _TokenStore = class _TokenStore {
|
|
|
1830
1980
|
if (response.status !== 200) {
|
|
1831
1981
|
if (response.status === 403 || response.status === 404) {
|
|
1832
1982
|
this.clear();
|
|
1983
|
+
for (const listener of this.sessionEndedListeners) {
|
|
1984
|
+
try {
|
|
1985
|
+
listener();
|
|
1986
|
+
} catch (err) {
|
|
1987
|
+
this.logger.warn("A listener for the end of the session failed", err);
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1833
1990
|
return null;
|
|
1834
1991
|
}
|
|
1835
1992
|
throw new MoonbaseError("Unexpected result", `Could not refresh access token, status code ${response.status}`, response.status);
|
|
@@ -1840,11 +1997,10 @@ var _TokenStore = class _TokenStore {
|
|
|
1840
1997
|
}
|
|
1841
1998
|
handleStorageUpdate(updatedTokens) {
|
|
1842
1999
|
this.tokens = updatedTokens;
|
|
1843
|
-
if (this.refreshTimeoutId != null && typeof window !== "undefined")
|
|
1844
|
-
window.clearTimeout(this.refreshTimeoutId);
|
|
1845
2000
|
if (this.tokens) {
|
|
1846
2001
|
this.tokens.expiresAt = new Date(this.tokens.expiresAt);
|
|
1847
2002
|
}
|
|
2003
|
+
this.scheduleRefresh();
|
|
1848
2004
|
}
|
|
1849
2005
|
warnAboutStorage(err) {
|
|
1850
2006
|
this.logger.warn(
|
|
@@ -2541,7 +2697,8 @@ function parseOffSiteReferrer(rawReferrer, currentOrigin) {
|
|
|
2541
2697
|
}
|
|
2542
2698
|
function resolveUtm(cached, fresh) {
|
|
2543
2699
|
const freshHasUtm = !!(fresh.source || fresh.medium || fresh.campaign || fresh.term || fresh.content);
|
|
2544
|
-
const
|
|
2700
|
+
const freshHasClickId = !!(fresh.gclid || fresh.gbraid || fresh.wbraid || fresh.fbclid || fresh.msclkid || fresh.ttclid);
|
|
2701
|
+
const freshIsExternalArrival = freshHasUtm || freshHasClickId || !!fresh.referrer;
|
|
2545
2702
|
if (freshIsExternalArrival)
|
|
2546
2703
|
return fresh;
|
|
2547
2704
|
return cached != null ? cached : fresh;
|
|
@@ -2599,6 +2756,7 @@ export {
|
|
|
2599
2756
|
SubscriptionCancellationReason,
|
|
2600
2757
|
SubscriptionStatus,
|
|
2601
2758
|
TokenStore,
|
|
2759
|
+
accessTokenLifetime,
|
|
2602
2760
|
imageWidths,
|
|
2603
2761
|
isLocalStorageAvailable,
|
|
2604
2762
|
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.8.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",
|