@scayle/storefront-core 7.54.0 → 7.56.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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @scayle/storefront-core
2
2
 
3
+ ## 7.56.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add refresh token logic to customer API client
8
+
9
+ ### Patch Changes
10
+
11
+ - Ensure `user.storefrontAccessToken` is up to date when calling the `getUser` RPC
12
+
13
+ ## 7.55.0
14
+
15
+ ### Minor Changes
16
+
17
+ - Allow disabling the `HashAlgorithm` by setting its value to `none` within a shop config.
18
+ - Added exports for `ProductSortConfig` type and enums `APISortOption` and `APISortOrder`.
19
+
3
20
  ## 7.54.0
4
21
 
5
22
  ### Minor Changes
@@ -0,0 +1,7 @@
1
+ # Storefront Core API Clients
2
+
3
+ ## Overview
4
+
5
+ This directory is the home of "mini" API clients. While some SCAYLE APIs provide a TypeScript SDK as an npm package (e.g. SAPI) others do not. When the API is simple enough, we may create a "mini" SDK within `storefront-core`. This way we can provide a TypeScript native abstraction over the API but not suffer the overhead of an additional package to maintain.
6
+
7
+ The SDKs created here should generally be a single file. The goal should be to make them as self-contained as possible, but it is ok if they need to pull a type or utility function from elsewhere in `storefront-core`.
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.CustomerAPIClient = void 0;
7
7
  var _fetch = require("../utils/fetch.cjs");
8
8
  var _httpStatus = require("../constants/httpStatus.cjs");
9
+ var _oauth = require("./oauth.cjs");
9
10
  class CustomerAPIClient {
10
11
  baseURL;
11
12
  context;
@@ -24,26 +25,39 @@ class CustomerAPIClient {
24
25
  } : {})
25
26
  };
26
27
  }
27
- async handleResponse(response) {
28
- if (!response.ok) {
29
- if (response.status === _httpStatus.HttpStatusCode.UNAUTHORIZED && this.context.accessToken) {
30
- this.context.log.debug("Invalid Checkout Token. Deleting session");
31
- await this.context.destroySession();
28
+ async sendRequest(request, retry = true) {
29
+ const response = await request();
30
+ if (response.ok) {
31
+ return await response.json();
32
+ }
33
+ if ((response.status === _httpStatus.HttpStatusCode.UNAUTHORIZED && response.headers.get("WWW-Authenticate")?.includes("invalid_token") || response.status === _httpStatus.HttpStatusCode.FORBIDDEN) && this.context.accessToken && this.context.refreshToken) {
34
+ if (retry) {
35
+ const client = (0, _oauth.getOAuthClient)(this.context);
36
+ const tokens = await client.refreshToken({
37
+ grant_type: "refresh_token",
38
+ refresh_token: this.context.refreshToken
39
+ });
40
+ this.context.updateTokens({
41
+ accessToken: tokens?.access_token,
42
+ refreshToken: tokens?.refresh_token
43
+ });
44
+ return await this.sendRequest(request, false);
32
45
  }
33
- throw new _fetch.FetchError(response);
46
+ this.context.log.debug("Invalid Checkout Token. Deleting session");
47
+ await this.context.destroySession();
34
48
  }
35
- return await response.json();
49
+ throw new _fetch.FetchError(response);
36
50
  }
37
51
  /**
38
52
  * Get the addresses for the current customer
39
53
  */
40
54
  async getAddresses(shopId) {
41
- return await fetch(`${this.baseURL}/api/oauth/customer/addresses`, {
55
+ return await this.sendRequest(() => fetch(`${this.baseURL}/api/oauth/customer/addresses`, {
42
56
  headers: {
43
57
  ...this.headers,
44
58
  "X-Shop-Id": shopId.toString()
45
59
  }
46
- }).then(response => this.handleResponse(response));
60
+ }));
47
61
  }
48
62
  /**
49
63
  * Returns customer data and latest orders.
@@ -51,7 +65,7 @@ class CustomerAPIClient {
51
65
  * @see https://scayle.dev/api/oauth/latest/fetch-authorized-payload
52
66
  */
53
67
  async getMe(shopId, accessToken) {
54
- const response = await fetch(`${this.baseURL}/api/oauth/me`, {
68
+ return await this.sendRequest(() => fetch(`${this.baseURL}/api/oauth/me`, {
55
69
  headers: {
56
70
  ...this.headers,
57
71
  ...(accessToken ? {
@@ -59,20 +73,19 @@ class CustomerAPIClient {
59
73
  } : {}),
60
74
  "X-Shop-Id": shopId.toString()
61
75
  }
62
- });
63
- return this.handleResponse(response);
76
+ }));
64
77
  }
65
78
  /**
66
79
  * Fetch a customer's order
67
80
  * @see https://scayle.dev/api/oauth/latest/fetch-order-by-id
68
81
  */
69
82
  async getOrder(shopId, orderId) {
70
- return await fetch(`${this.baseURL}/api/oauth/customer/order/${orderId}`, {
83
+ return this.sendRequest(() => fetch(`${this.baseURL}/api/oauth/customer/order/${orderId}`, {
71
84
  headers: {
72
85
  ...this.headers,
73
86
  "X-Shop-Id": shopId.toString()
74
87
  }
75
- }).then(response => this.handleResponse(response));
88
+ }));
76
89
  }
77
90
  /**
78
91
  * Update the customer's contact details
@@ -83,7 +96,7 @@ class CustomerAPIClient {
83
96
  email,
84
97
  phone
85
98
  }) {
86
- return await fetch(`${this.baseURL}/api/oauth/customer/contact`, {
99
+ return await this.sendRequest(() => fetch(`${this.baseURL}/api/oauth/customer/contact`, {
87
100
  method: "PUT",
88
101
  headers: {
89
102
  ...this.headers,
@@ -93,20 +106,20 @@ class CustomerAPIClient {
93
106
  email,
94
107
  phone
95
108
  })
96
- }).then(response => this.handleResponse(response));
109
+ }));
97
110
  }
98
111
  /**
99
112
  * Update the customer's personal details
100
113
  */
101
114
  async updatePersonalInfo(shopId, payload) {
102
- return await fetch(`${this.baseURL}/api/oauth/customer/personal`, {
115
+ return await this.sendRequest(() => fetch(`${this.baseURL}/api/oauth/customer/personal`, {
103
116
  method: "PUT",
104
117
  headers: {
105
118
  ...this.headers,
106
119
  "X-Shop-Id": shopId.toString()
107
120
  },
108
121
  body: JSON.stringify(payload)
109
- }).then(response => this.handleResponse(response));
122
+ }));
110
123
  }
111
124
  /**
112
125
  * Update the customer's password
@@ -117,7 +130,7 @@ class CustomerAPIClient {
117
130
  password,
118
131
  newPassword
119
132
  }) {
120
- return await fetch(`${this.baseURL}/api/oauth/customer/password`, {
133
+ return await this.sendRequest(() => fetch(`${this.baseURL}/api/oauth/customer/password`, {
121
134
  method: "PUT",
122
135
  headers: {
123
136
  ...this.headers,
@@ -127,7 +140,7 @@ class CustomerAPIClient {
127
140
  password,
128
141
  newPassword
129
142
  })
130
- }).then(response => this.handleResponse(response));
143
+ }));
131
144
  }
132
145
  }
133
146
  exports.CustomerAPIClient = CustomerAPIClient;
@@ -39,7 +39,7 @@ export declare class CustomerAPIClient {
39
39
  context: RpcContext;
40
40
  constructor(context: RpcContext);
41
41
  get headers(): HeadersInit;
42
- handleResponse<BodyType>(response: Response): Promise<BodyType>;
42
+ sendRequest<BodyType>(request: () => Promise<Response>, retry?: boolean): Promise<BodyType>;
43
43
  /**
44
44
  * Get the addresses for the current customer
45
45
  */
@@ -1,5 +1,6 @@
1
1
  import { FetchError } from "../utils/fetch.mjs";
2
2
  import { HttpStatusCode } from "../constants/httpStatus.mjs";
3
+ import { getOAuthClient } from "./oauth.mjs";
3
4
  export class CustomerAPIClient {
4
5
  baseURL;
5
6
  context;
@@ -16,27 +17,42 @@ export class CustomerAPIClient {
16
17
  ...accessHeader ? { "X-Access-Header": accessHeader } : {}
17
18
  };
18
19
  }
19
- async handleResponse(response) {
20
- if (!response.ok) {
21
- if (response.status === HttpStatusCode.UNAUTHORIZED && this.context.accessToken) {
22
- this.context.log.debug("Invalid Checkout Token. Deleting session");
23
- await this.context.destroySession();
20
+ async sendRequest(request, retry = true) {
21
+ const response = await request();
22
+ if (response.ok) {
23
+ return await response.json();
24
+ }
25
+ if ((response.status === HttpStatusCode.UNAUTHORIZED && response.headers.get("WWW-Authenticate")?.includes(
26
+ "invalid_token"
27
+ ) || response.status === HttpStatusCode.FORBIDDEN) && this.context.accessToken && this.context.refreshToken) {
28
+ if (retry) {
29
+ const client = getOAuthClient(this.context);
30
+ const tokens = await client.refreshToken({
31
+ grant_type: "refresh_token",
32
+ refresh_token: this.context.refreshToken
33
+ });
34
+ this.context.updateTokens({
35
+ accessToken: tokens?.access_token,
36
+ refreshToken: tokens?.refresh_token
37
+ });
38
+ return await this.sendRequest(request, false);
24
39
  }
25
- throw new FetchError(response);
40
+ this.context.log.debug("Invalid Checkout Token. Deleting session");
41
+ await this.context.destroySession();
26
42
  }
27
- return await response.json();
43
+ throw new FetchError(response);
28
44
  }
29
45
  /**
30
46
  * Get the addresses for the current customer
31
47
  */
32
48
  async getAddresses(shopId) {
33
- return await fetch(`${this.baseURL}/api/oauth/customer/addresses`, {
34
- headers: {
35
- ...this.headers,
36
- "X-Shop-Id": shopId.toString()
37
- }
38
- }).then(
39
- (response) => this.handleResponse(response)
49
+ return await this.sendRequest(
50
+ () => fetch(`${this.baseURL}/api/oauth/customer/addresses`, {
51
+ headers: {
52
+ ...this.headers,
53
+ "X-Shop-Id": shopId.toString()
54
+ }
55
+ })
40
56
  );
41
57
  }
42
58
  /**
@@ -45,26 +61,29 @@ export class CustomerAPIClient {
45
61
  * @see https://scayle.dev/api/oauth/latest/fetch-authorized-payload
46
62
  */
47
63
  async getMe(shopId, accessToken) {
48
- const response = await fetch(`${this.baseURL}/api/oauth/me`, {
49
- headers: {
50
- ...this.headers,
51
- ...accessToken ? { Authorization: `Bearer ${accessToken}` } : {},
52
- "X-Shop-Id": shopId.toString()
53
- }
54
- });
55
- return this.handleResponse(response);
64
+ return await this.sendRequest(
65
+ () => fetch(`${this.baseURL}/api/oauth/me`, {
66
+ headers: {
67
+ ...this.headers,
68
+ ...accessToken ? { Authorization: `Bearer ${accessToken}` } : {},
69
+ "X-Shop-Id": shopId.toString()
70
+ }
71
+ })
72
+ );
56
73
  }
57
74
  /**
58
75
  * Fetch a customer's order
59
76
  * @see https://scayle.dev/api/oauth/latest/fetch-order-by-id
60
77
  */
61
78
  async getOrder(shopId, orderId) {
62
- return await fetch(`${this.baseURL}/api/oauth/customer/order/${orderId}`, {
63
- headers: {
64
- ...this.headers,
65
- "X-Shop-Id": shopId.toString()
66
- }
67
- }).then((response) => this.handleResponse(response));
79
+ return this.sendRequest(
80
+ () => fetch(`${this.baseURL}/api/oauth/customer/order/${orderId}`, {
81
+ headers: {
82
+ ...this.headers,
83
+ "X-Shop-Id": shopId.toString()
84
+ }
85
+ })
86
+ );
68
87
  }
69
88
  /**
70
89
  * Update the customer's contact details
@@ -72,30 +91,34 @@ export class CustomerAPIClient {
72
91
  * @see https://scayle.dev/api/oauth/latest/put-customer-contact
73
92
  */
74
93
  async updateContactInfo(shopId, { email, phone }) {
75
- return await fetch(`${this.baseURL}/api/oauth/customer/contact`, {
76
- method: "PUT",
77
- headers: {
78
- ...this.headers,
79
- "X-Shop-Id": shopId.toString()
80
- },
81
- body: JSON.stringify({
82
- email,
83
- phone
94
+ return await this.sendRequest(
95
+ () => fetch(`${this.baseURL}/api/oauth/customer/contact`, {
96
+ method: "PUT",
97
+ headers: {
98
+ ...this.headers,
99
+ "X-Shop-Id": shopId.toString()
100
+ },
101
+ body: JSON.stringify({
102
+ email,
103
+ phone
104
+ })
84
105
  })
85
- }).then((response) => this.handleResponse(response));
106
+ );
86
107
  }
87
108
  /**
88
109
  * Update the customer's personal details
89
110
  */
90
111
  async updatePersonalInfo(shopId, payload) {
91
- return await fetch(`${this.baseURL}/api/oauth/customer/personal`, {
92
- method: "PUT",
93
- headers: {
94
- ...this.headers,
95
- "X-Shop-Id": shopId.toString()
96
- },
97
- body: JSON.stringify(payload)
98
- }).then((response) => this.handleResponse(response));
112
+ return await this.sendRequest(
113
+ () => fetch(`${this.baseURL}/api/oauth/customer/personal`, {
114
+ method: "PUT",
115
+ headers: {
116
+ ...this.headers,
117
+ "X-Shop-Id": shopId.toString()
118
+ },
119
+ body: JSON.stringify(payload)
120
+ })
121
+ );
99
122
  }
100
123
  /**
101
124
  * Update the customer's password
@@ -103,13 +126,15 @@ export class CustomerAPIClient {
103
126
  * @see https://scayle.dev/api/oauth/latest/put-customer-password
104
127
  */
105
128
  async updatePassword(shopId, { password, newPassword }) {
106
- return await fetch(`${this.baseURL}/api/oauth/customer/password`, {
107
- method: "PUT",
108
- headers: {
109
- ...this.headers,
110
- "X-Shop-Id": shopId.toString()
111
- },
112
- body: JSON.stringify({ password, newPassword })
113
- }).then((response) => this.handleResponse(response));
129
+ return await this.sendRequest(
130
+ () => fetch(`${this.baseURL}/api/oauth/customer/password`, {
131
+ method: "PUT",
132
+ headers: {
133
+ ...this.headers,
134
+ "X-Shop-Id": shopId.toString()
135
+ },
136
+ body: JSON.stringify({ password, newPassword })
137
+ })
138
+ );
114
139
  }
115
140
  }
@@ -6,5 +6,6 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.HashAlgorithm = void 0;
7
7
  const HashAlgorithm = exports.HashAlgorithm = {
8
8
  MD5: "md5",
9
- SHA256: "sha256"
9
+ SHA256: "sha256",
10
+ NONE: "none"
10
11
  };
@@ -2,5 +2,6 @@ import type { ValuesType } from 'utility-types';
2
2
  export declare const HashAlgorithm: {
3
3
  readonly MD5: "md5";
4
4
  readonly SHA256: "sha256";
5
+ readonly NONE: "none";
5
6
  };
6
7
  export type HashAlgorithm = ValuesType<typeof HashAlgorithm> | null;
@@ -1,4 +1,5 @@
1
1
  export const HashAlgorithm = {
2
2
  MD5: "md5",
3
- SHA256: "sha256"
3
+ SHA256: "sha256",
4
+ NONE: "none"
4
5
  };
package/dist/index.cjs CHANGED
@@ -4,8 +4,22 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  var _exportNames = {
7
- rpcMethods: true
7
+ rpcMethods: true,
8
+ APISortOption: true,
9
+ APISortOrder: true
8
10
  };
11
+ Object.defineProperty(exports, "APISortOption", {
12
+ enumerable: true,
13
+ get: function () {
14
+ return _storefrontApi.APISortOption;
15
+ }
16
+ });
17
+ Object.defineProperty(exports, "APISortOrder", {
18
+ enumerable: true,
19
+ get: function () {
20
+ return _storefrontApi.APISortOrder;
21
+ }
22
+ });
9
23
  exports.rpcMethods = void 0;
10
24
  var rpcMethods = _interopRequireWildcard(require("./rpc/methods/index.cjs"));
11
25
  exports.rpcMethods = rpcMethods;
@@ -81,5 +95,6 @@ Object.keys(_types).forEach(function (key) {
81
95
  }
82
96
  });
83
97
  });
98
+ var _storefrontApi = require("@scayle/storefront-api");
84
99
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
85
100
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
package/dist/index.d.ts CHANGED
@@ -17,3 +17,4 @@ export type RpcMethodName = Resolve<keyof RpcMethods>;
17
17
  export type RpcMethod<N extends RpcMethodName> = RpcMethods[N];
18
18
  export type RpcMethodParameters<N extends RpcMethodName> = Parameters<RpcMethod<N>>[0];
19
19
  export type RpcMethodReturnType<N extends RpcMethodName> = ReturnType<RpcMethod<N>>;
20
+ export { APISortOption, APISortOrder, type ProductSortConfig, } from '@scayle/storefront-api';
package/dist/index.mjs CHANGED
@@ -6,3 +6,7 @@ export * from "./constants/index.mjs";
6
6
  export * from "./cache/index.mjs";
7
7
  export * from "./helpers/index.mjs";
8
8
  export * from "./types/index.mjs";
9
+ export {
10
+ APISortOption,
11
+ APISortOrder
12
+ } from "@scayle/storefront-api";
@@ -24,7 +24,7 @@ const getCheckoutToken = exports.getCheckoutToken = async function getCheckoutTo
24
24
  carrier,
25
25
  basketId: context.basketKey,
26
26
  campaignKey: context.campaignKey
27
- }).setIssuedAt(now).setNotBefore(now).setExpirationTime("1h").setIssuer(`${"@scayle/storefront-core"}@${"7.53.0"}`).setProtectedHeader({
27
+ }).setIssuedAt(now).setNotBefore(now).setExpirationTime("1h").setIssuer(`${"@scayle/storefront-core"}@${"7.55.0"}`).setProtectedHeader({
28
28
  alg: "HS256",
29
29
  typ: "JWT"
30
30
  }).sign(secret);
@@ -13,7 +13,7 @@ export const getCheckoutToken = async function getCheckoutToken2(jwtPayload, con
13
13
  carrier,
14
14
  basketId: context.basketKey,
15
15
  campaignKey: context.campaignKey
16
- }).setIssuedAt(now).setNotBefore(now).setExpirationTime("1h").setIssuer(`${"@scayle/storefront-core"}@${"7.53.0"}`).setProtectedHeader({ alg: "HS256", typ: "JWT" }).sign(secret);
16
+ }).setIssuedAt(now).setNotBefore(now).setExpirationTime("1h").setIssuer(`${"@scayle/storefront-core"}@${"7.55.0"}`).setProtectedHeader({ alg: "HS256", typ: "JWT" }).sign(secret);
17
17
  return {
18
18
  accessToken: context.accessToken,
19
19
  checkoutJwt
@@ -7,8 +7,20 @@ exports.refreshUser = exports.getUser = exports.fetchUser = void 0;
7
7
  var _types = require("../../types/index.cjs");
8
8
  var _customer = require("../../api/customer.cjs");
9
9
  const getUser = exports.getUser = function getUser2(context) {
10
+ const {
11
+ user,
12
+ accessToken,
13
+ shopId
14
+ } = context;
15
+ if (user) {
16
+ user.authentication = user.authentication ?? {
17
+ type: "idp"
18
+ };
19
+ user.authentication.storefrontAccessToken = accessToken;
20
+ user.loginShopId = shopId;
21
+ }
10
22
  return {
11
- user: context.user
23
+ user
12
24
  };
13
25
  };
14
26
  const fetchUser = exports.fetchUser = async function fetchUser2(payload, context) {
@@ -1,8 +1,16 @@
1
1
  import { assertSession } from "../../types/index.mjs";
2
2
  import { CustomerAPIClient } from "../../api/customer.mjs";
3
3
  const getUser = function getUser2(context) {
4
+ const { user, accessToken, shopId } = context;
5
+ if (user) {
6
+ user.authentication = user.authentication ?? {
7
+ type: "idp"
8
+ };
9
+ user.authentication.storefrontAccessToken = accessToken;
10
+ user.loginShopId = shopId;
11
+ }
4
12
  return {
5
- user: context.user
13
+ user
6
14
  };
7
15
  };
8
16
  const fetchUser = async function fetchUser2(payload, context) {
@@ -1,5 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
1
+ "use strict";
@@ -1,6 +1,5 @@
1
1
  import type { APISortOption, APISortOrder } from '@scayle/storefront-api';
2
2
  import type { SortName, SortQuery } from '../../constants';
3
- export { APISortOption, APISortOrder };
4
3
  export type SortingValueKey = 'topSeller' | 'dateNewest' | 'priceDesc' | 'priceAsc' | 'reductionDesc' | 'reductionAsc';
5
4
  export interface SortValue {
6
5
  name: SortName;
@@ -1 +0,0 @@
1
- export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scayle/storefront-core",
3
- "version": "7.54.0",
3
+ "version": "7.56.0",
4
4
  "description": "Collection of essential utilities to work with the Storefront API",
5
5
  "author": "SCAYLE Commerce Engine",
6
6
  "license": "MIT",
@@ -71,10 +71,10 @@
71
71
  "devDependencies": {
72
72
  "@scayle/eslint-config-storefront": "4.2.0",
73
73
  "@types/crypto-js": "4.2.2",
74
- "@types/node": "20.14.1",
74
+ "@types/node": "20.14.2",
75
75
  "@types/webpack-env": "1.18.5",
76
76
  "@vitest/coverage-v8": "1.6.0",
77
- "dprint": "0.46.1",
77
+ "dprint": "0.46.2",
78
78
  "eslint": "9.4.0",
79
79
  "eslint-formatter-gitlab": "5.1.0",
80
80
  "publint": "0.2.8",