@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 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.clear();
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.6.0";
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
- const storedToken = this.store.get(_TokenStore.storageKey);
1802
- if (storedToken) {
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
- // Hardcoded 15 minutes now, might want to check the JWT tho
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
- if (typeof window !== "undefined") {
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
- if (this.refreshTimeoutId != null && typeof window !== "undefined")
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 < /* @__PURE__ */ new Date();
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.clear();
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.6.0";
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
- const storedToken = this.store.get(_TokenStore.storageKey);
1739
- if (storedToken) {
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
- // Hardcoded 15 minutes now, might want to check the JWT tho
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
- if (typeof window !== "undefined") {
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
- if (this.refreshTimeoutId != null && typeof window !== "undefined")
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 < /* @__PURE__ */ new Date();
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.6.0",
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",