@scayle/storefront-core 7.37.0 → 7.38.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,17 @@
1
1
  # @scayle/storefront-core
2
2
 
3
+ ## 7.38.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Allow the session of an `RpcContext` to be undefined
8
+
9
+ BREAKING: This changes the structure of the `RpcContext`, so it may be a breaking change if you have written custom RPC methods.
10
+
11
+ The affected properties on the `RpcContext` are `sessionId`, `wishlistKey` and `basketKey` and the affected methods are `destroySession`, `createUserBoundSession`, `updateUser`, and `updateTokens`. If you use these methods or properties in a custom RPC method, make sure that you handle the case where they might be undefined. TypeScript will also catch these cases if you have `strictNullChecks` enabled.
12
+
13
+ You can check `context.sessionId` (or another session-dependent property) to determine if the session is present. If one of these properties is present, all will be. Alternatively, you can call `assertSession(context)` before referencing any properties on the context. If the session is not present, an error will be thrown. For any usage of `context` after `assertSession` is called, TypeScript will understand that the session properties are present.
14
+
3
15
  ## 7.37.0
4
16
 
5
17
  ### Minor Changes
@@ -60,11 +60,11 @@ export declare class CustomerAPIClient {
60
60
  *
61
61
  * @see https://scayle.dev/api/oauth/latest/put-customer-contact
62
62
  */
63
- updateContactInfo(shopId: number, { email, phone }: ContactData): Promise<ShopUser>;
63
+ updateContactInfo(shopId: number, { email, phone }: Partial<ContactData>): Promise<ShopUser>;
64
64
  /**
65
65
  * Update the customer's personal details
66
66
  */
67
- updatePersonalInfo(shopId: number, payload: PersonalData): Promise<ShopUser>;
67
+ updatePersonalInfo(shopId: number, payload: Partial<PersonalData>): Promise<ShopUser>;
68
68
  /**
69
69
  * Update the customer's password
70
70
  *
@@ -27,9 +27,12 @@ function emptyOAuthResponseHandler(response) {
27
27
  }
28
28
  }
29
29
  function getOAuthClient(context) {
30
- const clientId = context.oauth?.clientId;
31
- const clientSecret = context.oauth?.clientSecret;
32
- const apiHost = context.oauth?.apiHost;
30
+ if (!context.oauth) {
31
+ throw new Error("OAuth configuration is missing");
32
+ }
33
+ const clientId = context.oauth.clientId;
34
+ const clientSecret = context.oauth.clientSecret;
35
+ const apiHost = context.oauth.apiHost;
33
36
  return new OAuthClient({
34
37
  clientId,
35
38
  clientSecret,
@@ -15,7 +15,7 @@ export declare class OAuthClient {
15
15
  headers: HeadersInit;
16
16
  baseURL: string;
17
17
  logger?: Log;
18
- clientId?: string;
18
+ clientId: string;
19
19
  constructor(options: OAuthOptions, logger?: Log);
20
20
  /**
21
21
  * Register a user and retrieve a token set
@@ -20,9 +20,12 @@ function emptyOAuthResponseHandler(response) {
20
20
  }
21
21
  }
22
22
  export function getOAuthClient(context) {
23
- const clientId = context.oauth?.clientId;
24
- const clientSecret = context.oauth?.clientSecret;
25
- const apiHost = context.oauth?.apiHost;
23
+ if (!context.oauth) {
24
+ throw new Error("OAuth configuration is missing");
25
+ }
26
+ const clientId = context.oauth.clientId;
27
+ const clientSecret = context.oauth.clientSecret;
28
+ const apiHost = context.oauth.apiHost;
26
29
  return new OAuthClient({ clientId, clientSecret, apiHost }, context.log);
27
30
  }
28
31
  export class OAuthClient {
@@ -40,7 +40,9 @@ class Cached {
40
40
  return cachedResponse;
41
41
  }
42
42
  } catch (e) {
43
- this.handleError(e);
43
+ if (e instanceof Error) {
44
+ this.handleError(e);
45
+ }
44
46
  }
45
47
  const response = await fn(...args);
46
48
  if (response === void 0 || response === null) {
@@ -49,7 +51,9 @@ class Cached {
49
51
  try {
50
52
  await this.setCacheValue(cacheKey, response, options);
51
53
  } catch (e) {
52
- this.handleError(e);
54
+ if (e instanceof Error) {
55
+ this.handleError(e);
56
+ }
53
57
  }
54
58
  return response;
55
59
  };
@@ -34,7 +34,9 @@ export class Cached {
34
34
  return cachedResponse;
35
35
  }
36
36
  } catch (e) {
37
- this.handleError(e);
37
+ if (e instanceof Error) {
38
+ this.handleError(e);
39
+ }
38
40
  }
39
41
  const response = await fn(...args);
40
42
  if (response === void 0 || response === null) {
@@ -43,7 +45,9 @@ export class Cached {
43
45
  try {
44
46
  await this.setCacheValue(cacheKey, response, options);
45
47
  } catch (e) {
46
- this.handleError(e);
48
+ if (e instanceof Error) {
49
+ this.handleError(e);
50
+ }
47
51
  }
48
52
  return response;
49
53
  };
@@ -73,7 +73,7 @@ export type Filter = Record<string, string | number | (string | number | null)[]
73
73
  *
74
74
  * @param filter
75
75
  */
76
- export declare const serializeFilters: (filter: Filter) => SerializedFilter;
76
+ export declare const serializeFilters: (filter: Filter) => SerializedFilter | undefined;
77
77
  /**
78
78
  * Converts filter values back into their original data type.
79
79
  *
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.removeItemFromBasket = exports.mergeBaskets = exports.getBasket = exports.clearBasket = exports.addItemsToBasket = exports.addItemToBasket = void 0;
7
7
  var _errors = require("../../../errors/index.cjs");
8
+ var _types = require("../../../types/index.cjs");
8
9
  var _constants = require("../../../constants/index.cjs");
9
10
  var _user = require("../../../utils/user.cjs");
10
11
  const BAPI_ERROR_NAME = "BAPI ERROR";
@@ -21,6 +22,7 @@ const addItemToBasket = exports.addItemToBasket = async function addItemToBasket
21
22
  itemGroup,
22
23
  with: _with
23
24
  }, context) {
25
+ (0, _types.assertSession)(context);
24
26
  const {
25
27
  campaignKey,
26
28
  bapiClient,
@@ -33,7 +35,7 @@ const addItemToBasket = exports.addItemToBasket = async function addItemToBasket
33
35
  variantId,
34
36
  quantity,
35
37
  params: {
36
- promotionId,
38
+ promotionId: promotionId ?? void 0,
37
39
  campaignKey,
38
40
  displayData,
39
41
  pricePromotionKey: resolvedWith?.pricePromotionKey || "",
@@ -62,6 +64,7 @@ const addItemToBasket = exports.addItemToBasket = async function addItemToBasket
62
64
  }
63
65
  };
64
66
  const addItemsToBasket = exports.addItemsToBasket = async function addItemsToBasket2(params, context) {
67
+ (0, _types.assertSession)(context);
65
68
  const {
66
69
  campaignKey,
67
70
  bapiClient,
@@ -72,7 +75,7 @@ const addItemsToBasket = exports.addItemsToBasket = async function addItemsToBas
72
75
  variantId: item.variantId,
73
76
  quantity: item.quantity,
74
77
  params: {
75
- promotionId: item.promotionId,
78
+ promotionId: item.promotionId ?? void 0,
76
79
  campaignKey,
77
80
  displayData: item.displayData,
78
81
  pricePromotionKey: resolvedWith?.pricePromotionKey || "",
@@ -104,6 +107,7 @@ const addItemsToBasket = exports.addItemsToBasket = async function addItemsToBas
104
107
  }
105
108
  };
106
109
  const getBasket = exports.getBasket = async function getBasket2(options, context) {
110
+ (0, _types.assertSession)(context);
107
111
  const {
108
112
  bapiClient,
109
113
  campaignKey,
@@ -127,6 +131,7 @@ const getBasket = exports.getBasket = async function getBasket2(options, context
127
131
  return response.basket;
128
132
  };
129
133
  const removeItemFromBasket = exports.removeItemFromBasket = async function removeItemFromBasket2(options, context) {
134
+ (0, _types.assertSession)(context);
130
135
  const {
131
136
  bapiClient,
132
137
  campaignKey,
@@ -160,18 +165,16 @@ const mergeBaskets = exports.mergeBaskets = async function mergeBaskets2({
160
165
  return await (0, _user.mergeBaskets)(fromBasketKey, toBasketKey, resolvedWith, context);
161
166
  };
162
167
  function parseBasketError(response) {
163
- if (response.type === "failure") {
164
- const parsedError = {
165
- message: "",
166
- statusCode: 500
167
- };
168
- if ("statusCode" in response) {
169
- parsedError.statusCode = response.statusCode;
170
- } else {
171
- const [error] = response.errors;
172
- parsedError.message = response.errors?.map(error2 => `${error2.operation !== "delete" ? error2.kind + ":" : ""}${error2.operation}:${error2.variantId} - ${error2.message}`).join(" | ");
173
- parsedError.statusCode = error.statusCode;
174
- }
175
- return parsedError;
168
+ const parsedError = {
169
+ message: "",
170
+ statusCode: 500
171
+ };
172
+ if ("statusCode" in response) {
173
+ parsedError.statusCode = response.statusCode;
174
+ } else {
175
+ const [error] = response.errors;
176
+ parsedError.message = response.errors?.map(error2 => `${error2.operation !== "delete" ? error2.kind + ":" : ""}${error2.operation}:${error2.variantId} - ${error2.message}`).join(" | ");
177
+ parsedError.statusCode = error.statusCode;
176
178
  }
179
+ return parsedError;
177
180
  }
@@ -1,4 +1,7 @@
1
1
  import { BAPIError } from "../../../errors/index.mjs";
2
+ import {
3
+ assertSession
4
+ } from "../../../types/index.mjs";
2
5
  import {
3
6
  ExistingItemHandling,
4
7
  MIN_WITH_PARAMS_BASKET
@@ -18,6 +21,7 @@ export const addItemToBasket = async function addItemToBasket2({
18
21
  itemGroup,
19
22
  with: _with
20
23
  }, context) {
24
+ assertSession(context);
21
25
  const { campaignKey, bapiClient, basketKey } = context;
22
26
  const resolvedWith = getWithParams(
23
27
  { with: _with },
@@ -30,7 +34,7 @@ export const addItemToBasket = async function addItemToBasket2({
30
34
  variantId,
31
35
  quantity,
32
36
  params: {
33
- promotionId,
37
+ promotionId: promotionId ?? void 0,
34
38
  campaignKey,
35
39
  displayData,
36
40
  pricePromotionKey: resolvedWith?.pricePromotionKey || "",
@@ -62,13 +66,14 @@ export const addItemToBasket = async function addItemToBasket2({
62
66
  }
63
67
  };
64
68
  export const addItemsToBasket = async function addItemsToBasket2(params, context) {
69
+ assertSession(context);
65
70
  const { campaignKey, bapiClient, basketKey } = context;
66
71
  const resolvedWith = getWithParams(params, context);
67
72
  const itemsToBeAddedOrUpdated = params.items.map((item) => ({
68
73
  variantId: item.variantId,
69
74
  quantity: item.quantity,
70
75
  params: {
71
- promotionId: item.promotionId,
76
+ promotionId: item.promotionId ?? void 0,
72
77
  campaignKey,
73
78
  displayData: item.displayData,
74
79
  pricePromotionKey: resolvedWith?.pricePromotionKey || "",
@@ -109,6 +114,7 @@ export const addItemsToBasket = async function addItemsToBasket2(params, context
109
114
  }
110
115
  };
111
116
  export const getBasket = async function getBasket2(options, context) {
117
+ assertSession(context);
112
118
  const { bapiClient, campaignKey, basketKey } = context;
113
119
  const resolvedWith = getWithParams(
114
120
  { with: options },
@@ -131,6 +137,7 @@ export const getBasket = async function getBasket2(options, context) {
131
137
  return response.basket;
132
138
  };
133
139
  export const removeItemFromBasket = async function removeItemFromBasket2(options, context) {
140
+ assertSession(context);
134
141
  const { bapiClient, campaignKey, basketKey } = context;
135
142
  const resolvedWith = getWithParams(options, context);
136
143
  return await bapiClient.basket.deleteItem(basketKey, options.itemKey, {
@@ -162,20 +169,18 @@ export const mergeBaskets = async function mergeBaskets2({ fromBasketKey, toBask
162
169
  );
163
170
  };
164
171
  function parseBasketError(response) {
165
- if (response.type === "failure") {
166
- const parsedError = {
167
- message: "",
168
- statusCode: 500
169
- };
170
- if ("statusCode" in response) {
171
- parsedError.statusCode = response.statusCode;
172
- } else {
173
- const [error] = response.errors;
174
- parsedError.message = response.errors?.map(
175
- (error2) => `${error2.operation !== "delete" ? error2.kind + ":" : ""}${error2.operation}:${error2.variantId} - ${error2.message}`
176
- ).join(" | ");
177
- parsedError.statusCode = error.statusCode;
178
- }
179
- return parsedError;
172
+ const parsedError = {
173
+ message: "",
174
+ statusCode: 500
175
+ };
176
+ if ("statusCode" in response) {
177
+ parsedError.statusCode = response.statusCode;
178
+ } else {
179
+ const [error] = response.errors;
180
+ parsedError.message = response.errors?.map(
181
+ (error2) => `${error2.operation !== "delete" ? error2.kind + ":" : ""}${error2.operation}:${error2.variantId} - ${error2.message}`
182
+ ).join(" | ");
183
+ parsedError.statusCode = error.statusCode;
180
184
  }
185
+ return parsedError;
181
186
  }
@@ -58,10 +58,13 @@ const updatePassword = exports.updatePassword = async function updatePassword2({
58
58
  newPassword
59
59
  }, context) {
60
60
  const shopUser = context.user;
61
- const oauthEnabled = context.oauth.apiHost && context.oauth.clientId && context.oauth.clientSecret;
61
+ const oauthEnabled = context.oauth?.apiHost && context.oauth?.clientId && context.oauth?.clientSecret;
62
62
  try {
63
63
  if (oauthEnabled) {
64
64
  const client2 = (0, _oauth.getOAuthClient)(context);
65
+ if (!context.accessToken) {
66
+ throw new Error("User is not logged in");
67
+ }
65
68
  await client2.updatePassword({
66
69
  password: oldPassword,
67
70
  new_password: newPassword
@@ -94,4 +97,7 @@ const updatePassword = exports.updatePassword = async function updatePassword2({
94
97
  throw new Error("404 - Failed to update user's password - User not found");
95
98
  }
96
99
  }
100
+ return {
101
+ user: shopUser
102
+ };
97
103
  };
@@ -3,5 +3,5 @@ export declare const updateShopUser: RpcHandler<Partial<ShopUser>, {
3
3
  user: ShopUser;
4
4
  }>;
5
5
  export declare const updatePassword: RpcHandler<UpdatePasswordParams, {
6
- user: ShopUser;
6
+ user: ShopUser | undefined;
7
7
  }>;
@@ -46,10 +46,13 @@ export const updateShopUser = async function updateShopUser2(payload, context) {
46
46
  };
47
47
  export const updatePassword = async function updatePassword2({ oldPassword, newPassword }, context) {
48
48
  const shopUser = context.user;
49
- const oauthEnabled = context.oauth.apiHost && context.oauth.clientId && context.oauth.clientSecret;
49
+ const oauthEnabled = context.oauth?.apiHost && context.oauth?.clientId && context.oauth?.clientSecret;
50
50
  try {
51
51
  if (oauthEnabled) {
52
52
  const client2 = getOAuthClient(context);
53
+ if (!context.accessToken) {
54
+ throw new Error("User is not logged in");
55
+ }
53
56
  await client2.updatePassword(
54
57
  {
55
58
  password: oldPassword,
@@ -83,4 +86,5 @@ export const updatePassword = async function updatePassword2({ oldPassword, newP
83
86
  throw new Error("404 - Failed to update user's password - User not found");
84
87
  }
85
88
  }
89
+ return { user: shopUser };
86
90
  };
@@ -5,17 +5,17 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.handleIDPLoginCallback = exports.getExternalIdpRedirect = void 0;
7
7
  var _jose = require("jose");
8
+ var _types = require("../../../types/index.cjs");
8
9
  var _oauth = require("../../../api/oauth.cjs");
9
10
  const getExternalIdpRedirect = exports.getExternalIdpRedirect = async function getExternalIdpRedirect2(context) {
10
11
  const shopId = context.shopId.toString();
11
12
  const OAuthClient = (0, _oauth.getOAuthClient)(context);
12
13
  const checkoutSecret = context.checkout.secret;
13
- const isIDPEnabled = context.idp.enabled;
14
- const IDPKeys = context.idp.idpKeys;
15
- const IDPRedirectURL = context.idp.idpRedirectURL;
16
- if (!isIDPEnabled) {
14
+ if (!context.idp?.enabled) {
17
15
  throw new Error("IDP disabled");
18
16
  }
17
+ const IDPKeys = context.idp.idpKeys;
18
+ const IDPRedirectURL = context.idp.idpRedirectURL;
19
19
  if (!IDPKeys.length) {
20
20
  throw new Error("No IDP keys configured");
21
21
  }
@@ -40,6 +40,7 @@ const getExternalIdpRedirect = exports.getExternalIdpRedirect = async function g
40
40
  return Object.fromEntries(results);
41
41
  };
42
42
  const handleIDPLoginCallback = exports.handleIDPLoginCallback = async function handleIDPLoginCallback2(code, context) {
43
+ (0, _types.assertSession)(context);
43
44
  const OAuthClient = (0, _oauth.getOAuthClient)(context);
44
45
  const {
45
46
  access_token: accessToken,
@@ -1,15 +1,15 @@
1
1
  import { SignJWT } from "jose";
2
+ import { assertSession } from "../../../types/index.mjs";
2
3
  import { getOAuthClient } from "../../../api/oauth.mjs";
3
4
  export const getExternalIdpRedirect = async function getExternalIdpRedirect2(context) {
4
5
  const shopId = context.shopId.toString();
5
6
  const OAuthClient = getOAuthClient(context);
6
7
  const checkoutSecret = context.checkout.secret;
7
- const isIDPEnabled = context.idp.enabled;
8
- const IDPKeys = context.idp.idpKeys;
9
- const IDPRedirectURL = context.idp.idpRedirectURL;
10
- if (!isIDPEnabled) {
8
+ if (!context.idp?.enabled) {
11
9
  throw new Error("IDP disabled");
12
10
  }
11
+ const IDPKeys = context.idp.idpKeys;
12
+ const IDPRedirectURL = context.idp.idpRedirectURL;
13
13
  if (!IDPKeys.length) {
14
14
  throw new Error("No IDP keys configured");
15
15
  }
@@ -36,6 +36,7 @@ export const getExternalIdpRedirect = async function getExternalIdpRedirect2(con
36
36
  return Object.fromEntries(results);
37
37
  };
38
38
  export const handleIDPLoginCallback = async function handleIDPLoginCallback2(code, context) {
39
+ assertSession(context);
39
40
  const OAuthClient = getOAuthClient(context);
40
41
  const { access_token: accessToken, refresh_token: refreshToken } = await OAuthClient.generateToken(code);
41
42
  context.updateTokens({
@@ -115,13 +115,14 @@ const getFilters = exports.getFilters = async function getFilters2({
115
115
  includeSoldOut = false,
116
116
  includeSellableForFree = false,
117
117
  orFiltersOperator
118
- }, {
119
- cached,
120
- bapiClient,
121
- campaignKey
122
- }) {
118
+ }, context) {
123
119
  let result;
124
120
  let allFiltersForCategory = [];
121
+ const {
122
+ cached,
123
+ bapiClient,
124
+ campaignKey
125
+ } = context;
125
126
  if (category !== "/") {
126
127
  result = await cached(bapiClient.categories.getByPath, {
127
128
  cacheKeyPrefix: `getByPath-categories-${category}`
@@ -132,11 +133,7 @@ const getFilters = exports.getFilters = async function getFilters2({
132
133
  slug: category
133
134
  },
134
135
  includedFilters
135
- }, {
136
- cached,
137
- bapiClient,
138
- campaignKey
139
- });
136
+ }, context);
140
137
  }
141
138
  const sanitizedAttributes = sanitizeAttributesForBAPI({
142
139
  attributes: where?.attributes || [],
@@ -94,16 +94,17 @@ export const getFilters = async function getFilters2({
94
94
  includeSoldOut = false,
95
95
  includeSellableForFree = false,
96
96
  orFiltersOperator
97
- }, { cached, bapiClient, campaignKey }) {
97
+ }, context) {
98
98
  let result;
99
99
  let allFiltersForCategory = [];
100
+ const { cached, bapiClient, campaignKey } = context;
100
101
  if (category !== "/") {
101
102
  result = await cached(bapiClient.categories.getByPath, {
102
103
  cacheKeyPrefix: `getByPath-categories-${category}`
103
104
  })(splitAndRemoveEmpty(category));
104
105
  allFiltersForCategory = await fetchAllFiltersForCategory(
105
106
  { category: { id: result.id, slug: category }, includedFilters },
106
- { cached, bapiClient, campaignKey }
107
+ context
107
108
  );
108
109
  }
109
110
  const sanitizedAttributes = sanitizeAttributesForBAPI({
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.updatePasswordByHash = exports.refreshAccessToken = exports.oauthRevokeToken = exports.oauthRegister = exports.oauthLogin = exports.oauthGuestLogin = exports.oauthForgetPassword = exports.convertErrorForRpcCall = void 0;
7
7
  var _jose = require("jose");
8
+ var _types = require("../../types/index.cjs");
8
9
  var _constants = require("../../constants/index.cjs");
9
10
  var _fetch = require("../../utils/fetch.cjs");
10
11
  var _user = require("../../utils/user.cjs");
@@ -36,6 +37,7 @@ async function postLogin(context, tokens) {
36
37
  await context.createUserBoundSession();
37
38
  }
38
39
  const oauthLogin = async (login, context) => {
40
+ (0, _types.assertSession)(context);
39
41
  const shopId = context.shopId;
40
42
  const client = (0, _oauth.getOAuthClient)(context);
41
43
  if (!login.email || !login.password) {
@@ -56,6 +58,7 @@ const oauthLogin = async (login, context) => {
56
58
  };
57
59
  exports.oauthLogin = oauthLogin;
58
60
  const oauthRegister = async (register, context) => {
61
+ (0, _types.assertSession)(context);
59
62
  const shopId = context.shopId;
60
63
  const client = (0, _oauth.getOAuthClient)(context);
61
64
  try {
@@ -73,6 +76,7 @@ const oauthRegister = async (register, context) => {
73
76
  };
74
77
  exports.oauthRegister = oauthRegister;
75
78
  const oauthGuestLogin = async (guest, context) => {
79
+ (0, _types.assertSession)(context);
76
80
  const shopId = context.shopId;
77
81
  const client = (0, _oauth.getOAuthClient)(context);
78
82
  try {
@@ -90,6 +94,7 @@ const oauthGuestLogin = async (guest, context) => {
90
94
  };
91
95
  exports.oauthGuestLogin = oauthGuestLogin;
92
96
  const refreshAccessToken = async context => {
97
+ (0, _types.assertSession)(context);
93
98
  const refreshToken = context.refreshToken;
94
99
  const client = (0, _oauth.getOAuthClient)(context);
95
100
  if (!refreshToken) {
@@ -112,10 +117,14 @@ const refreshAccessToken = async context => {
112
117
  if (err) {
113
118
  throw err;
114
119
  }
120
+ return {
121
+ success: false
122
+ };
115
123
  }
116
124
  };
117
125
  exports.refreshAccessToken = refreshAccessToken;
118
126
  const oauthRevokeToken = async context => {
127
+ (0, _types.assertSession)(context);
119
128
  const accessToken = context.accessToken;
120
129
  if (!accessToken) {
121
130
  throw new Error("No app oauth authentication credentials");
@@ -141,9 +150,13 @@ exports.oauthRevokeToken = oauthRevokeToken;
141
150
  const oauthForgetPassword = async ({
142
151
  email
143
152
  }, context) => {
153
+ (0, _types.assertSession)(context);
144
154
  const shopId = context.shopId;
145
155
  const client = (0, _oauth.getOAuthClient)(context);
146
156
  try {
157
+ if (!context.auth.resetPasswordUrl) {
158
+ throw new Error("Missing password reset URL");
159
+ }
147
160
  const resetUrl = new URL(context.auth.resetPasswordUrl);
148
161
  if (!resetUrl.searchParams.has("hash")) {
149
162
  resetUrl.searchParams.append("hash", "{hash}");
@@ -170,6 +183,7 @@ const oauthForgetPassword = async ({
170
183
  };
171
184
  exports.oauthForgetPassword = oauthForgetPassword;
172
185
  const updatePasswordByHash = async (passwordHash, context) => {
186
+ (0, _types.assertSession)(context);
173
187
  const shopId = context.shopId;
174
188
  const client = (0, _oauth.getOAuthClient)(context);
175
189
  try {
@@ -3,7 +3,7 @@ import type { GuestRequest, LoginRequest, ParamRpcHandler, RegisterRequest, RpcH
3
3
  /**
4
4
  * Ensure error is a fetch error with one of the status codes
5
5
  */
6
- export declare const convertErrorForRpcCall: (error: any, httpStatuses: Array<number>) => Error;
6
+ export declare const convertErrorForRpcCall: (error: any, httpStatuses: Array<number>) => Error | undefined;
7
7
  export declare const oauthLogin: ParamRpcHandler<Optional<LoginRequest, 'shop_id'>, undefined>;
8
8
  export declare const oauthRegister: ParamRpcHandler<Optional<RegisterRequest, 'shop_id'>, undefined>;
9
9
  export declare const oauthGuestLogin: ParamRpcHandler<Optional<GuestRequest, 'shop_id'>, undefined>;
@@ -1,4 +1,5 @@
1
1
  import { decodeJwt } from "jose";
2
+ import { assertSession } from "../../types/index.mjs";
2
3
  import { DEFAULT_WITH_LISTING, HttpStatusCode } from "../../constants/index.mjs";
3
4
  import { FetchError } from "../../utils/fetch.mjs";
4
5
  import { mergeBaskets, mergeWishlists } from "../../utils/user.mjs";
@@ -37,6 +38,7 @@ async function postLogin(context, tokens) {
37
38
  await context.createUserBoundSession();
38
39
  }
39
40
  export const oauthLogin = async (login, context) => {
41
+ assertSession(context);
40
42
  const shopId = context.shopId;
41
43
  const client = getOAuthClient(context);
42
44
  if (!login.email || !login.password) {
@@ -60,6 +62,7 @@ export const oauthLogin = async (login, context) => {
60
62
  }
61
63
  };
62
64
  export const oauthRegister = async (register, context) => {
65
+ assertSession(context);
63
66
  const shopId = context.shopId;
64
67
  const client = getOAuthClient(context);
65
68
  try {
@@ -80,6 +83,7 @@ export const oauthRegister = async (register, context) => {
80
83
  }
81
84
  };
82
85
  export const oauthGuestLogin = async (guest, context) => {
86
+ assertSession(context);
83
87
  const shopId = context.shopId;
84
88
  const client = getOAuthClient(context);
85
89
  try {
@@ -100,6 +104,7 @@ export const oauthGuestLogin = async (guest, context) => {
100
104
  }
101
105
  };
102
106
  export const refreshAccessToken = async (context) => {
107
+ assertSession(context);
103
108
  const refreshToken = context.refreshToken;
104
109
  const client = getOAuthClient(context);
105
110
  if (!refreshToken) {
@@ -123,9 +128,11 @@ export const refreshAccessToken = async (context) => {
123
128
  if (err) {
124
129
  throw err;
125
130
  }
131
+ return { success: false };
126
132
  }
127
133
  };
128
134
  export const oauthRevokeToken = async (context) => {
135
+ assertSession(context);
129
136
  const accessToken = context.accessToken;
130
137
  if (!accessToken) {
131
138
  throw new Error("No app oauth authentication credentials");
@@ -148,9 +155,13 @@ export const oauthRevokeToken = async (context) => {
148
155
  }
149
156
  };
150
157
  export const oauthForgetPassword = async ({ email }, context) => {
158
+ assertSession(context);
151
159
  const shopId = context.shopId;
152
160
  const client = getOAuthClient(context);
153
161
  try {
162
+ if (!context.auth.resetPasswordUrl) {
163
+ throw new Error("Missing password reset URL");
164
+ }
154
165
  const resetUrl = new URL(context.auth.resetPasswordUrl);
155
166
  if (!resetUrl.searchParams.has("hash")) {
156
167
  resetUrl.searchParams.append("hash", "{hash}");
@@ -177,6 +188,7 @@ export const oauthForgetPassword = async ({ email }, context) => {
177
188
  }
178
189
  };
179
190
  export const updatePasswordByHash = async (passwordHash, context) => {
191
+ assertSession(context);
180
192
  const shopId = context.shopId;
181
193
  const client = getOAuthClient(context);
182
194
  try {
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.refreshUser = exports.getUser = exports.fetchUser = void 0;
7
+ var _types = require("../../types/index.cjs");
7
8
  var _customer = require("../../api/customer.cjs");
8
9
  const getUser = exports.getUser = function getUser2(context) {
9
10
  return {
@@ -31,6 +32,7 @@ const fetchUser = exports.fetchUser = async function fetchUser2(payload, context
31
32
  };
32
33
  };
33
34
  const refreshUser = exports.refreshUser = async function refreshUser2(context) {
35
+ (0, _types.assertSession)(context);
34
36
  const {
35
37
  accessToken,
36
38
  shopId
@@ -40,10 +42,10 @@ const refreshUser = exports.refreshUser = async function refreshUser2(context) {
40
42
  if (user?.id) {
41
43
  context.updateUser({
42
44
  ...user,
43
- authentication: {
44
- ...user?.authentication,
45
+ authentication: user.authentication ? {
46
+ ...user.authentication,
45
47
  storefrontAccessToken: accessToken
46
- },
48
+ } : void 0,
47
49
  ...{
48
50
  loginShopId: shopId
49
51
  }
@@ -1,4 +1,4 @@
1
- import { AuthConfig, RpcHandler, ShopUser } from '../../types';
1
+ import type { AuthConfig, RpcHandler, ShopUser } from '../../types';
2
2
  declare const getUser: RpcHandler<{
3
3
  user: ShopUser | undefined;
4
4
  }>;
@@ -1,3 +1,4 @@
1
+ import { assertSession } from "../../types/index.mjs";
1
2
  import { CustomerAPIClient } from "../../api/customer.mjs";
2
3
  const getUser = function getUser2(context) {
3
4
  return {
@@ -19,16 +20,17 @@ const fetchUser = async function fetchUser2(payload, context) {
19
20
  };
20
21
  };
21
22
  const refreshUser = async function refreshUser2(context) {
23
+ assertSession(context);
22
24
  const { accessToken, shopId } = context;
23
25
  const client = new CustomerAPIClient(context);
24
26
  const user = await client.getMe(shopId);
25
27
  if (user?.id) {
26
28
  context.updateUser({
27
29
  ...user,
28
- authentication: {
29
- ...user?.authentication,
30
+ authentication: user.authentication ? {
31
+ ...user.authentication,
30
32
  storefrontAccessToken: accessToken
31
- },
33
+ } : void 0,
32
34
  ...{ loginShopId: shopId }
33
35
  });
34
36
  return { user };
@@ -4,11 +4,13 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.removeItemFromWishlist = exports.getWishlist = exports.clearWishlist = exports.addItemToWishlist = void 0;
7
+ var _types = require("../../types/index.cjs");
7
8
  var _constants = require("../../constants/index.cjs");
8
9
  function getWithParams(params, context) {
9
10
  return params.with ?? context.withParams?.basket ?? _constants.MIN_WITH_PARAMS_WISHLIST;
10
11
  }
11
12
  const addItemToWishlist = exports.addItemToWishlist = async function addItemToWishlist2(options, context) {
13
+ (0, _types.assertSession)(context);
12
14
  const {
13
15
  bapiClient,
14
16
  campaignKey,
@@ -48,20 +50,23 @@ const addItemToWishlist = exports.addItemToWishlist = async function addItemToWi
48
50
  }
49
51
  };
50
52
  const getWishlist = exports.getWishlist = async function getWishlist2(options, context) {
53
+ (0, _types.assertSession)(context);
51
54
  const {
52
55
  bapiClient,
53
- campaignKey
56
+ campaignKey,
57
+ wishlistKey
54
58
  } = context;
55
59
  const resolvedWith = getWithParams({
56
60
  with: options
57
61
  }, context);
58
- return await bapiClient.wishlist.get(context.wishlistKey, {
62
+ return await bapiClient.wishlist.get(wishlistKey, {
59
63
  with: resolvedWith,
60
64
  campaignKey,
61
65
  pricePromotionKey: resolvedWith?.pricePromotionKey ?? ""
62
66
  });
63
67
  };
64
68
  const removeItemFromWishlist = exports.removeItemFromWishlist = async function removeItemFromWishlist2(options, context) {
69
+ (0, _types.assertSession)(context);
65
70
  const {
66
71
  bapiClient,
67
72
  campaignKey,
@@ -1,5 +1,5 @@
1
1
  import type { WishlistResponseData } from '@aboutyou/backbone/endpoints/wishlist/getWishlist';
2
- import { RpcHandler, ParamRpcHandler, WishlistWithOptions } from '../../types';
2
+ import type { RpcHandler, ParamRpcHandler, WishlistWithOptions } from '../../types';
3
3
  export declare const addItemToWishlist: ParamRpcHandler<{
4
4
  variantId?: number;
5
5
  productId?: number;
@@ -1,8 +1,10 @@
1
+ import { assertSession } from "../../types/index.mjs";
1
2
  import { MIN_WITH_PARAMS_WISHLIST } from "../../constants/index.mjs";
2
3
  function getWithParams(params, context) {
3
4
  return params.with ?? context.withParams?.basket ?? MIN_WITH_PARAMS_WISHLIST;
4
5
  }
5
6
  export const addItemToWishlist = async function addItemToWishlist2(options, context) {
7
+ assertSession(context);
6
8
  const { bapiClient, campaignKey, wishlistKey } = context;
7
9
  const { productId, variantId } = options;
8
10
  const resolvedWith = getWithParams(options, context);
@@ -28,15 +30,17 @@ export const addItemToWishlist = async function addItemToWishlist2(options, cont
28
30
  }
29
31
  };
30
32
  export const getWishlist = async function getWishlist2(options, context) {
31
- const { bapiClient, campaignKey } = context;
33
+ assertSession(context);
34
+ const { bapiClient, campaignKey, wishlistKey } = context;
32
35
  const resolvedWith = getWithParams({ with: options }, context);
33
- return await bapiClient.wishlist.get(context.wishlistKey, {
36
+ return await bapiClient.wishlist.get(wishlistKey, {
34
37
  with: resolvedWith,
35
38
  campaignKey,
36
39
  pricePromotionKey: resolvedWith?.pricePromotionKey ?? ""
37
40
  });
38
41
  };
39
42
  export const removeItemFromWishlist = async function removeItemFromWishlist2(options, context) {
43
+ assertSession(context);
40
44
  const { bapiClient, campaignKey, wishlistKey } = context;
41
45
  const resolvedWith = getWithParams(options, context);
42
46
  return await bapiClient.wishlist.deleteItem(wishlistKey, options.itemKey, {
@@ -1 +1,11 @@
1
- "use strict";
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.assertSession = assertSession;
7
+ function assertSession(context) {
8
+ if (!context.sessionId) {
9
+ throw new Error("No session for RpcContext available");
10
+ }
11
+ }
@@ -1,7 +1,7 @@
1
1
  import type { BapiClient } from '@aboutyou/backbone';
2
- import { Log } from '../../utils';
3
- import { CachedType } from '../../cache/cached';
4
- import { ShopUser } from '../user';
2
+ import type { Log } from '../../utils';
3
+ import type { CachedType } from '../../cache/cached';
4
+ import type { ShopUser } from '../user';
5
5
  import type { WishlistWithOptions, BasketWithOptions, ProductWith, VariantWith, SearchWith, CategoryWith, ProductCategoryWith } from '../';
6
6
  import { OAuthTokens, IDPConfig } from './auth';
7
7
  export type WithParams = Partial<{
@@ -16,9 +16,29 @@ export type WithParams = Partial<{
16
16
  export interface RuntimeConfiguration {
17
17
  [key: string]: any;
18
18
  }
19
- export interface RpcContext {
19
+ interface ContextWithSession {
20
20
  wishlistKey: string;
21
21
  basketKey: string;
22
+ sessionId: string;
23
+ user?: ShopUser;
24
+ accessToken?: string;
25
+ destroySession: () => Promise<void>;
26
+ createUserBoundSession: () => Promise<void>;
27
+ updateUser: (user: ShopUser) => void;
28
+ updateTokens: (tokens: OAuthTokens) => void;
29
+ }
30
+ interface ContextNoSession {
31
+ wishlistKey: undefined;
32
+ basketKey: undefined;
33
+ sessionId: undefined;
34
+ user: undefined;
35
+ accessToken: undefined;
36
+ destroySession: undefined;
37
+ createUserBoundSession: undefined;
38
+ updateUser: undefined;
39
+ updateTokens: undefined;
40
+ }
41
+ export type RpcContext = {
22
42
  locale: string;
23
43
  checkout: {
24
44
  url: string;
@@ -33,26 +53,19 @@ export interface RpcContext {
33
53
  };
34
54
  bapiClient: BapiClient;
35
55
  cached: CachedType;
36
- user?: ShopUser;
37
- accessToken?: string;
38
56
  refreshToken?: string;
39
57
  isCmsPreview: boolean;
40
58
  shopId: number;
41
59
  domain: string;
42
60
  withParams?: WithParams;
43
61
  campaignKey?: string;
44
- updateUser: (user: ShopUser) => void;
45
62
  destroySessionsForUserId: (userId: number, sessionsToKeep?: string[]) => Promise<void>;
46
- updateTokens: (tokens: OAuthTokens) => void;
47
- destroySession: () => Promise<void>;
48
63
  generateBasketKeyForUserId: (userId: string) => Promise<string>;
49
64
  generateWishlistKeyForUserId: (userId: string) => Promise<string>;
50
- createUserBoundSession: () => Promise<void>;
51
65
  storeCampaignKeyword?: string;
52
66
  routerBasePath?: string;
53
67
  ip?: string | undefined;
54
68
  log: Log;
55
- sessionId: string;
56
69
  auth: {
57
70
  resetPasswordUrl?: string;
58
71
  };
@@ -63,4 +76,9 @@ export interface RpcContext {
63
76
  };
64
77
  runtimeConfiguration: RuntimeConfiguration;
65
78
  idp?: IDPConfig;
66
- }
79
+ } & (ContextNoSession | ContextWithSession);
80
+ export type RpcContextWithSession = RpcContext & {
81
+ sessionId: string;
82
+ };
83
+ export declare function assertSession(context: RpcContext): asserts context is RpcContextWithSession;
84
+ export {};
@@ -0,0 +1,5 @@
1
+ export function assertSession(context) {
2
+ if (!context.sessionId) {
3
+ throw new Error("No session for RpcContext available");
4
+ }
5
+ }
@@ -7,18 +7,18 @@ export declare const generateKey: ({ keyTemplate, hashAlgorithm, shopId, userId,
7
7
  shopId: string;
8
8
  userId: string;
9
9
  log?: Log | undefined;
10
- }) => Promise<any>;
10
+ }) => Promise<string>;
11
11
  export declare const generateWishlistKey: ({ keyTemplate, hashAlgorithm, shopId, userId, log, }: {
12
12
  keyTemplate: string;
13
13
  hashAlgorithm: HashAlgorithm;
14
14
  shopId: string;
15
15
  userId: string;
16
16
  log?: Log | undefined;
17
- }) => Promise<any>;
17
+ }) => Promise<string>;
18
18
  export declare const generateBasketKey: ({ keyTemplate, hashAlgorithm, shopId, userId, log, }: {
19
19
  keyTemplate: string;
20
20
  hashAlgorithm: HashAlgorithm;
21
21
  shopId: string;
22
22
  userId: string;
23
23
  log?: Log | undefined;
24
- }) => Promise<any>;
24
+ }) => Promise<string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scayle/storefront-core",
3
- "version": "7.37.0",
3
+ "version": "7.38.0",
4
4
  "description": "Collection of essential utilities to work with the Storefront API",
5
5
  "author": "SCAYLE Commerce Engine",
6
6
  "license": "MIT",
@@ -59,22 +59,22 @@
59
59
  "test:ci": "vitest --run --passWithNoTests --coverage --reporter=default --reporter=junit"
60
60
  },
61
61
  "dependencies": {
62
- "@aboutyou/backbone": "16.0.2",
62
+ "@aboutyou/backbone": "16.1.0",
63
63
  "crypto-js": "4.2.0",
64
- "jose": "^5.2.0",
65
- "radash": "^11.0.0",
66
- "slugify": "^1.6.0",
67
- "ufo": "^1.1.1",
68
- "uncrypto": "^0.1.3",
69
- "utility-types": "^3.10.0"
64
+ "jose": "5.2.0",
65
+ "radash": "11.0.0",
66
+ "slugify": "1.6.6",
67
+ "ufo": "1.3.2",
68
+ "uncrypto": "0.1.3",
69
+ "utility-types": "3.11.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@scayle/eslint-config-storefront": "3.2.6",
73
73
  "@scayle/prettier-config-storefront": "2.0.2",
74
- "@types/crypto-js": "4.2.1",
75
- "@types/node": "20.11.4",
74
+ "@types/crypto-js": "4.2.2",
75
+ "@types/node": "20.11.5",
76
76
  "@types/webpack-env": "1.18.4",
77
- "@vitest/coverage-v8": "1.2.0",
77
+ "@vitest/coverage-v8": "1.2.1",
78
78
  "eslint": "8.56.0",
79
79
  "eslint-formatter-gitlab": "5.1.0",
80
80
  "prettier": "3.0.0",
@@ -84,7 +84,7 @@
84
84
  "typescript": "5.3.3",
85
85
  "unbuild": "2.0.0",
86
86
  "unstorage": "1.10.1",
87
- "vitest": "1.2.0"
87
+ "vitest": "1.2.1"
88
88
  },
89
89
  "optionalDependencies": {
90
90
  "redis": "4"