@scayle/storefront-core 7.46.1 → 7.48.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,31 @@
1
1
  # @scayle/storefront-core
2
2
 
3
+ ## 7.48.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add support for dynamically adding query parameters to the IDP redirect callback URLs to be read back when the user returns from the IDP.
8
+
9
+ ```ts
10
+ const { data: externalIDPRedirects } = await useIDP({
11
+ queryParams: { redirectTo: '/account' },
12
+ })
13
+ ```
14
+
15
+ Please note that `code` and `state` are not supported as these are used by the SCAYLE Authentication API.
16
+
17
+ ### Patch Changes
18
+
19
+ - Added merge basket and merge wishlist functionality after login with IDP
20
+ - Updated dependencies
21
+ - @aboutyou/backbone@16.2.1
22
+
23
+ ## 7.47.0
24
+
25
+ ### Minor Changes
26
+
27
+ - Add `resolveSearch` and `getSearchSuggestions` RPC methods
28
+
3
29
  ## 7.46.1
4
30
 
5
31
  ### Patch Changes
@@ -1,7 +1,4 @@
1
- import type { BasketWith } from '@aboutyou/backbone/endpoints/basket/getBasket';
2
- import type { ProductWith } from '@aboutyou/backbone/types/ProductWith';
3
- import type { WishlistWith } from '@aboutyou/backbone/endpoints/wishlist/getWishlist';
4
- import { SearchWith } from '../types';
1
+ import type { SearchWith, BasketWith, ProductWith, WishlistWith } from '../types';
5
2
  declare const DEFAULT_WITH_LISTING: {
6
3
  items: {
7
4
  product: {
@@ -7,36 +7,44 @@ exports.handleIDPLoginCallback = exports.getExternalIdpRedirect = void 0;
7
7
  var _jose = require("jose");
8
8
  var _types = require("../../../types/index.cjs");
9
9
  var _oauth = require("../../../api/oauth.cjs");
10
- var _user = require("../user.cjs");
11
- const getExternalIdpRedirect = exports.getExternalIdpRedirect = async function getExternalIdpRedirect2(context) {
12
- const shopId = context.shopId.toString();
13
- const OAuthClient = (0, _oauth.getOAuthClient)(context);
14
- const checkoutSecret = context.checkout.secret;
10
+ var _session = require("../session.cjs");
11
+ const getExternalIdpRedirect = exports.getExternalIdpRedirect = async function getExternalIdpRedirect2({
12
+ queryParams
13
+ }, context) {
15
14
  if (!context.idp?.enabled) {
16
15
  return {};
17
16
  }
18
- const IDPKeys = context.idp.idpKeys;
19
- const IDPRedirectURL = context.idp.idpRedirectURL;
20
- if (!IDPKeys.length) {
21
- throw new Error("No IDP keys configured");
17
+ if (context.idp.idpKeys.length === 0) {
18
+ return new Response("No IDP keys are configured", {
19
+ status: 400
20
+ });
22
21
  }
23
- if (!IDPRedirectURL) {
24
- throw new Error("No redirect URL");
22
+ if (!context.idp.idpRedirectURL) {
23
+ return new Response("No IDP redirect url is configured", {
24
+ status: 400
25
+ });
25
26
  }
26
- if (!shopId) {
27
- throw new Error("Missing shopId");
27
+ const OAuthClient = (0, _oauth.getOAuthClient)(context);
28
+ const redirectUrl = new URL(context.idp.idpRedirectURL);
29
+ if (queryParams) {
30
+ for (const [key, value] of Object.entries(queryParams)) {
31
+ redirectUrl.searchParams.set(key, value);
32
+ }
28
33
  }
29
- const secret = new TextEncoder().encode(checkoutSecret);
30
- const results = await Promise.all(IDPKeys.map(async idpKey => {
34
+ const secret = new TextEncoder().encode(context.checkout.secret);
35
+ const results = await Promise.all(context.idp.idpKeys.map(async idpKey => {
31
36
  const jwtPayload = await new _jose.SignJWT({
32
37
  idpKey,
33
- callbackUrl: IDPRedirectURL,
38
+ callbackUrl: redirectUrl.toString(),
34
39
  clientId: OAuthClient.clientId.toString()
35
40
  }).setProtectedHeader({
36
41
  alg: "HS256",
37
42
  typ: "JWT"
38
43
  }).setIssuedAt().setExpirationTime("2h").sign(secret);
39
- return [idpKey, `${OAuthClient.baseURL}/auth/external/redirect?shopId=${shopId}&jwt=${jwtPayload}`];
44
+ const url = new URL(`${OAuthClient.baseURL}/auth/external/redirect`);
45
+ url.searchParams.set("shopId", `${context.shopId}`);
46
+ url.searchParams.set("jwt", jwtPayload);
47
+ return [idpKey, url.toString()];
40
48
  }));
41
49
  return Object.fromEntries(results);
42
50
  };
@@ -44,15 +52,8 @@ const handleIDPLoginCallback = exports.handleIDPLoginCallback = async function h
44
52
  (0, _types.assertSession)(context);
45
53
  const OAuthClient = (0, _oauth.getOAuthClient)(context);
46
54
  const code = typeof payload === "string" ? payload : payload.code;
47
- const {
48
- access_token: accessToken,
49
- refresh_token: refreshToken
50
- } = await OAuthClient.generateToken(code);
51
- context.updateTokens({
52
- accessToken,
53
- refreshToken
54
- });
55
- await (0, _user.refreshUser)(context);
55
+ const tokens = await OAuthClient.generateToken(code);
56
+ await (0, _session.postLogin)(context, tokens);
56
57
  return {
57
58
  message: "success"
58
59
  };
@@ -1,9 +1,11 @@
1
1
  import type { RpcContext } from '../../../types';
2
- export declare const getExternalIdpRedirect: (context: RpcContext) => Promise<{
2
+ export declare const getExternalIdpRedirect: ({ queryParams }: {
3
+ queryParams?: Record<string, string>;
4
+ }, context: RpcContext) => Promise<Response | {
3
5
  [k: string]: string;
4
6
  }>;
5
- export declare const handleIDPLoginCallback: (payload: string | {
7
+ export declare const handleIDPLoginCallback: (payload: {
6
8
  code: string;
7
- }, context: RpcContext) => Promise<{
9
+ } | string, context: RpcContext) => Promise<{
8
10
  message: string;
9
11
  }>;
@@ -1,37 +1,36 @@
1
1
  import { SignJWT } from "jose";
2
2
  import { assertSession } from "../../../types/index.mjs";
3
3
  import { getOAuthClient } from "../../../api/oauth.mjs";
4
- import { refreshUser } from "../user.mjs";
5
- export const getExternalIdpRedirect = async function getExternalIdpRedirect2(context) {
6
- const shopId = context.shopId.toString();
7
- const OAuthClient = getOAuthClient(context);
8
- const checkoutSecret = context.checkout.secret;
4
+ import { postLogin } from "../session.mjs";
5
+ export const getExternalIdpRedirect = async function getExternalIdpRedirect2({ queryParams }, context) {
9
6
  if (!context.idp?.enabled) {
10
7
  return {};
11
8
  }
12
- const IDPKeys = context.idp.idpKeys;
13
- const IDPRedirectURL = context.idp.idpRedirectURL;
14
- if (!IDPKeys.length) {
15
- throw new Error("No IDP keys configured");
9
+ if (context.idp.idpKeys.length === 0) {
10
+ return new Response("No IDP keys are configured", { status: 400 });
16
11
  }
17
- if (!IDPRedirectURL) {
18
- throw new Error("No redirect URL");
12
+ if (!context.idp.idpRedirectURL) {
13
+ return new Response("No IDP redirect url is configured", { status: 400 });
19
14
  }
20
- if (!shopId) {
21
- throw new Error("Missing shopId");
15
+ const OAuthClient = getOAuthClient(context);
16
+ const redirectUrl = new URL(context.idp.idpRedirectURL);
17
+ if (queryParams) {
18
+ for (const [key, value] of Object.entries(queryParams)) {
19
+ redirectUrl.searchParams.set(key, value);
20
+ }
22
21
  }
23
- const secret = new TextEncoder().encode(checkoutSecret);
22
+ const secret = new TextEncoder().encode(context.checkout.secret);
24
23
  const results = await Promise.all(
25
- IDPKeys.map(async (idpKey) => {
24
+ context.idp.idpKeys.map(async (idpKey) => {
26
25
  const jwtPayload = await new SignJWT({
27
26
  idpKey,
28
- callbackUrl: IDPRedirectURL,
27
+ callbackUrl: redirectUrl.toString(),
29
28
  clientId: OAuthClient.clientId.toString()
30
29
  }).setProtectedHeader({ alg: "HS256", typ: "JWT" }).setIssuedAt().setExpirationTime("2h").sign(secret);
31
- return [
32
- idpKey,
33
- `${OAuthClient.baseURL}/auth/external/redirect?shopId=${shopId}&jwt=${jwtPayload}`
34
- ];
30
+ const url = new URL(`${OAuthClient.baseURL}/auth/external/redirect`);
31
+ url.searchParams.set("shopId", `${context.shopId}`);
32
+ url.searchParams.set("jwt", jwtPayload);
33
+ return [idpKey, url.toString()];
35
34
  })
36
35
  );
37
36
  return Object.fromEntries(results);
@@ -40,12 +39,8 @@ export const handleIDPLoginCallback = async function handleIDPLoginCallback2(pay
40
39
  assertSession(context);
41
40
  const OAuthClient = getOAuthClient(context);
42
41
  const code = typeof payload === "string" ? payload : payload.code;
43
- const { access_token: accessToken, refresh_token: refreshToken } = await OAuthClient.generateToken(code);
44
- context.updateTokens({
45
- accessToken,
46
- refreshToken
47
- });
48
- await refreshUser(context);
42
+ const tokens = await OAuthClient.generateToken(code);
43
+ await postLogin(context, tokens);
49
44
  return {
50
45
  message: "success"
51
46
  };
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.searchProducts = void 0;
6
+ exports.searchProducts = exports.resolveSearch = exports.getSearchSuggestions = void 0;
7
7
  var _constants = require("../../constants/index.cjs");
8
8
  var _stringHelpers = require("../../helpers/stringHelpers.cjs");
9
9
  const searchProducts = exports.searchProducts = async function searchProducts2({
@@ -25,6 +25,39 @@ const searchProducts = exports.searchProducts = async function searchProducts2({
25
25
  with: _with ?? withParams?.search ?? _constants.MIN_WITH_PARAMS_SEARCH
26
26
  });
27
27
  };
28
+ const getSearchSuggestions = exports.getSearchSuggestions = async function getSearchSuggestions2({
29
+ term,
30
+ with: _with,
31
+ categoryId
32
+ }, {
33
+ bapiClient,
34
+ withParams
35
+ }) {
36
+ return await bapiClient.searchv2.suggestions(term, {
37
+ categoryId,
38
+ with: _with ?? withParams?.searchV2
39
+ });
40
+ };
41
+ const resolveSearch = exports.resolveSearch = async function resolveSearch2({
42
+ term,
43
+ with: _with,
44
+ categoryId
45
+ }, {
46
+ cached,
47
+ bapiClient,
48
+ withParams
49
+ }) {
50
+ const resolvedEntity = await cached(bapiClient.searchv2.resolve, {
51
+ cacheKeyPrefix: `resolve-${term}`
52
+ })(term, {
53
+ categoryId,
54
+ with: _with ?? withParams?.searchV2
55
+ });
56
+ if (!resolvedEntity) {
57
+ return null;
58
+ }
59
+ return resolvedEntity;
60
+ };
28
61
  const getCategoryId = async (cached, bapiClient, slug) => {
29
62
  if (!slug) {
30
63
  return;
@@ -1,9 +1,13 @@
1
1
  import type { TypeaheadSuggestionsEndpointRequestParameters, TypeaheadSuggestionsEndpointResponseData } from '@aboutyou/backbone/endpoints/typeahead/typeahead';
2
+ import type { SearchV2ResolveEndpointParameters, SearchV2SuggestionsEndpointResponseData, SearchV2SuggestionsEndpointParameters } from '../../types/bapi/search';
2
3
  type SearchWith = TypeaheadSuggestionsEndpointRequestParameters['with'];
4
+ /** @deprecated `searchProducts` is deprecated. Please, use `getSuggestions` or `resolve` RPC methods */
3
5
  export declare const searchProducts: ({ term, slug, with: _with, productLimit }: {
4
6
  term: string;
5
7
  slug?: string | undefined;
6
8
  with?: SearchWith;
7
9
  productLimit?: number | undefined;
8
10
  }, { cached, bapiClient, withParams }: import("../../types").RpcContext) => Promise<TypeaheadSuggestionsEndpointResponseData>;
11
+ export declare const getSearchSuggestions: ({ term, with: _with, categoryId }: SearchV2SuggestionsEndpointParameters, { bapiClient, withParams }: import("../../types").RpcContext) => Promise<SearchV2SuggestionsEndpointResponseData>;
12
+ export declare const resolveSearch: ({ term, with: _with, categoryId }: SearchV2ResolveEndpointParameters, { cached, bapiClient, withParams }: import("../../types").RpcContext) => Promise<import("@aboutyou/backbone/types/Search").SearchEntity | null>;
9
13
  export {};
@@ -10,6 +10,27 @@ export const searchProducts = async function searchProducts2({ term, slug, with:
10
10
  with: _with ?? withParams?.search ?? MIN_WITH_PARAMS_SEARCH
11
11
  });
12
12
  };
13
+ export const getSearchSuggestions = async function getSearchSuggestions2({ term, with: _with, categoryId }, { bapiClient, withParams }) {
14
+ return await bapiClient.searchv2.suggestions(
15
+ term,
16
+ {
17
+ categoryId,
18
+ with: _with ?? withParams?.searchV2
19
+ }
20
+ );
21
+ };
22
+ export const resolveSearch = async function resolveSearch2({ term, with: _with, categoryId }, { cached, bapiClient, withParams }) {
23
+ const resolvedEntity = await cached(bapiClient.searchv2.resolve, {
24
+ cacheKeyPrefix: `resolve-${term}`
25
+ })(term, {
26
+ categoryId,
27
+ with: _with ?? withParams?.searchV2
28
+ });
29
+ if (!resolvedEntity) {
30
+ return null;
31
+ }
32
+ return resolvedEntity;
33
+ };
13
34
  const getCategoryId = async (cached, bapiClient, slug) => {
14
35
  if (!slug) {
15
36
  return;
@@ -3,7 +3,9 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.updatePasswordByHash = exports.refreshAccessToken = exports.oauthRevokeToken = exports.oauthRegister = exports.oauthLogin = exports.oauthGuestLogin = exports.oauthForgetPassword = void 0;
6
+ exports.oauthRevokeToken = exports.oauthRegister = exports.oauthLogin = exports.oauthGuestLogin = exports.oauthForgetPassword = void 0;
7
+ exports.postLogin = postLogin;
8
+ exports.updatePasswordByHash = exports.refreshAccessToken = void 0;
7
9
  var _jose = require("jose");
8
10
  var _types = require("../../types/index.cjs");
9
11
  var _constants = require("../../constants/index.cjs");
@@ -1,5 +1,6 @@
1
1
  import type { Optional } from 'utility-types';
2
- import type { GuestRequest, LoginRequest, RegisterRequest, RpcContext, UpdatePasswordByHashRequest } from '../../types';
2
+ import type { GuestRequest, LoginRequest, Oauth, RegisterRequest, RpcContext, RpcContextWithSession, UpdatePasswordByHashRequest } from '../../types';
3
+ export declare function postLogin(context: RpcContextWithSession, tokens: Oauth): Promise<void>;
3
4
  export declare const oauthLogin: (login: Optional<LoginRequest, "shop_id">, context: RpcContext) => Promise<Response>;
4
5
  export declare const oauthRegister: (register: Optional<RegisterRequest, "shop_id">, context: RpcContext) => Promise<Response>;
5
6
  export declare const oauthGuestLogin: (guest: Optional<GuestRequest, "shop_id">, context: RpcContext) => Promise<Response>;
@@ -11,7 +11,7 @@ const convertErrorForRpcCall = (error, httpStatuses) => {
11
11
  return new Response(null, { status: error.response.status });
12
12
  }
13
13
  };
14
- async function postLogin(context, tokens) {
14
+ export async function postLogin(context, tokens) {
15
15
  const user = await unwrap(
16
16
  fetchUser({ accessToken: tokens.access_token, callback: "" }, context)
17
17
  );
@@ -2,7 +2,7 @@ import type { BapiClient } from '@aboutyou/backbone';
2
2
  import type { Log } from '../../utils';
3
3
  import type { CachedType } from '../../cache/cached';
4
4
  import type { ShopUser } from '../user';
5
- import type { WishlistWithOptions, BasketWithOptions, ProductWith, VariantWith, SearchWith, CategoryWith, ProductCategoryWith } from '../';
5
+ import type { WishlistWithOptions, BasketWithOptions, ProductWith, VariantWith, SearchWith, SearchV2With, CategoryWith, ProductCategoryWith } from '../';
6
6
  import { OAuthTokens, IDPConfig } from './auth';
7
7
  export type WithParams = Partial<{
8
8
  basket: BasketWithOptions;
@@ -11,6 +11,7 @@ export type WithParams = Partial<{
11
11
  productCategory: ProductCategoryWith;
12
12
  category: CategoryWith;
13
13
  search: SearchWith;
14
+ searchV2: SearchV2With;
14
15
  variant: VariantWith;
15
16
  }>;
16
17
  export interface RuntimeConfiguration {
@@ -2,6 +2,10 @@ import type { ProductWith } from '@aboutyou/backbone/types/ProductWith';
2
2
  import type { TypeaheadSuggestionsEndpointRequestParameters } from '@aboutyou/backbone/endpoints/typeahead/typeahead';
3
3
  import { Query } from '../index';
4
4
  export type { BrandOrCategorySuggestion, ProductSuggestion, TypeaheadSuggestion, TypeaheadBrandOrCategorySuggestion, TypeaheadProductSuggestion, TypeaheadSuggestionsEndpointResponseData, } from '@aboutyou/backbone/endpoints/typeahead/typeahead';
5
+ export type { SearchV2With } from '@aboutyou/backbone/endpoints/searchv2/includes';
6
+ export type { SearchV2SuggestionsEndpointResponseData, SearchV2SuggestionsEndpointParameters, } from '@aboutyou/backbone/endpoints/searchv2/suggestions';
7
+ export type { SearchV2ResolveEndpointResponseData, SearchV2ResolveEndpointParameters, } from '@aboutyou/backbone/endpoints/searchv2/resolve';
8
+ export type * from '@aboutyou/backbone/types/Search';
5
9
  export type SearchInput = {
6
10
  term?: Query;
7
11
  slug?: string;
@@ -1,24 +1,24 @@
1
1
  import type { Log } from '..';
2
2
  import { HashAlgorithm } from '../constants/hash';
3
3
  export declare const generateKey: ({ keyTemplate, hashAlgorithm, shopId, userId, log, }: {
4
- keyName?: string | undefined;
4
+ keyName?: string;
5
5
  keyTemplate: string;
6
6
  hashAlgorithm: HashAlgorithm;
7
7
  shopId: string;
8
8
  userId: string;
9
- log?: Log | undefined;
9
+ log?: Log;
10
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
- log?: Log | undefined;
16
+ log?: Log;
17
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
- log?: Log | undefined;
23
+ log?: Log;
24
24
  }) => Promise<string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scayle/storefront-core",
3
- "version": "7.46.1",
3
+ "version": "7.48.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,7 +59,7 @@
59
59
  "test:ci": "vitest --run --passWithNoTests --coverage --reporter=default --reporter=junit"
60
60
  },
61
61
  "dependencies": {
62
- "@aboutyou/backbone": "16.2.0",
62
+ "@aboutyou/backbone": "16.2.1",
63
63
  "crypto-js": "4.2.0",
64
64
  "jose": "5.2.3",
65
65
  "radash": "12.1.0",
@@ -71,16 +71,16 @@
71
71
  "devDependencies": {
72
72
  "@scayle/eslint-config-storefront": "3.2.6",
73
73
  "@types/crypto-js": "4.2.2",
74
- "@types/node": "20.11.30",
74
+ "@types/node": "20.12.5",
75
75
  "@types/webpack-env": "1.18.4",
76
76
  "@vitest/coverage-v8": "1.4.0",
77
- "dprint": "0.45.0",
77
+ "dprint": "0.45.1",
78
78
  "eslint": "8.57.0",
79
79
  "eslint-formatter-gitlab": "5.1.0",
80
80
  "publint": "0.2.7",
81
81
  "rimraf": "5.0.5",
82
82
  "ts-node": "10.9.2",
83
- "typescript": "5.4.3",
83
+ "typescript": "5.4.4",
84
84
  "unbuild": "2.0.0",
85
85
  "unstorage": "1.10.2",
86
86
  "vitest": "1.4.0"