@scayle/storefront-core 7.59.2 → 7.60.1

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,31 @@
1
1
  # @scayle/storefront-core
2
2
 
3
+ ## 7.60.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @scayle/storefront-api@17.4.2
9
+
10
+ ## 7.60.0
11
+
12
+ ### Minor Changes
13
+
14
+ - Instead of throwing errors, RPCs will now return an `ErrorResponse` object conforming to [RFC9457](https://datatracker.ietf.org/doc/html/rfc9457).
15
+
16
+ In addition to this change, existing RPCs have been reviewed and updated to return more accurate and descriptive HTTP status codes.
17
+
18
+ This change allows us to provide more informative error messages and utilize standardized HTTP status codes for better context. The `ErrorResponse` object can include additional details about the error, making it easier to identify and address root causes.
19
+
20
+ **Action Required:**
21
+
22
+ Code that previously made direct RPC calls (calling the function directly instead of via `useRpc` or `rpcCall`) and relied on `try {} catch {}` blocks for error handling needs to be updated. Moving forward, please check the status of the RPC response directly.
23
+
24
+ ### Patch Changes
25
+
26
+ - Updated dependencies
27
+ - @scayle/storefront-api@17.4.1
28
+
3
29
  ## 7.59.2
4
30
 
5
31
  ### Patch Changes
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ErrorResponse = void 0;
7
+ class ErrorResponse extends Response {
8
+ title;
9
+ details;
10
+ constructor(statusCode, statusMessage, title, detail) {
11
+ const response = {
12
+ title,
13
+ status: statusCode,
14
+ ...detail
15
+ };
16
+ super(JSON.stringify(response, null, 2), {
17
+ status: statusCode,
18
+ statusText: statusMessage,
19
+ headers: {
20
+ "Content-Type": "application/problem+json"
21
+ }
22
+ });
23
+ this.title = title;
24
+ this.details = detail;
25
+ }
26
+ }
27
+ exports.ErrorResponse = ErrorResponse;
@@ -0,0 +1,12 @@
1
+ interface ErrorResponseDetails {
2
+ detail?: string;
3
+ instance?: string;
4
+ type?: string;
5
+ [s: string]: string | undefined;
6
+ }
7
+ export declare class ErrorResponse extends Response {
8
+ title: string;
9
+ details?: ErrorResponseDetails;
10
+ constructor(statusCode: number, statusMessage: string, title: string, detail?: ErrorResponseDetails);
11
+ }
12
+ export {};
@@ -0,0 +1,20 @@
1
+ export class ErrorResponse extends Response {
2
+ title;
3
+ details;
4
+ constructor(statusCode, statusMessage, title, detail) {
5
+ const response = {
6
+ title,
7
+ status: statusCode,
8
+ ...detail
9
+ };
10
+ super(JSON.stringify(response, null, 2), {
11
+ status: statusCode,
12
+ statusText: statusMessage,
13
+ headers: {
14
+ "Content-Type": "application/problem+json"
15
+ }
16
+ });
17
+ this.title = title;
18
+ this.details = detail;
19
+ }
20
+ }
@@ -24,4 +24,15 @@ Object.keys(_baseError).forEach(function (key) {
24
24
  return _baseError[key];
25
25
  }
26
26
  });
27
+ });
28
+ var _errorResponse = require("./errorResponse.cjs");
29
+ Object.keys(_errorResponse).forEach(function (key) {
30
+ if (key === "default" || key === "__esModule") return;
31
+ if (key in exports && exports[key] === _errorResponse[key]) return;
32
+ Object.defineProperty(exports, key, {
33
+ enumerable: true,
34
+ get: function () {
35
+ return _errorResponse[key];
36
+ }
37
+ });
27
38
  });
@@ -1,2 +1,3 @@
1
1
  export * from './BAPIError';
2
2
  export * from './baseError';
3
+ export * from './errorResponse';
@@ -1,2 +1,3 @@
1
1
  export * from "./BAPIError.mjs";
2
2
  export * from "./baseError.mjs";
3
+ export * from "./errorResponse.mjs";
@@ -61,7 +61,9 @@ const addItemToBasket = exports.addItemToBasket = async function addItemToBasket
61
61
  message,
62
62
  statusCode
63
63
  });
64
- throw new _errors.BAPIError(BAPI_ERROR_NAME, statusCode, message, "addItemToBasket:bapiClient.basket.addOrUpdateItems");
64
+ return new _errors.ErrorResponse(statusCode, BAPI_ERROR_NAME, "Adding item to basket failed", {
65
+ detail: message
66
+ });
65
67
  }
66
68
  };
67
69
  const addItemsToBasket = exports.addItemsToBasket = async function addItemsToBasket2(params, context) {
@@ -103,7 +105,9 @@ const addItemsToBasket = exports.addItemsToBasket = async function addItemsToBas
103
105
  statusCode,
104
106
  message
105
107
  });
106
- throw new _errors.BAPIError(BAPI_ERROR_NAME, statusCode, message, "addItemsToBasket:bapiClient.basket.addOrUpdateItems");
108
+ return new _errors.ErrorResponse(statusCode, BAPI_ERROR_NAME, "Adding one or more items to basket failed", {
109
+ detail: message
110
+ });
107
111
  }
108
112
  };
109
113
  const getBasket = exports.getBasket = async function getBasket2(options, context) {
@@ -126,7 +130,9 @@ const getBasket = exports.getBasket = async function getBasket2(options, context
126
130
  statusCode,
127
131
  message
128
132
  } = parseBasketError(response);
129
- throw new _errors.BAPIError(BAPI_ERROR_NAME, statusCode, message, "bapiClient.basket.get");
133
+ return new _errors.ErrorResponse(statusCode, BAPI_ERROR_NAME, "Adding item to basket failed", {
134
+ detail: message
135
+ });
130
136
  }
131
137
  return response.basket;
132
138
  };
@@ -145,7 +151,11 @@ const removeItemFromBasket = exports.removeItemFromBasket = async function remov
145
151
  });
146
152
  };
147
153
  const clearBasket = exports.clearBasket = async function clearBasket2(context) {
148
- const basket = await (0, _response.unwrap)(getBasket({}, context));
154
+ const getBasketResponse = await getBasket({}, context);
155
+ if (getBasketResponse instanceof _errors.ErrorResponse) {
156
+ return getBasketResponse;
157
+ }
158
+ const basket = await (0, _response.unwrap)(getBasketResponse);
149
159
  await Promise.all(basket.items.map(async item => {
150
160
  await removeItemFromBasket({
151
161
  itemKey: item.key
@@ -1,21 +1,22 @@
1
1
  import type { AddOrUpdateItemError } from '@scayle/storefront-api';
2
+ import { ErrorResponse } from '../../../errors';
2
3
  import type { AddOrUpdateItemType, BasketResponseData, BasketWithOptions, Product, RpcContext, Variant } from '../../../types';
3
4
  import { ExistingItemHandling } from '../../../constants';
4
5
  export declare const addItemToBasket: ({ variantId, promotionId, quantity, displayData, customData, existingItemHandling, itemGroup, with: _with, }: AddOrUpdateItemType & {
5
6
  existingItemHandling?: ExistingItemHandling;
6
7
  with?: BasketWithOptions;
7
- }, context: RpcContext) => Promise<BasketResponseData<Product, Variant>>;
8
+ }, context: RpcContext) => Promise<ErrorResponse | BasketResponseData<Product, Variant>>;
8
9
  export declare const addItemsToBasket: (params: {
9
10
  items: AddOrUpdateItemType[];
10
11
  existingItemHandling: ExistingItemHandling;
11
12
  with?: BasketWithOptions;
12
- }, context: RpcContext) => Promise<BasketResponseData<Product, Variant>>;
13
- export declare const getBasket: (options: BasketWithOptions | undefined, context: RpcContext) => Promise<BasketResponseData<Product, Variant>>;
13
+ }, context: RpcContext) => Promise<ErrorResponse | BasketResponseData<Product, Variant>>;
14
+ export declare const getBasket: (options: BasketWithOptions | undefined, context: RpcContext) => Promise<ErrorResponse | BasketResponseData<Product, Variant>>;
14
15
  export declare const removeItemFromBasket: (options: {
15
16
  itemKey: string;
16
17
  with?: BasketWithOptions;
17
18
  }, context: RpcContext) => Promise<BasketResponseData<Product, Variant>>;
18
- export declare const clearBasket: (context: RpcContext) => Promise<boolean>;
19
+ export declare const clearBasket: (context: RpcContext) => Promise<true | ErrorResponse>;
19
20
  export declare const mergeBaskets: ({ fromBasketKey, toBasketKey, with: _with }: {
20
21
  fromBasketKey: string;
21
22
  toBasketKey: string;
@@ -1,4 +1,4 @@
1
- import { BAPIError } from "../../../errors/index.mjs";
1
+ import { ErrorResponse } from "../../../errors/index.mjs";
2
2
  import { assertSession } from "../../../types/index.mjs";
3
3
  import {
4
4
  ExistingItemHandling,
@@ -56,11 +56,13 @@ export const addItemToBasket = async function addItemToBasket2({
56
56
  } else {
57
57
  const { message, statusCode } = parseBasketError(result);
58
58
  context.log.error("Adding item to basket failed", { message, statusCode });
59
- throw new BAPIError(
60
- BAPI_ERROR_NAME,
59
+ return new ErrorResponse(
61
60
  statusCode,
62
- message,
63
- "addItemToBasket:bapiClient.basket.addOrUpdateItems"
61
+ BAPI_ERROR_NAME,
62
+ "Adding item to basket failed",
63
+ {
64
+ detail: message
65
+ }
64
66
  );
65
67
  }
66
68
  };
@@ -101,11 +103,13 @@ export const addItemsToBasket = async function addItemsToBasket2(params, context
101
103
  statusCode,
102
104
  message
103
105
  });
104
- throw new BAPIError(
105
- BAPI_ERROR_NAME,
106
+ return new ErrorResponse(
106
107
  statusCode,
107
- message,
108
- "addItemsToBasket:bapiClient.basket.addOrUpdateItems"
108
+ BAPI_ERROR_NAME,
109
+ "Adding one or more items to basket failed",
110
+ {
111
+ detail: message
112
+ }
109
113
  );
110
114
  }
111
115
  };
@@ -123,11 +127,13 @@ export const getBasket = async function getBasket2(options, context) {
123
127
  });
124
128
  if (response.type === "failure") {
125
129
  const { statusCode, message } = parseBasketError(response);
126
- throw new BAPIError(
127
- BAPI_ERROR_NAME,
130
+ return new ErrorResponse(
128
131
  statusCode,
129
- message,
130
- "bapiClient.basket.get"
132
+ BAPI_ERROR_NAME,
133
+ "Adding item to basket failed",
134
+ {
135
+ detail: message
136
+ }
131
137
  );
132
138
  }
133
139
  return response.basket;
@@ -143,7 +149,11 @@ export const removeItemFromBasket = async function removeItemFromBasket2(options
143
149
  });
144
150
  };
145
151
  export const clearBasket = async function clearBasket2(context) {
146
- const basket = await unwrap(getBasket({}, context));
152
+ const getBasketResponse = await getBasket({}, context);
153
+ if (getBasketResponse instanceof ErrorResponse) {
154
+ return getBasketResponse;
155
+ }
156
+ const basket = await unwrap(getBasketResponse);
147
157
  await Promise.all(
148
158
  basket.items.map(async (item) => {
149
159
  await removeItemFromBasket({ itemKey: item.key }, context);
@@ -5,10 +5,12 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.getCheckoutToken = void 0;
7
7
  var _jose = require("jose");
8
+ var _errors = require("../../../errors/index.cjs");
9
+ var _constants = require("../../../constants/index.cjs");
8
10
  var _user = require("../user.cjs");
9
11
  const getCheckoutToken = exports.getCheckoutToken = async function getCheckoutToken2(jwtPayload = {}, context) {
10
12
  if (!context.accessToken) {
11
- throw new Error("No access token present");
13
+ return new _errors.ErrorResponse(_constants.HttpStatusCode.UNAUTHORIZED, _constants.HttpStatusMessage.UNAUTHORIZED, "No access token present");
12
14
  }
13
15
  const refreshedAccessToken = await (0, _user.getAccessToken)({
14
16
  forceTokenRefresh: true
@@ -31,7 +33,7 @@ const getCheckoutToken = exports.getCheckoutToken = async function getCheckoutTo
31
33
  carrier,
32
34
  basketId: context.basketKey,
33
35
  campaignKey: context.campaignKey
34
- }).setIssuedAt(now).setNotBefore(now).setExpirationTime("1h").setIssuer(`${"@scayle/storefront-core"}@${"7.59.2"}`).setProtectedHeader({
36
+ }).setIssuedAt(now).setNotBefore(now).setExpirationTime("1h").setIssuer(`${"@scayle/storefront-core"}@${"7.60.1"}`).setProtectedHeader({
35
37
  alg: "HS256",
36
38
  typ: "JWT"
37
39
  }).sign(secret);
@@ -1,8 +1,14 @@
1
1
  import { SignJWT } from "jose";
2
+ import { ErrorResponse } from "../../../errors/index.mjs";
3
+ import { HttpStatusCode, HttpStatusMessage } from "../../../constants/index.mjs";
2
4
  import { getAccessToken } from "../user.mjs";
3
5
  export const getCheckoutToken = async function getCheckoutToken2(jwtPayload = {}, context) {
4
6
  if (!context.accessToken) {
5
- throw new Error("No access token present");
7
+ return new ErrorResponse(
8
+ HttpStatusCode.UNAUTHORIZED,
9
+ HttpStatusMessage.UNAUTHORIZED,
10
+ "No access token present"
11
+ );
6
12
  }
7
13
  const refreshedAccessToken = await getAccessToken(
8
14
  { forceTokenRefresh: true },
@@ -21,7 +27,7 @@ export const getCheckoutToken = async function getCheckoutToken2(jwtPayload = {}
21
27
  carrier,
22
28
  basketId: context.basketKey,
23
29
  campaignKey: context.campaignKey
24
- }).setIssuedAt(now).setNotBefore(now).setExpirationTime("1h").setIssuer(`${"@scayle/storefront-core"}@${"7.59.2"}`).setProtectedHeader({ alg: "HS256", typ: "JWT" }).sign(secret);
30
+ }).setIssuedAt(now).setNotBefore(now).setExpirationTime("1h").setIssuer(`${"@scayle/storefront-core"}@${"7.60.1"}`).setProtectedHeader({ alg: "HS256", typ: "JWT" }).sign(secret);
25
31
  return {
26
32
  accessToken: refreshedAccessToken,
27
33
  checkoutJwt
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.getOrderById = void 0;
7
+ var _errors = require("../../../errors/index.cjs");
7
8
  var _fetch = require("../../../utils/fetch.cjs");
8
9
  var _customer = require("../../../api/customer.cjs");
9
10
  var _httpStatus = require("../../../constants/httpStatus.cjs");
@@ -12,14 +13,14 @@ const getOrderById = exports.getOrderById = async function getOrderById2({
12
13
  }, context) {
13
14
  const accessToken = context.accessToken;
14
15
  if (!orderId || isNaN(orderId)) {
15
- throw new Error("No order-id provided");
16
+ return new _errors.ErrorResponse(_httpStatus.HttpStatusCode.NOT_FOUND, _httpStatus.HttpStatusMessage.NOT_FOUND, "No order ID provided");
16
17
  }
17
18
  if (!accessToken) {
18
- throw new Error("No access token");
19
+ return new _errors.ErrorResponse(_httpStatus.HttpStatusCode.UNAUTHORIZED, _httpStatus.HttpStatusMessage.UNAUTHORIZED, "No access token present");
19
20
  }
20
21
  const isOrderInUserOrderlist = context.user?.orderSummary?.find(order => order.id === orderId);
21
22
  if (!isOrderInUserOrderlist) {
22
- throw new Error("Order not belonging to current user");
23
+ return new _errors.ErrorResponse(_httpStatus.HttpStatusCode.UNAUTHORIZED, _httpStatus.HttpStatusMessage.UNAUTHORIZED, "Order not belonging to current user");
23
24
  }
24
25
  const client = new _customer.CustomerAPIClient(context);
25
26
  try {
@@ -27,13 +28,15 @@ const getOrderById = exports.getOrderById = async function getOrderById2({
27
28
  } catch (error) {
28
29
  if (error instanceof _fetch.FetchError) {
29
30
  if (error.response.status === _httpStatus.HttpStatusCode.NOT_FOUND) {
30
- throw new Error("Order not found");
31
+ return new _errors.ErrorResponse(_httpStatus.HttpStatusCode.NOT_FOUND, _httpStatus.HttpStatusMessage.NOT_FOUND, "Order not found");
31
32
  }
32
- throw new Error(`Unknown response status: ${error.response.status}`);
33
+ return new _errors.ErrorResponse(_httpStatus.HttpStatusCode.INTERNAL_SERVER_ERROR, _httpStatus.HttpStatusMessage.INTERNAL_SERVER_ERROR, "Unknown error", {
34
+ detail: `Fetching order "${orderId}" failed with status code ${error.response.status}`
35
+ });
33
36
  }
34
37
  context.log.error("Error while fetching order details", {
35
38
  error
36
39
  });
37
- throw error;
40
+ return new _errors.ErrorResponse(_httpStatus.HttpStatusCode.INTERNAL_SERVER_ERROR, _httpStatus.HttpStatusMessage.INTERNAL_SERVER_ERROR, "Unknown error");
38
41
  }
39
42
  };
@@ -1,4 +1,5 @@
1
+ import { ErrorResponse } from '../../../errors';
1
2
  import type { Order } from '../../../types';
2
3
  export declare const getOrderById: ({ orderId }: {
3
4
  orderId: number;
4
- }, context: import("../../../types").RpcContext) => Promise<Order>;
5
+ }, context: import("../../../types").RpcContext) => Promise<Order | ErrorResponse>;
@@ -1,19 +1,35 @@
1
+ import { ErrorResponse } from "../../../errors/index.mjs";
1
2
  import { FetchError } from "../../../utils/fetch.mjs";
2
3
  import { CustomerAPIClient } from "../../../api/customer.mjs";
3
- import { HttpStatusCode } from "../../../constants/httpStatus.mjs";
4
+ import {
5
+ HttpStatusCode,
6
+ HttpStatusMessage
7
+ } from "../../../constants/httpStatus.mjs";
4
8
  export const getOrderById = async function getOrderById2({ orderId }, context) {
5
9
  const accessToken = context.accessToken;
6
10
  if (!orderId || isNaN(orderId)) {
7
- throw new Error("No order-id provided");
11
+ return new ErrorResponse(
12
+ HttpStatusCode.NOT_FOUND,
13
+ HttpStatusMessage.NOT_FOUND,
14
+ "No order ID provided"
15
+ );
8
16
  }
9
17
  if (!accessToken) {
10
- throw new Error("No access token");
18
+ return new ErrorResponse(
19
+ HttpStatusCode.UNAUTHORIZED,
20
+ HttpStatusMessage.UNAUTHORIZED,
21
+ "No access token present"
22
+ );
11
23
  }
12
24
  const isOrderInUserOrderlist = context.user?.orderSummary?.find(
13
25
  (order) => order.id === orderId
14
26
  );
15
27
  if (!isOrderInUserOrderlist) {
16
- throw new Error("Order not belonging to current user");
28
+ return new ErrorResponse(
29
+ HttpStatusCode.UNAUTHORIZED,
30
+ HttpStatusMessage.UNAUTHORIZED,
31
+ "Order not belonging to current user"
32
+ );
17
33
  }
18
34
  const client = new CustomerAPIClient(context);
19
35
  try {
@@ -21,11 +37,26 @@ export const getOrderById = async function getOrderById2({ orderId }, context) {
21
37
  } catch (error) {
22
38
  if (error instanceof FetchError) {
23
39
  if (error.response.status === HttpStatusCode.NOT_FOUND) {
24
- throw new Error("Order not found");
40
+ return new ErrorResponse(
41
+ HttpStatusCode.NOT_FOUND,
42
+ HttpStatusMessage.NOT_FOUND,
43
+ "Order not found"
44
+ );
25
45
  }
26
- throw new Error(`Unknown response status: ${error.response.status}`);
46
+ return new ErrorResponse(
47
+ HttpStatusCode.INTERNAL_SERVER_ERROR,
48
+ HttpStatusMessage.INTERNAL_SERVER_ERROR,
49
+ "Unknown error",
50
+ {
51
+ detail: `Fetching order "${orderId}" failed with status code ${error.response.status}`
52
+ }
53
+ );
27
54
  }
28
55
  context.log.error("Error while fetching order details", { error });
29
- throw error;
56
+ return new ErrorResponse(
57
+ HttpStatusCode.INTERNAL_SERVER_ERROR,
58
+ HttpStatusMessage.INTERNAL_SERVER_ERROR,
59
+ "Unknown error"
60
+ );
30
61
  }
31
62
  };
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.updateShopUser = exports.updatePassword = void 0;
7
+ var _errors = require("../../../errors/index.cjs");
7
8
  var _customer = require("../../../api/customer.cjs");
8
9
  var _oauth = require("../../../api/oauth.cjs");
9
10
  var _fetch = require("../../../utils/fetch.cjs");
@@ -22,7 +23,7 @@ const updateShopUser = exports.updateShopUser = async function updateShopUser2(p
22
23
  if (!context.user) {
23
24
  const message = "Unauthorized request: Missing access token";
24
25
  context.log.error(message);
25
- throw new Error(message);
26
+ return new _errors.ErrorResponse(_httpStatus.HttpStatusCode.UNAUTHORIZED, _httpStatus.HttpStatusMessage.UNAUTHORIZED, "No access token present");
26
27
  }
27
28
  const user = {
28
29
  ...context.user,
@@ -50,7 +51,7 @@ const updateShopUser = exports.updateShopUser = async function updateShopUser2(p
50
51
  };
51
52
  } catch (error) {
52
53
  context.log.error("Error while updating user information", error);
53
- throw error;
54
+ return new _errors.ErrorResponse(_httpStatus.HttpStatusCode.INTERNAL_SERVER_ERROR, _httpStatus.HttpStatusMessage.INTERNAL_SERVER_ERROR, "Error while updating user information");
54
55
  }
55
56
  };
56
57
  const updatePassword = exports.updatePassword = async function updatePassword2({
@@ -63,7 +64,7 @@ const updatePassword = exports.updatePassword = async function updatePassword2({
63
64
  if (oauthEnabled) {
64
65
  const client2 = (0, _oauth.getOAuthClient)(context);
65
66
  if (!context.accessToken) {
66
- throw new Error("User is not logged in");
67
+ return new _errors.ErrorResponse(_httpStatus.HttpStatusCode.UNAUTHORIZED, _httpStatus.HttpStatusMessage.UNAUTHORIZED, "No access token present");
67
68
  }
68
69
  await client2.updatePassword({
69
70
  password: oldPassword,
@@ -87,14 +88,20 @@ const updatePassword = exports.updatePassword = async function updatePassword2({
87
88
  } catch (error) {
88
89
  if (!(error instanceof _fetch.FetchError)) {
89
90
  context.log.error("Error while updating user's password", error);
90
- throw new Error(error);
91
+ return new _errors.ErrorResponse(_httpStatus.HttpStatusCode.INTERNAL_SERVER_ERROR, _httpStatus.HttpStatusMessage.INTERNAL_SERVER_ERROR, "Error while updating user's password");
91
92
  }
92
93
  if (error.response.status === _httpStatus.HttpStatusCode.UNAUTHORIZED) {
93
- throw new Error("401 - Failed to update user's password - Unauthorized request");
94
+ return new _errors.ErrorResponse(_httpStatus.HttpStatusCode.UNAUTHORIZED, _httpStatus.HttpStatusMessage.UNAUTHORIZED, "Failed to update user's password", {
95
+ detail: "Unauthorized request"
96
+ });
94
97
  } else if (error.response.status === _httpStatus.HttpStatusCode.FORBIDDEN) {
95
- throw new Error("403 - Failed to update user's password - Invalid auth");
98
+ return new _errors.ErrorResponse(_httpStatus.HttpStatusCode.FORBIDDEN, _httpStatus.HttpStatusMessage.FORBIDDEN, "Failed to update user's password", {
99
+ detail: "Invalid auth"
100
+ });
96
101
  } else if (error.response.status === _httpStatus.HttpStatusCode.NOT_FOUND) {
97
- throw new Error("404 - Failed to update user's password - User not found");
102
+ return new _errors.ErrorResponse(_httpStatus.HttpStatusCode.NOT_FOUND, _httpStatus.HttpStatusMessage.NOT_FOUND, "Failed to update user's password", {
103
+ detail: "User not found"
104
+ });
98
105
  }
99
106
  }
100
107
  return {
@@ -1,7 +1,8 @@
1
+ import { ErrorResponse } from '../../../errors';
1
2
  import type { ShopUser, UpdatePasswordParams } from '../../../types';
2
- export declare const updateShopUser: (payload: Partial<ShopUser>, context: import("../../../types").RpcContext) => Promise<{
3
+ export declare const updateShopUser: (payload: Partial<ShopUser>, context: import("../../../types").RpcContext) => Promise<ErrorResponse | {
3
4
  user: ShopUser;
4
5
  }>;
5
- export declare const updatePassword: ({ oldPassword, newPassword }: UpdatePasswordParams, context: import("../../../types").RpcContext) => Promise<{
6
+ export declare const updatePassword: ({ oldPassword, newPassword }: UpdatePasswordParams, context: import("../../../types").RpcContext) => Promise<ErrorResponse | {
6
7
  user: ShopUser | undefined;
7
8
  }>;
@@ -1,7 +1,11 @@
1
+ import { ErrorResponse } from "../../../errors/index.mjs";
1
2
  import { CustomerAPIClient } from "../../../api/customer.mjs";
2
3
  import { getOAuthClient } from "../../../api/oauth.mjs";
3
4
  import { FetchError } from "../../../utils/fetch.mjs";
4
- import { HttpStatusCode } from "../../../constants/httpStatus.mjs";
5
+ import {
6
+ HttpStatusCode,
7
+ HttpStatusMessage
8
+ } from "../../../constants/httpStatus.mjs";
5
9
  export const updateShopUser = async function updateShopUser2(payload, context) {
6
10
  const shopId = context.shopId;
7
11
  const updatedUser = {
@@ -16,7 +20,11 @@ export const updateShopUser = async function updateShopUser2(payload, context) {
16
20
  if (!context.user) {
17
21
  const message = "Unauthorized request: Missing access token";
18
22
  context.log.error(message);
19
- throw new Error(message);
23
+ return new ErrorResponse(
24
+ HttpStatusCode.UNAUTHORIZED,
25
+ HttpStatusMessage.UNAUTHORIZED,
26
+ "No access token present"
27
+ );
20
28
  }
21
29
  const user = {
22
30
  ...context.user,
@@ -41,7 +49,11 @@ export const updateShopUser = async function updateShopUser2(payload, context) {
41
49
  return { user };
42
50
  } catch (error) {
43
51
  context.log.error("Error while updating user information", error);
44
- throw error;
52
+ return new ErrorResponse(
53
+ HttpStatusCode.INTERNAL_SERVER_ERROR,
54
+ HttpStatusMessage.INTERNAL_SERVER_ERROR,
55
+ "Error while updating user information"
56
+ );
45
57
  }
46
58
  };
47
59
  export const updatePassword = async function updatePassword2({ oldPassword, newPassword }, context) {
@@ -51,7 +63,11 @@ export const updatePassword = async function updatePassword2({ oldPassword, newP
51
63
  if (oauthEnabled) {
52
64
  const client2 = getOAuthClient(context);
53
65
  if (!context.accessToken) {
54
- throw new Error("User is not logged in");
66
+ return new ErrorResponse(
67
+ HttpStatusCode.UNAUTHORIZED,
68
+ HttpStatusMessage.UNAUTHORIZED,
69
+ "No access token present"
70
+ );
55
71
  }
56
72
  await client2.updatePassword(
57
73
  {
@@ -74,16 +90,33 @@ export const updatePassword = async function updatePassword2({ oldPassword, newP
74
90
  } catch (error) {
75
91
  if (!(error instanceof FetchError)) {
76
92
  context.log.error("Error while updating user's password", error);
77
- throw new Error(error);
93
+ return new ErrorResponse(
94
+ HttpStatusCode.INTERNAL_SERVER_ERROR,
95
+ HttpStatusMessage.INTERNAL_SERVER_ERROR,
96
+ "Error while updating user's password"
97
+ );
78
98
  }
79
99
  if (error.response.status === HttpStatusCode.UNAUTHORIZED) {
80
- throw new Error(
81
- "401 - Failed to update user's password - Unauthorized request"
100
+ return new ErrorResponse(
101
+ HttpStatusCode.UNAUTHORIZED,
102
+ HttpStatusMessage.UNAUTHORIZED,
103
+ "Failed to update user's password",
104
+ { detail: "Unauthorized request" }
82
105
  );
83
106
  } else if (error.response.status === HttpStatusCode.FORBIDDEN) {
84
- throw new Error("403 - Failed to update user's password - Invalid auth");
107
+ return new ErrorResponse(
108
+ HttpStatusCode.FORBIDDEN,
109
+ HttpStatusMessage.FORBIDDEN,
110
+ "Failed to update user's password",
111
+ { detail: "Invalid auth" }
112
+ );
85
113
  } else if (error.response.status === HttpStatusCode.NOT_FOUND) {
86
- throw new Error("404 - Failed to update user's password - User not found");
114
+ return new ErrorResponse(
115
+ HttpStatusCode.NOT_FOUND,
116
+ HttpStatusMessage.NOT_FOUND,
117
+ "Failed to update user's password",
118
+ { detail: "User not found" }
119
+ );
87
120
  }
88
121
  }
89
122
  return { user: shopUser };
@@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.handleIDPLoginCallback = exports.getExternalIdpRedirect = void 0;
7
7
  var _jose = require("jose");
8
+ var _errors = require("../../../errors/index.cjs");
9
+ var _constants = require("../../../constants/index.cjs");
8
10
  var _types = require("../../../types/index.cjs");
9
11
  var _oauth = require("../../../api/oauth.cjs");
10
12
  var _session = require("../session.cjs");
@@ -15,14 +17,10 @@ const getExternalIdpRedirect = exports.getExternalIdpRedirect = async function g
15
17
  return {};
16
18
  }
17
19
  if (context.idp.idpKeys.length === 0) {
18
- return new Response("No IDP keys are configured", {
19
- status: 400
20
- });
20
+ return new _errors.ErrorResponse(_constants.HttpStatusCode.BAD_REQUEST, _constants.HttpStatusMessage.BAD_REQUEST, "No IDP keys are configured");
21
21
  }
22
22
  if (!context.idp.idpRedirectURL) {
23
- return new Response("No IDP redirect url is configured", {
24
- status: 400
25
- });
23
+ return new _errors.ErrorResponse(_constants.HttpStatusCode.BAD_REQUEST, _constants.HttpStatusMessage.BAD_REQUEST, "No IDP redirect url is configured");
26
24
  }
27
25
  const OAuthClient = (0, _oauth.getOAuthClient)(context);
28
26
  const redirectUrl = new URL(context.idp.idpRedirectURL);
@@ -1,7 +1,8 @@
1
+ import { ErrorResponse } from '../../../errors';
1
2
  import type { RpcContext } from '../../../types';
2
3
  export declare const getExternalIdpRedirect: ({ queryParams }: {
3
4
  queryParams?: Record<string, string>;
4
- }, context: RpcContext) => Promise<Response | {
5
+ }, context: RpcContext) => Promise<ErrorResponse | {
5
6
  [k: string]: string;
6
7
  }>;
7
8
  export declare const handleIDPLoginCallback: (payload: {
@@ -1,4 +1,6 @@
1
1
  import { SignJWT } from "jose";
2
+ import { ErrorResponse } from "../../../errors/index.mjs";
3
+ import { HttpStatusCode, HttpStatusMessage } from "../../../constants/index.mjs";
2
4
  import { assertSession } from "../../../types/index.mjs";
3
5
  import { getOAuthClient } from "../../../api/oauth.mjs";
4
6
  import { postLogin } from "../session.mjs";
@@ -7,10 +9,18 @@ export const getExternalIdpRedirect = async function getExternalIdpRedirect2({ q
7
9
  return {};
8
10
  }
9
11
  if (context.idp.idpKeys.length === 0) {
10
- return new Response("No IDP keys are configured", { status: 400 });
12
+ return new ErrorResponse(
13
+ HttpStatusCode.BAD_REQUEST,
14
+ HttpStatusMessage.BAD_REQUEST,
15
+ "No IDP keys are configured"
16
+ );
11
17
  }
12
18
  if (!context.idp.idpRedirectURL) {
13
- return new Response("No IDP redirect url is configured", { status: 400 });
19
+ return new ErrorResponse(
20
+ HttpStatusCode.BAD_REQUEST,
21
+ HttpStatusMessage.BAD_REQUEST,
22
+ "No IDP redirect url is configured"
23
+ );
14
24
  }
15
25
  const OAuthClient = getOAuthClient(context);
16
26
  const redirectUrl = new URL(context.idp.idpRedirectURL);
@@ -8,6 +8,7 @@ var _helpers = require("../../helpers/index.cjs");
8
8
  var _constants = require("../../constants/index.cjs");
9
9
  var _cache = require("../../cache/index.cjs");
10
10
  var _response = require("../../utils/response.cjs");
11
+ var _errors = require("../../errors/index.cjs");
11
12
  const MAX_PER_PAGE = 100;
12
13
  const sanitizeAttributesForBAPI = ({
13
14
  attributes,
@@ -152,13 +153,17 @@ const getFilters = exports.getFilters = async function getFilters2({
152
153
  result = await cached(bapiClient.categories.getByPath, {
153
154
  cacheKeyPrefix: `getByPath-categories-${category}`
154
155
  })((0, _helpers.splitAndRemoveEmpty)(category));
155
- allFiltersForCategory = await (0, _response.unwrap)(fetchAllFiltersForCategory({
156
+ const response = await fetchAllFiltersForCategory({
156
157
  category: {
157
158
  id: result.id,
158
159
  slug: category
159
160
  },
160
161
  includedFilters
161
- }, context));
162
+ }, context);
163
+ if (response instanceof _errors.ErrorResponse) {
164
+ return response;
165
+ }
166
+ allFiltersForCategory = await (0, _response.unwrap)(response);
162
167
  }
163
168
  const sanitizedAttributes = sanitizeAttributesForBAPI({
164
169
  attributes: where?.attributes || [],
@@ -227,14 +232,18 @@ const getProductsByCategory = exports.getProductsByCategory = async function get
227
232
  cacheKeyPrefix: `getByPath-categories-${category}`
228
233
  })((0, _helpers.splitAndRemoveEmpty)(category));
229
234
  }
230
- const {
231
- filters
232
- } = await (0, _response.unwrap)(getFilters({
235
+ const getFiltersResponse = getFilters({
233
236
  category,
234
237
  includeSoldOut,
235
238
  where,
236
239
  includeSellableForFree
237
- }, context));
240
+ }, context);
241
+ if (getFiltersResponse instanceof _errors.ErrorResponse) {
242
+ return getFiltersResponse;
243
+ }
244
+ const {
245
+ filters
246
+ } = await (0, _response.unwrap)(getFiltersResponse);
238
247
  const {
239
248
  entities: products,
240
249
  pagination
@@ -1,5 +1,6 @@
1
1
  import type { FiltersEndpointResponseData } from '@scayle/storefront-api';
2
2
  import type { FetchFiltersParams, FetchProductParams, FetchProductsByCategoryParams, FetchProductsByIdsParams, FetchProductsByReferenceKeysParams, FetchProductsCountParams, Product, RpcContext } from '../../types';
3
+ import { ErrorResponse } from '../../errors';
3
4
  export declare const getProductById: (options: FetchProductParams, { bapiClient, cached, campaignKey, withParams }: RpcContext) => Promise<Product>;
4
5
  export declare const getProductsByIds: (options: FetchProductsByIdsParams, { bapiClient, cached, campaignKey, withParams }: RpcContext) => Promise<Product[]>;
5
6
  export declare const getProductsByReferenceKeys: (options: FetchProductsByReferenceKeysParams, { bapiClient, cached, campaignKey, withParams }: RpcContext) => Promise<Product[]>;
@@ -13,11 +14,11 @@ export declare const fetchAllFiltersForCategory: ({ includedFilters, category }:
13
14
  id: number;
14
15
  };
15
16
  }, { cached, bapiClient, campaignKey }: RpcContext) => Promise<string[]>;
16
- export declare const getFilters: ({ category, includedFilters, where, includeSoldOut, includeSellableForFree, orFiltersOperator, }: FetchFiltersParams, context: RpcContext) => Promise<{
17
+ export declare const getFilters: ({ category, includedFilters, where, includeSoldOut, includeSellableForFree, orFiltersOperator, }: FetchFiltersParams, context: RpcContext) => Promise<(string[] & ErrorResponse) | {
17
18
  filters: FiltersEndpointResponseData;
18
19
  unfilteredCount: number;
19
20
  }>;
20
- export declare const getProductsByCategory: ({ category, cache, with: _with, perPage, page, where, sort, pricePromotionKey, includeSellableForFree, includeSoldOut, orFiltersOperator, }: FetchProductsByCategoryParams, context: RpcContext) => Promise<{
21
+ export declare const getProductsByCategory: ({ category, cache, with: _with, perPage, page, where, sort, pricePromotionKey, includeSellableForFree, includeSoldOut, orFiltersOperator, }: FetchProductsByCategoryParams, context: RpcContext) => Promise<ErrorResponse | {
21
22
  products: Product[];
22
23
  pagination: {
23
24
  current: number;
@@ -2,6 +2,7 @@ import { splitAndRemoveEmpty } from "../../helpers/index.mjs";
2
2
  import { MIN_WITH_PARAMS_PRODUCT } from "../../constants/index.mjs";
3
3
  import { MINUTE } from "../../cache/index.mjs";
4
4
  import { unwrap } from "../../utils/response.mjs";
5
+ import { ErrorResponse } from "../../errors/index.mjs";
5
6
  const MAX_PER_PAGE = 100;
6
7
  const sanitizeAttributesForBAPI = ({
7
8
  attributes,
@@ -112,12 +113,14 @@ export const getFilters = async function getFilters2({
112
113
  result = await cached(bapiClient.categories.getByPath, {
113
114
  cacheKeyPrefix: `getByPath-categories-${category}`
114
115
  })(splitAndRemoveEmpty(category));
115
- allFiltersForCategory = await unwrap(
116
- fetchAllFiltersForCategory(
117
- { category: { id: result.id, slug: category }, includedFilters },
118
- context
119
- )
116
+ const response = await fetchAllFiltersForCategory(
117
+ { category: { id: result.id, slug: category }, includedFilters },
118
+ context
120
119
  );
120
+ if (response instanceof ErrorResponse) {
121
+ return response;
122
+ }
123
+ allFiltersForCategory = await unwrap(response);
121
124
  }
122
125
  const sanitizedAttributes = sanitizeAttributesForBAPI({
123
126
  attributes: where?.attributes || [],
@@ -191,12 +194,14 @@ export const getProductsByCategory = async function getProductsByCategory2({
191
194
  cacheKeyPrefix: `getByPath-categories-${category}`
192
195
  })(splitAndRemoveEmpty(category));
193
196
  }
194
- const { filters } = await unwrap(
195
- getFilters(
196
- { category, includeSoldOut, where, includeSellableForFree },
197
- context
198
- )
197
+ const getFiltersResponse = getFilters(
198
+ { category, includeSoldOut, where, includeSellableForFree },
199
+ context
199
200
  );
201
+ if (getFiltersResponse instanceof ErrorResponse) {
202
+ return getFiltersResponse;
203
+ }
204
+ const { filters } = await unwrap(getFiltersResponse);
200
205
  const { entities: products, pagination } = await cached(
201
206
  bapiClient.products.query,
202
207
  {
@@ -14,17 +14,20 @@ var _user = require("../../utils/user.cjs");
14
14
  var _user2 = require("../../rpc/methods/user.cjs");
15
15
  var _response = require("../../utils/response.cjs");
16
16
  var _oauth = require("../../api/oauth.cjs");
17
+ var _errors = require("../../errors/index.cjs");
17
18
  const convertErrorForRpcCall = (error, httpStatuses) => {
18
19
  if (error instanceof _fetch.FetchError && httpStatuses.includes(error.response.status)) {
19
- return new Response(null, {
20
- status: error.response.status
21
- });
20
+ return new _errors.ErrorResponse(error.response.status, error.response.statusText, error.name);
22
21
  }
23
22
  };
24
23
  async function postLogin(context, tokens) {
25
- const user = await (0, _response.unwrap)((0, _user2.fetchUser)({
24
+ const fetchUserResponse = await (0, _user2.fetchUser)({
26
25
  accessToken: tokens.access_token
27
- }, context));
26
+ }, context);
27
+ if (fetchUserResponse instanceof _errors.ErrorResponse) {
28
+ return fetchUserResponse;
29
+ }
30
+ const user = await (0, _response.unwrap)(fetchUserResponse);
28
31
  context.updateTokens({
29
32
  accessToken: tokens.access_token,
30
33
  refreshToken: tokens.refresh_token
@@ -47,24 +50,24 @@ const oauthLogin = async (login, context) => {
47
50
  const shopId = context.shopId;
48
51
  const client = (0, _oauth.getOAuthClient)(context);
49
52
  if (!login.email || !login.password) {
50
- throw new Error("Login or password are missing, seems like validation has failed");
53
+ return new _errors.ErrorResponse(_constants.HttpStatusCode.BAD_REQUEST, _constants.HttpStatusMessage.BAD_REQUEST, "Login or password are missing");
51
54
  }
52
55
  try {
53
56
  const tokens = await client.login({
54
57
  ...login,
55
58
  shop_id: shopId
56
59
  });
57
- await postLogin(context, tokens);
58
- } catch (error) {
59
- const res = convertErrorForRpcCall(error, [_constants.HttpStatusCode.INTERNAL_SERVER_ERROR, _constants.HttpStatusCode.BAD_REQUEST, _constants.HttpStatusCode.UNAUTHORIZED, _constants.HttpStatusCode.NOT_FOUND, _constants.HttpStatusCode.FORBIDDEN]);
60
- if (res) {
61
- return res;
60
+ const postLoginResponse = await postLogin(context, tokens);
61
+ if (postLoginResponse instanceof _errors.ErrorResponse) {
62
+ return postLoginResponse;
62
63
  }
63
- throw error;
64
+ return new Response(null, {
65
+ status: _constants.HttpStatusCode.NO_CONTENT
66
+ });
67
+ } catch (error) {
68
+ context.log.error("OAuthClient.login failed", error);
69
+ return new _errors.ErrorResponse(_constants.HttpStatusCode.INTERNAL_SERVER_ERROR, _constants.HttpStatusMessage.INTERNAL_SERVER_ERROR, "Login failed");
64
70
  }
65
- return new Response(null, {
66
- status: _constants.HttpStatusCode.NO_CONTENT
67
- });
68
71
  };
69
72
  exports.oauthLogin = oauthLogin;
70
73
  const oauthRegister = async (register, context) => {
@@ -76,17 +79,17 @@ const oauthRegister = async (register, context) => {
76
79
  ...register,
77
80
  shop_id: shopId
78
81
  });
79
- await postLogin(context, tokens);
80
- } catch (error) {
81
- const res = convertErrorForRpcCall(error, [_constants.HttpStatusCode.BAD_REQUEST, _constants.HttpStatusCode.UNAUTHORIZED, _constants.HttpStatusCode.CONFLICT, _constants.HttpStatusCode.FORBIDDEN]);
82
- if (res) {
83
- return res;
82
+ const postLoginResponse = await postLogin(context, tokens);
83
+ if (postLoginResponse instanceof _errors.ErrorResponse) {
84
+ return postLoginResponse;
84
85
  }
85
- throw error;
86
+ return new Response(null, {
87
+ status: _constants.HttpStatusCode.NO_CONTENT
88
+ });
89
+ } catch (error) {
90
+ context.log.error("OAuthClient.register failed", error);
91
+ return new _errors.ErrorResponse(_constants.HttpStatusCode.INTERNAL_SERVER_ERROR, _constants.HttpStatusMessage.INTERNAL_SERVER_ERROR, "Register failed");
86
92
  }
87
- return new Response(null, {
88
- status: _constants.HttpStatusCode.NO_CONTENT
89
- });
90
93
  };
91
94
  exports.oauthRegister = oauthRegister;
92
95
  const oauthGuestLogin = async (guest, context) => {
@@ -98,17 +101,17 @@ const oauthGuestLogin = async (guest, context) => {
98
101
  ...guest,
99
102
  shop_id: shopId
100
103
  });
101
- await postLogin(context, tokens);
102
- } catch (error) {
103
- const res = convertErrorForRpcCall(error, [_constants.HttpStatusCode.BAD_REQUEST, _constants.HttpStatusCode.UNAUTHORIZED, _constants.HttpStatusCode.CONFLICT, _constants.HttpStatusCode.FORBIDDEN]);
104
- if (res) {
105
- return res;
104
+ const postLoginResponse = await postLogin(context, tokens);
105
+ if (postLoginResponse instanceof _errors.ErrorResponse) {
106
+ return postLoginResponse;
106
107
  }
107
- throw error;
108
+ return new Response(null, {
109
+ status: _constants.HttpStatusCode.NO_CONTENT
110
+ });
111
+ } catch (error) {
112
+ context.log.error("OAuthClient.guestLogin failed", error);
113
+ return new _errors.ErrorResponse(_constants.HttpStatusCode.INTERNAL_SERVER_ERROR, _constants.HttpStatusMessage.INTERNAL_SERVER_ERROR, "Guest login failed");
108
114
  }
109
- return new Response(null, {
110
- status: _constants.HttpStatusCode.NO_CONTENT
111
- });
112
115
  };
113
116
  exports.oauthGuestLogin = oauthGuestLogin;
114
117
  const refreshAccessToken = async context => {
@@ -116,7 +119,7 @@ const refreshAccessToken = async context => {
116
119
  const refreshToken = context.refreshToken;
117
120
  const client = (0, _oauth.getOAuthClient)(context);
118
121
  if (!refreshToken) {
119
- throw new Error("No app refresh token provided");
122
+ return new _errors.ErrorResponse(_constants.HttpStatusCode.BAD_REQUEST, _constants.HttpStatusMessage.BAD_REQUEST, "No refresh token present");
120
123
  }
121
124
  try {
122
125
  const tokens = await client.refreshToken({
@@ -145,7 +148,7 @@ const oauthRevokeToken = async context => {
145
148
  (0, _types.assertSession)(context);
146
149
  const accessToken = context.accessToken;
147
150
  if (!accessToken) {
148
- throw new Error("No app oauth authentication credentials");
151
+ return new _errors.ErrorResponse(_constants.HttpStatusCode.UNAUTHORIZED, _constants.HttpStatusMessage.UNAUTHORIZED, "No access token present");
149
152
  }
150
153
  const user = context.user;
151
154
  const client = (0, _oauth.getOAuthClient)(context);
@@ -179,7 +182,7 @@ const oauthForgetPassword = async ({
179
182
  const client = (0, _oauth.getOAuthClient)(context);
180
183
  try {
181
184
  if (!context.auth.resetPasswordUrl) {
182
- throw new Error("Missing password reset URL");
185
+ return new _errors.ErrorResponse(_constants.HttpStatusCode.INTERNAL_SERVER_ERROR, _constants.HttpStatusMessage.INTERNAL_SERVER_ERROR, "Missing password reset URL");
183
186
  }
184
187
  const resetUrl = new URL(context.auth.resetPasswordUrl);
185
188
  if (!resetUrl.searchParams.has("hash")) {
@@ -225,7 +228,7 @@ const updatePasswordByHash = async (passwordHash, context) => {
225
228
  if (res) {
226
229
  return res;
227
230
  }
228
- throw error;
231
+ return new _errors.ErrorResponse(_constants.HttpStatusCode.INTERNAL_SERVER_ERROR, _constants.HttpStatusMessage.INTERNAL_SERVER_ERROR, "Error during update password by hash");
229
232
  }
230
233
  return new Response(null, {
231
234
  status: _constants.HttpStatusCode.NO_CONTENT
@@ -1,6 +1,7 @@
1
1
  import type { Optional } from 'utility-types';
2
- import type { GuestRequest, LoginRequest, Oauth, RegisterRequest, RpcContext, RpcContextWithSession, UpdatePasswordByHashRequest } from '../../types';
3
- export declare function postLogin(context: RpcContextWithSession, tokens: Oauth): Promise<void>;
2
+ import type { GuestRequest, LoginRequest, Oauth, RegisterRequest, RpcContext, RpcContextWithSession, ShopUser, UpdatePasswordByHashRequest } from '../../types';
3
+ import { ErrorResponse } from '../../errors';
4
+ export declare function postLogin(context: RpcContextWithSession, tokens: Oauth): Promise<(ShopUser & ErrorResponse) | undefined>;
4
5
  export declare const oauthLogin: (login: Optional<LoginRequest, "shop_id">, context: RpcContext) => Promise<Response>;
5
6
  export declare const oauthRegister: (register: Optional<RegisterRequest, "shop_id">, context: RpcContext) => Promise<Response>;
6
7
  export declare const oauthGuestLogin: (guest: Optional<GuestRequest, "shop_id">, context: RpcContext) => Promise<Response>;
@@ -1,20 +1,34 @@
1
1
  import { decodeJwt } from "jose";
2
2
  import { assertSession } from "../../types/index.mjs";
3
- import { DEFAULT_WITH_LISTING, HttpStatusCode } from "../../constants/index.mjs";
3
+ import {
4
+ DEFAULT_WITH_LISTING,
5
+ HttpStatusCode,
6
+ HttpStatusMessage
7
+ } from "../../constants/index.mjs";
4
8
  import { FetchError } from "../../utils/fetch.mjs";
5
9
  import { mergeBaskets, mergeWishlists } from "../../utils/user.mjs";
6
10
  import { fetchUser } from "../../rpc/methods/user.mjs";
7
11
  import { unwrap } from "../../utils/response.mjs";
8
12
  import { getOAuthClient } from "../../api/oauth.mjs";
13
+ import { ErrorResponse } from "../../errors/index.mjs";
9
14
  const convertErrorForRpcCall = (error, httpStatuses) => {
10
15
  if (error instanceof FetchError && httpStatuses.includes(error.response.status)) {
11
- return new Response(null, { status: error.response.status });
16
+ return new ErrorResponse(
17
+ error.response.status,
18
+ error.response.statusText,
19
+ error.name
20
+ );
12
21
  }
13
22
  };
14
23
  export async function postLogin(context, tokens) {
15
- const user = await unwrap(
16
- fetchUser({ accessToken: tokens.access_token }, context)
24
+ const fetchUserResponse = await fetchUser(
25
+ { accessToken: tokens.access_token },
26
+ context
17
27
  );
28
+ if (fetchUserResponse instanceof ErrorResponse) {
29
+ return fetchUserResponse;
30
+ }
31
+ const user = await unwrap(fetchUserResponse);
18
32
  context.updateTokens({
19
33
  accessToken: tokens.access_token,
20
34
  refreshToken: tokens.refresh_token
@@ -48,27 +62,27 @@ export const oauthLogin = async (login, context) => {
48
62
  const shopId = context.shopId;
49
63
  const client = getOAuthClient(context);
50
64
  if (!login.email || !login.password) {
51
- throw new Error(
52
- "Login or password are missing, seems like validation has failed"
65
+ return new ErrorResponse(
66
+ HttpStatusCode.BAD_REQUEST,
67
+ HttpStatusMessage.BAD_REQUEST,
68
+ "Login or password are missing"
53
69
  );
54
70
  }
55
71
  try {
56
72
  const tokens = await client.login({ ...login, shop_id: shopId });
57
- await postLogin(context, tokens);
73
+ const postLoginResponse = await postLogin(context, tokens);
74
+ if (postLoginResponse instanceof ErrorResponse) {
75
+ return postLoginResponse;
76
+ }
77
+ return new Response(null, { status: HttpStatusCode.NO_CONTENT });
58
78
  } catch (error) {
59
- const res = convertErrorForRpcCall(error, [
79
+ context.log.error("OAuthClient.login failed", error);
80
+ return new ErrorResponse(
60
81
  HttpStatusCode.INTERNAL_SERVER_ERROR,
61
- HttpStatusCode.BAD_REQUEST,
62
- HttpStatusCode.UNAUTHORIZED,
63
- HttpStatusCode.NOT_FOUND,
64
- HttpStatusCode.FORBIDDEN
65
- ]);
66
- if (res) {
67
- return res;
68
- }
69
- throw error;
82
+ HttpStatusMessage.INTERNAL_SERVER_ERROR,
83
+ "Login failed"
84
+ );
70
85
  }
71
- return new Response(null, { status: HttpStatusCode.NO_CONTENT });
72
86
  };
73
87
  export const oauthRegister = async (register, context) => {
74
88
  assertSession(context);
@@ -79,20 +93,19 @@ export const oauthRegister = async (register, context) => {
79
93
  ...register,
80
94
  shop_id: shopId
81
95
  });
82
- await postLogin(context, tokens);
83
- } catch (error) {
84
- const res = convertErrorForRpcCall(error, [
85
- HttpStatusCode.BAD_REQUEST,
86
- HttpStatusCode.UNAUTHORIZED,
87
- HttpStatusCode.CONFLICT,
88
- HttpStatusCode.FORBIDDEN
89
- ]);
90
- if (res) {
91
- return res;
96
+ const postLoginResponse = await postLogin(context, tokens);
97
+ if (postLoginResponse instanceof ErrorResponse) {
98
+ return postLoginResponse;
92
99
  }
93
- throw error;
100
+ return new Response(null, { status: HttpStatusCode.NO_CONTENT });
101
+ } catch (error) {
102
+ context.log.error("OAuthClient.register failed", error);
103
+ return new ErrorResponse(
104
+ HttpStatusCode.INTERNAL_SERVER_ERROR,
105
+ HttpStatusMessage.INTERNAL_SERVER_ERROR,
106
+ "Register failed"
107
+ );
94
108
  }
95
- return new Response(null, { status: HttpStatusCode.NO_CONTENT });
96
109
  };
97
110
  export const oauthGuestLogin = async (guest, context) => {
98
111
  assertSession(context);
@@ -103,27 +116,30 @@ export const oauthGuestLogin = async (guest, context) => {
103
116
  ...guest,
104
117
  shop_id: shopId
105
118
  });
106
- await postLogin(context, tokens);
107
- } catch (error) {
108
- const res = convertErrorForRpcCall(error, [
109
- HttpStatusCode.BAD_REQUEST,
110
- HttpStatusCode.UNAUTHORIZED,
111
- HttpStatusCode.CONFLICT,
112
- HttpStatusCode.FORBIDDEN
113
- ]);
114
- if (res) {
115
- return res;
119
+ const postLoginResponse = await postLogin(context, tokens);
120
+ if (postLoginResponse instanceof ErrorResponse) {
121
+ return postLoginResponse;
116
122
  }
117
- throw error;
123
+ return new Response(null, { status: HttpStatusCode.NO_CONTENT });
124
+ } catch (error) {
125
+ context.log.error("OAuthClient.guestLogin failed", error);
126
+ return new ErrorResponse(
127
+ HttpStatusCode.INTERNAL_SERVER_ERROR,
128
+ HttpStatusMessage.INTERNAL_SERVER_ERROR,
129
+ "Guest login failed"
130
+ );
118
131
  }
119
- return new Response(null, { status: HttpStatusCode.NO_CONTENT });
120
132
  };
121
133
  export const refreshAccessToken = async (context) => {
122
134
  assertSession(context);
123
135
  const refreshToken = context.refreshToken;
124
136
  const client = getOAuthClient(context);
125
137
  if (!refreshToken) {
126
- throw new Error("No app refresh token provided");
138
+ return new ErrorResponse(
139
+ HttpStatusCode.BAD_REQUEST,
140
+ HttpStatusMessage.BAD_REQUEST,
141
+ "No refresh token present"
142
+ );
127
143
  }
128
144
  try {
129
145
  const tokens = await client.refreshToken({
@@ -150,7 +166,11 @@ export const oauthRevokeToken = async (context) => {
150
166
  assertSession(context);
151
167
  const accessToken = context.accessToken;
152
168
  if (!accessToken) {
153
- throw new Error("No app oauth authentication credentials");
169
+ return new ErrorResponse(
170
+ HttpStatusCode.UNAUTHORIZED,
171
+ HttpStatusMessage.UNAUTHORIZED,
172
+ "No access token present"
173
+ );
154
174
  }
155
175
  const user = context.user;
156
176
  const client = getOAuthClient(context);
@@ -181,7 +201,11 @@ export const oauthForgetPassword = async ({ email }, context) => {
181
201
  const client = getOAuthClient(context);
182
202
  try {
183
203
  if (!context.auth.resetPasswordUrl) {
184
- throw new Error("Missing password reset URL");
204
+ return new ErrorResponse(
205
+ HttpStatusCode.INTERNAL_SERVER_ERROR,
206
+ HttpStatusMessage.INTERNAL_SERVER_ERROR,
207
+ "Missing password reset URL"
208
+ );
185
209
  }
186
210
  const resetUrl = new URL(context.auth.resetPasswordUrl);
187
211
  if (!resetUrl.searchParams.has("hash")) {
@@ -231,7 +255,11 @@ export const updatePasswordByHash = async (passwordHash, context) => {
231
255
  if (res) {
232
256
  return res;
233
257
  }
234
- throw error;
258
+ return new ErrorResponse(
259
+ HttpStatusCode.INTERNAL_SERVER_ERROR,
260
+ HttpStatusMessage.INTERNAL_SERVER_ERROR,
261
+ "Error during update password by hash"
262
+ );
235
263
  }
236
264
  return new Response(null, { status: HttpStatusCode.NO_CONTENT });
237
265
  };
@@ -7,6 +7,7 @@ exports.removeItemFromWishlist = exports.getWishlist = exports.clearWishlist = e
7
7
  var _types = require("../../types/index.cjs");
8
8
  var _constants = require("../../constants/index.cjs");
9
9
  var _response = require("../../utils/response.cjs");
10
+ var _errors = require("../../errors/index.cjs");
10
11
  function getWithParams(params, context) {
11
12
  return params.with ?? context.withParams?.wishlist ?? _constants.MIN_WITH_PARAMS_WISHLIST;
12
13
  }
@@ -40,7 +41,7 @@ const addItemToWishlist = exports.addItemToWishlist = async function addItemToWi
40
41
  const resolvedWith = getWithParams(options, context);
41
42
  const pricePromotionKey = resolvedWith?.pricePromotionKey ?? "";
42
43
  if (!productId && !variantId) {
43
- throw new Error("No productId or variantId given");
44
+ return new _errors.ErrorResponse(_constants.HttpStatusCode.BAD_REQUEST, _constants.HttpStatusMessage.BAD_REQUEST, "No productId or variantId given");
44
45
  }
45
46
  const result = await bapiClient.wishlist.addItem(wishlistKey, variantId ? {
46
47
  variantId
@@ -82,7 +83,11 @@ const removeItemFromWishlist = exports.removeItemFromWishlist = async function r
82
83
  });
83
84
  };
84
85
  const clearWishlist = exports.clearWishlist = async function clearWishlist2(context) {
85
- const wishlist = await (0, _response.unwrap)(getWishlist({}, context));
86
+ const wishlistResponse = await getWishlist({}, context);
87
+ if (wishlistResponse instanceof _errors.ErrorResponse) {
88
+ return wishlistResponse;
89
+ }
90
+ const wishlist = await (0, _response.unwrap)(wishlistResponse);
86
91
  await Promise.all(wishlist.items.map(async item => {
87
92
  await removeItemFromWishlist({
88
93
  itemKey: item.key
@@ -1,6 +1,11 @@
1
1
  import { assertSession } from "../../types/index.mjs";
2
- import { MIN_WITH_PARAMS_WISHLIST } from "../../constants/index.mjs";
2
+ import {
3
+ HttpStatusCode,
4
+ HttpStatusMessage,
5
+ MIN_WITH_PARAMS_WISHLIST
6
+ } from "../../constants/index.mjs";
3
7
  import { unwrap } from "../../utils/response.mjs";
8
+ import { ErrorResponse } from "../../errors/index.mjs";
4
9
  function getWithParams(params, context) {
5
10
  return params.with ?? context.withParams?.wishlist ?? MIN_WITH_PARAMS_WISHLIST;
6
11
  }
@@ -21,7 +26,11 @@ export const addItemToWishlist = async function addItemToWishlist2(options, cont
21
26
  const resolvedWith = getWithParams(options, context);
22
27
  const pricePromotionKey = resolvedWith?.pricePromotionKey ?? "";
23
28
  if (!productId && !variantId) {
24
- throw new Error("No productId or variantId given");
29
+ return new ErrorResponse(
30
+ HttpStatusCode.BAD_REQUEST,
31
+ HttpStatusMessage.BAD_REQUEST,
32
+ "No productId or variantId given"
33
+ );
25
34
  }
26
35
  const result = await bapiClient.wishlist.addItem(
27
36
  wishlistKey,
@@ -50,7 +59,11 @@ export const removeItemFromWishlist = async function removeItemFromWishlist2(opt
50
59
  });
51
60
  };
52
61
  export const clearWishlist = async function clearWishlist2(context) {
53
- const wishlist = await unwrap(getWishlist({}, context));
62
+ const wishlistResponse = await getWishlist({}, context);
63
+ if (wishlistResponse instanceof ErrorResponse) {
64
+ return wishlistResponse;
65
+ }
66
+ const wishlist = await unwrap(wishlistResponse);
54
67
  await Promise.all(
55
68
  wishlist.items.map(async (item) => {
56
69
  await removeItemFromWishlist({ itemKey: item.key }, context);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scayle/storefront-core",
3
- "version": "7.59.2",
3
+ "version": "7.60.1",
4
4
  "description": "Collection of essential utilities to work with the Storefront API",
5
5
  "author": "SCAYLE Commerce Engine",
6
6
  "license": "MIT",
@@ -59,7 +59,7 @@
59
59
  "test:ci": "vitest --run --passWithNoTests --coverage --reporter=default --reporter=junit"
60
60
  },
61
61
  "dependencies": {
62
- "@scayle/storefront-api": "17.4.0",
62
+ "@scayle/storefront-api": "17.4.2",
63
63
  "crypto-js": "^4.2.0",
64
64
  "hookable": "^5.5.3",
65
65
  "jose": "^5.6.3",
@@ -72,13 +72,13 @@
72
72
  "devDependencies": {
73
73
  "@scayle/eslint-config-storefront": "4.2.0",
74
74
  "@types/crypto-js": "4.2.2",
75
- "@types/node": "20.14.10",
75
+ "@types/node": "20.14.11",
76
76
  "@types/webpack-env": "1.18.5",
77
77
  "@vitest/coverage-v8": "2.0.3",
78
78
  "dprint": "0.47.2",
79
79
  "eslint": "9.7.0",
80
80
  "eslint-formatter-gitlab": "5.1.0",
81
- "publint": "0.2.8",
81
+ "publint": "0.2.9",
82
82
  "rimraf": "6.0.1",
83
83
  "ts-node": "10.9.2",
84
84
  "typescript": "5.5.3",