@moonbase.sh/storefront-api 3.7.0 → 3.9.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.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({
@@ -604,6 +621,12 @@ var CaptchaRequiredError = class extends MoonbaseError {
604
621
  this.name = "CaptchaRequiredError";
605
622
  }
606
623
  };
624
+ var PasswordSetupRequiredError = class extends MoonbaseError {
625
+ constructor(detail, title, status) {
626
+ super(title != null ? title : "Password setup required", detail, status);
627
+ this.name = "PasswordSetupRequiredError";
628
+ }
629
+ };
607
630
 
608
631
  // src/utils/problemDetails.ts
609
632
  import { z as z7 } from "zod";
@@ -627,6 +650,7 @@ function camelCase(str) {
627
650
  }).join("");
628
651
  }
629
652
  async function handleResponseProblem(response, logger) {
653
+ var _a;
630
654
  if (response.status === 404)
631
655
  throw new NotFoundError();
632
656
  if (response.status === 401) {
@@ -648,6 +672,8 @@ async function handleResponseProblem(response, logger) {
648
672
  throw new StoreClosedError(problemDetails.detail);
649
673
  if (problemDetails.errorType === "CaptchaRequired")
650
674
  throw new CaptchaRequiredError(problemDetails.detail, problemDetails.title);
675
+ if (problemDetails.errorType === "PasswordSetupRequired")
676
+ throw new PasswordSetupRequiredError(problemDetails.detail, problemDetails.title, (_a = problemDetails.status) != null ? _a : response.status);
651
677
  throw new MoonbaseError(
652
678
  problemDetails.title,
653
679
  problemDetails.detail,
@@ -666,6 +692,7 @@ __export(schemas_exports5, {
666
692
  identitySchema: () => identitySchema,
667
693
  ilokConnectedAccountSchema: () => ilokConnectedAccountSchema,
668
694
  pendingActivationSchema: () => pendingActivationSchema,
695
+ pendingPasswordProblemSchema: () => pendingPasswordProblemSchema,
669
696
  userAccountConfirmedSchema: () => userAccountConfirmedSchema,
670
697
  userAccountConfirmedStatusSchema: () => userAccountConfirmedStatusSchema,
671
698
  userSchema: () => userSchema
@@ -739,13 +766,18 @@ var pendingActivationSchema = z8.object({
739
766
  status: z8.literal("activation_pending"),
740
767
  email: z8.string()
741
768
  });
769
+ var pendingPasswordProblemSchema = z8.object({
770
+ errorType: z8.literal("PasswordSetupRequired"),
771
+ pendingPasswordToken: z8.string()
772
+ });
742
773
 
743
774
  // src/identity/endpoints.ts
744
775
  var IdentityEndpoints = class {
745
- constructor(api, tokenStore, logger) {
776
+ constructor(api, tokenStore, logger, pendingPasswords) {
746
777
  this.api = api;
747
778
  this.tokenStore = tokenStore;
748
779
  this.logger = logger;
780
+ this.pendingPasswords = pendingPasswords;
749
781
  }
750
782
  async get() {
751
783
  const response = await this.api.authenticatedFetch("/api/customer/meta/user", userSchema);
@@ -777,8 +809,12 @@ var IdentityEndpoints = class {
777
809
  return userSchema.parse(identity);
778
810
  }
779
811
  async signUp(name, email, password, address, acceptedPrivacyPolicy, acceptedTermsAndConditions, communicationOptIn, options) {
780
- const path = `/api/customer/identity/sign-up?scheme=JWT&communicationOptIn=${communicationOptIn ? "true" : "false"}`;
781
- const response = await fetch(`${this.api.baseUrl}${path}`, {
812
+ const query = new URLSearchParams({
813
+ scheme: "JWT",
814
+ communicationOptIn: communicationOptIn ? "true" : "false",
815
+ ...utmToObject(options == null ? void 0 : options.utm)
816
+ });
817
+ const response = await fetch(`${this.api.baseUrl}/api/customer/identity/sign-up?${query.toString()}`, {
782
818
  method: "POST",
783
819
  headers: {
784
820
  "Accept": "application/json",
@@ -797,8 +833,11 @@ var IdentityEndpoints = class {
797
833
  groupIds: normalizeGroupIds(options == null ? void 0 : options.groupIds)
798
834
  })
799
835
  });
800
- if (response.status >= 400)
836
+ if (response.status >= 400) {
837
+ if (response.status === 409)
838
+ await this.keepPendingPassword(email, response.clone());
801
839
  await handleResponseProblem(response, this.logger);
840
+ }
802
841
  const data = await response.json();
803
842
  if (response.status === 202) {
804
843
  const parsed = pendingActivationSchema.parse(data);
@@ -814,6 +853,19 @@ var IdentityEndpoints = class {
814
853
  this.tokenStore.setUser(identity);
815
854
  return { status: "signed-in", user: userSchema.parse(identity) };
816
855
  }
856
+ /**
857
+ * The address already has an account with no password, so the server emailed it a link and
858
+ * handed back the password just typed, sealed, for that link to set once it is opened here.
859
+ * Never thrown: the caller still gets the `PasswordSetupRequiredError` from the response.
860
+ */
861
+ async keepPendingPassword(email, response) {
862
+ try {
863
+ const problem = pendingPasswordProblemSchema.safeParse(await response.json());
864
+ if (problem.success)
865
+ this.pendingPasswords.set(email, problem.data.pendingPasswordToken);
866
+ } catch (e) {
867
+ }
868
+ }
817
869
  /**
818
870
  * Signs the buyer out here at once, and ends the session on the server so its refresh
819
871
  * token stops working too. Resolves once the server has answered, and never rejects:
@@ -865,7 +917,13 @@ var IdentityEndpoints = class {
865
917
  await handleResponseProblem(response, this.logger);
866
918
  }
867
919
  async confirmAccount(email, code) {
868
- const response = await this.api.fetch(`/api/customer/identity/confirm-account?email=${encodeURIComponent(email)}&code=${encodeURIComponent(code.replaceAll(" ", "+"))}&scheme=JWT`, userAccountConfirmedSchema, { method: "POST" });
920
+ const pendingPasswordToken = this.pendingPasswords.get(email);
921
+ const response = await this.api.fetch(`/api/customer/identity/confirm-account?email=${encodeURIComponent(email)}&code=${encodeURIComponent(code.replaceAll(" ", "+"))}&scheme=JWT`, userAccountConfirmedSchema, {
922
+ method: "POST",
923
+ body: pendingPasswordToken ? { pendingPasswordToken } : void 0
924
+ });
925
+ if (pendingPasswordToken)
926
+ this.pendingPasswords.remove(email);
869
927
  const result = response.data;
870
928
  if (result.status === "SignedIn") {
871
929
  if (!result.accessToken || !result.refreshToken) {
@@ -1027,6 +1085,15 @@ var ProductEndpoints = class {
1027
1085
  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);
1028
1086
  return response.data;
1029
1087
  }
1088
+ /**
1089
+ * The product's published releases, highest version first, with their release notes and files,
1090
+ * and whether the caller may download each. Open to anyone who can see the product, signed in or
1091
+ * not. A page can hold fewer releases than its size, so keep following `next` while `hasMore` is set.
1092
+ */
1093
+ async getReleases(productId, nextUrl) {
1094
+ const response = await this.api.fetch(nextUrl || `/api/customer/inventory/products/${productId}/releases${this.configuration.includeManifests ? "?includeManifests=true" : ""}`, paged(productReleaseSchema));
1095
+ return response.data;
1096
+ }
1030
1097
  async getOwned(nextUrl) {
1031
1098
  const response = await this.api.authenticatedFetch(nextUrl || `/api/customer/inventory/products${this.configuration.includeManifests ? "?includeManifests=true" : ""}`, paged(productSummarySchema));
1032
1099
  return response.data;
@@ -1053,7 +1120,7 @@ var ProductEndpoints = class {
1053
1120
  import { z as z14 } from "zod";
1054
1121
 
1055
1122
  // package.json
1056
- var version = "3.7.0";
1123
+ var version = "3.9.0";
1057
1124
 
1058
1125
  // src/utils/client.ts
1059
1126
  function buildClientHeader(sdk) {
@@ -1061,6 +1128,11 @@ function buildClientHeader(sdk) {
1061
1128
  }
1062
1129
 
1063
1130
  // src/utils/api.ts
1131
+ function loggableBody(body) {
1132
+ if (!body || typeof body !== "object" || Array.isArray(body))
1133
+ return body;
1134
+ return Object.fromEntries(Object.entries(body).map(([key, value]) => [key, /password|token/i.test(key) && value != null ? "[redacted]" : value]));
1135
+ }
1064
1136
  function objectToQuery(obj) {
1065
1137
  return Object.entries(obj != null ? obj : {}).filter(([_, value]) => value !== void 0).map(([key, value]) => `${key}=${encodeURIComponent(value instanceof Date ? value.toISOString() : value)}`).join("&");
1066
1138
  }
@@ -1082,7 +1154,7 @@ var MoonbaseApi = class {
1082
1154
  const contentType = (_a = options == null ? void 0 : options.contentType) != null ? _a : "application/json";
1083
1155
  this.logger.debug("Making request to Moonbase API...", {
1084
1156
  path,
1085
- body: options == null ? void 0 : options.body
1157
+ body: loggableBody(options == null ? void 0 : options.body)
1086
1158
  });
1087
1159
  const startedAt = /* @__PURE__ */ new Date();
1088
1160
  const request = {
@@ -1731,6 +1803,50 @@ var InMemoryStore = class {
1731
1803
  }
1732
1804
  };
1733
1805
 
1806
+ // src/utils/pendingPasswords.ts
1807
+ var lifetime = 24 * 60 * 60 * 1e3;
1808
+ function normalizeEmail(email) {
1809
+ return email.trim().toLowerCase();
1810
+ }
1811
+ var _PendingPasswordStore = class _PendingPasswordStore {
1812
+ constructor(store) {
1813
+ this.store = store != null ? store : isLocalStorageAvailable() ? new LocalStorageStore() : new InMemoryStore();
1814
+ }
1815
+ get(email) {
1816
+ var _a, _b;
1817
+ return (_b = (_a = this.read()[normalizeEmail(email)]) == null ? void 0 : _a.token) != null ? _b : null;
1818
+ }
1819
+ set(email, token) {
1820
+ this.write({ ...this.read(), [normalizeEmail(email)]: { token, storedAt: Date.now() } });
1821
+ }
1822
+ remove(email) {
1823
+ const entries = this.read();
1824
+ delete entries[normalizeEmail(email)];
1825
+ this.write(entries);
1826
+ }
1827
+ /** The entries still worth keeping. Drops the rest from storage as it goes. */
1828
+ read() {
1829
+ const stored = this.store.get(_PendingPasswordStore.storageKey);
1830
+ if (!stored || typeof stored !== "object")
1831
+ return {};
1832
+ const now = Date.now();
1833
+ const fresh = Object.fromEntries(Object.entries(stored).filter(
1834
+ ([, entry]) => typeof (entry == null ? void 0 : entry.token) === "string" && typeof entry.storedAt === "number" && now - entry.storedAt < lifetime
1835
+ ));
1836
+ if (Object.keys(fresh).length !== Object.keys(stored).length)
1837
+ this.write(fresh);
1838
+ return fresh;
1839
+ }
1840
+ write(entries) {
1841
+ if (Object.keys(entries).length > 0)
1842
+ this.store.set(_PendingPasswordStore.storageKey, entries);
1843
+ else
1844
+ this.store.remove(_PendingPasswordStore.storageKey);
1845
+ }
1846
+ };
1847
+ _PendingPasswordStore.storageKey = "moonbase_pending_passwords";
1848
+ var PendingPasswordStore = _PendingPasswordStore;
1849
+
1734
1850
  // src/utils/tokenStore.ts
1735
1851
  var defaultAccessTokenLifetime = 15 * 60 * 1e3;
1736
1852
  var renewBeforeExpiry = 5 * 60 * 1e3;
@@ -1740,9 +1856,9 @@ function accessTokenLifetime(accessToken) {
1740
1856
  try {
1741
1857
  const payload = accessToken.split(".")[1].replaceAll("-", "+").replaceAll("_", "/");
1742
1858
  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;
1859
+ const lifetime2 = (Number(claims.exp) - Number(claims.iat)) * 1e3;
1860
+ if (Number.isFinite(lifetime2) && lifetime2 > 0 && lifetime2 <= 24 * 60 * 60 * 1e3)
1861
+ return lifetime2;
1746
1862
  } catch (e) {
1747
1863
  }
1748
1864
  return defaultAccessTokenLifetime;
@@ -2667,7 +2783,8 @@ function parseOffSiteReferrer(rawReferrer, currentOrigin) {
2667
2783
  }
2668
2784
  function resolveUtm(cached, fresh) {
2669
2785
  const freshHasUtm = !!(fresh.source || fresh.medium || fresh.campaign || fresh.term || fresh.content);
2670
- const freshIsExternalArrival = freshHasUtm || !!fresh.referrer;
2786
+ const freshHasClickId = !!(fresh.gclid || fresh.gbraid || fresh.wbraid || fresh.fbclid || fresh.msclkid || fresh.ttclid);
2787
+ const freshIsExternalArrival = freshHasUtm || freshHasClickId || !!fresh.referrer;
2671
2788
  if (freshIsExternalArrival)
2672
2789
  return fresh;
2673
2790
  return cached != null ? cached : fresh;
@@ -2683,7 +2800,7 @@ var MoonbaseClient = class {
2683
2800
  this.tokenStore = new TokenStore(configuration);
2684
2801
  this.api = new MoonbaseApi(this.configuration.endpoint, this.tokenStore, this.logger, this.configuration.sdk);
2685
2802
  this.storefront = new StorefrontEndpoints(this.api, this.configuration);
2686
- this.identity = new IdentityEndpoints(this.api, this.tokenStore, this.logger);
2803
+ this.identity = new IdentityEndpoints(this.api, this.tokenStore, this.logger, new PendingPasswordStore(configuration.store));
2687
2804
  this.communications = new CommunicationsEndpoints(this.api);
2688
2805
  this.groups = new GroupEndpoints(this.api);
2689
2806
  this.vouchers = new VoucherEndpoints(this.api);
@@ -2720,6 +2837,7 @@ export {
2720
2837
  NotFoundError,
2721
2838
  OfferUtils,
2722
2839
  OrderStatus,
2840
+ PasswordSetupRequiredError,
2723
2841
  Platform,
2724
2842
  StoreClosedError,
2725
2843
  SubscriptionCancellationReason,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@moonbase.sh/storefront-api",
3
3
  "type": "module",
4
- "version": "3.7.0",
4
+ "version": "3.9.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",