Skip to content

Commit 516fe84

Browse files
authored
Merge pull request #2377 from SalesforceCommerceCloud/vm/sync-extensibility-with-latest-develop
[App Extensibility] Sync with latest `develop` branch (@W-18121817@)
2 parents cb384b5 + 5fd80e2 commit 516fe84

File tree

39 files changed

+2459
-2602
lines changed

39 files changed

+2459
-2602
lines changed

package-lock.json

Lines changed: 592 additions & 1618 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"jsdom": "^22.1.0",
3232
"lerna": "^6.6.1",
3333
"semver": "^7.5.2",
34-
"shelljs": "^0.8.5",
34+
"shelljs": "^0.9.2",
3535
"syncpack": "^10.1.0"
3636
},
3737
"engines": {

packages/commerce-sdk-react/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
## v3.3.0-extensibility-preview.4 (Feb 12, 2025)
22
- Add `ServerContext` type for `useServerContext` hook [#2239](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2239)
33

4+
## v3.3.0-dev (Feb 18, 2025)
5+
- Invalidate cache instead of removing cache when triggering logout [#2323](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2323)
6+
- Fix dependencies vulnerabilities [#2338](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2338)
7+
- Allow custom parameters/body to be passed to SLAS authorize/authenticate calls via commerce-sdk-react auth helpers [#2358](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2358)
8+
49
## v3.2.1 (Mar 05, 2025)
510
- Update PWA-Kit SDKs to v3.9.1 [#2301](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2301)
611

packages/commerce-sdk-react/package-lock.json

Lines changed: 584 additions & 747 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/commerce-sdk-react/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"version": "node ./scripts/version.js"
4141
},
4242
"dependencies": {
43-
"commerce-sdk-isomorphic": "^3.2.0",
43+
"commerce-sdk-isomorphic": "^3.3.0",
4444
"js-cookie": "^3.0.1",
4545
"jwt-decode": "^4.0.0"
4646
},
@@ -69,7 +69,7 @@
6969
"react-helmet": "^6.1.0",
7070
"react-router-dom": "^5.3.4",
7171
"semver": "^7.5.2",
72-
"shelljs": "^0.8.5",
72+
"shelljs": "^0.9.2",
7373
"typedoc": "^0.24.7",
7474
"typedoc-plugin-missing-exports": "^2.0.0",
7575
"typescript": "4.9.5"

packages/commerce-sdk-react/src/auth/index.test.ts

Lines changed: 95 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77
import Auth, {AuthData} from './'
88
import {waitFor} from '@testing-library/react'
99
import jwt from 'jsonwebtoken'
10-
import {helpers, ShopperCustomersTypes, ShopperLogin} from 'commerce-sdk-isomorphic'
10+
import {
11+
helpers,
12+
ShopperCustomersTypes,
13+
ShopperCustomers,
14+
ShopperLogin
15+
} from 'commerce-sdk-isomorphic'
1116
import * as utils from '../utils'
1217
import {SLAS_SECRET_PLACEHOLDER} from '../constant'
1318
import {ShopperLoginTypes} from 'commerce-sdk-isomorphic'
@@ -49,23 +54,23 @@ jest.mock('commerce-sdk-isomorphic', () => {
4954
authorizeIDP: jest.fn().mockResolvedValue(''),
5055
authorizePasswordless: jest.fn().mockResolvedValue(''),
5156
getPasswordLessAccessToken: jest.fn().mockResolvedValue('')
52-
},
53-
ShopperCustomers: jest.fn().mockImplementation(() => {
54-
return {
55-
updateCustomerPassword: () => {}
56-
}
57-
})
57+
}
5858
}
5959
})
6060

61-
jest.mock('../utils', () => ({
62-
__esModule: true,
63-
onClient: () => true,
64-
getParentOrigin: jest.fn().mockResolvedValue(''),
65-
isOriginTrusted: () => false,
66-
getDefaultCookieAttributes: () => {},
67-
isAbsoluteUrl: () => true
68-
}))
61+
jest.mock('../utils', () => {
62+
const originalModule = jest.requireActual('../utils')
63+
64+
return {
65+
...originalModule,
66+
__esModule: true,
67+
onClient: () => true,
68+
getParentOrigin: jest.fn().mockResolvedValue(''),
69+
isOriginTrusted: () => false,
70+
getDefaultCookieAttributes: () => {},
71+
isAbsoluteUrl: () => true
72+
}
73+
})
6974

7075
/** The auth data we store has a slightly different shape than what we use. */
7176
type StoredAuthData = Omit<AuthData, 'refresh_token'> & {refresh_token_guest?: string}
@@ -491,6 +496,48 @@ describe('Auth', () => {
491496
expect(helpers.loginGuestUser).toHaveBeenCalled()
492497
})
493498

499+
test('loginGuestUser can pass along custom parameters', async () => {
500+
const parameters = {c_test: 'custom parameter'}
501+
const auth = new Auth(config)
502+
await auth.loginGuestUser(parameters)
503+
// The first argument is the SLAS config, which we don't need to verify in this case
504+
// We only want to see that the custom parameters were included in the second argument
505+
expect(helpers.loginGuestUser).toHaveBeenCalledWith(
506+
expect.anything(),
507+
expect.objectContaining({c_test: 'custom parameter'})
508+
)
509+
})
510+
511+
test('register only sends custom parameters to registered login', async () => {
512+
const registerCustomerSpy = jest
513+
.spyOn(ShopperCustomers.prototype, 'registerCustomer')
514+
.mockImplementation()
515+
const auth = new Auth(config)
516+
const inputToRegister = {
517+
customer: baseCustomer,
518+
password: 'test',
519+
someOtherParameter: 'this should not be passed to login',
520+
c_test: 'custom parameter'
521+
}
522+
523+
await auth.register(inputToRegister)
524+
525+
// Body should only include credentials. No other parameters
526+
expect(registerCustomerSpy).toHaveBeenCalledWith(
527+
expect.objectContaining({body: {customer: baseCustomer, password: 'test'}})
528+
)
529+
530+
// We don't need to verify the first and third parameters as they correspond to the SLAS client and mandatory parameters
531+
// The second argument is credentials
532+
// We want to see that only the custom parameters were included in the fourth argument and not any other parameters
533+
expect(helpers.loginRegisteredUserB2C).toHaveBeenCalledWith(
534+
expect.anything(),
535+
expect.anything(),
536+
expect.anything(),
537+
{body: {c_test: 'custom parameter'}}
538+
)
539+
})
540+
494541
test.each([
495542
// When user has not selected DNT pref
496543
[true, undefined, {dnt: true}],
@@ -614,6 +661,28 @@ describe('Auth', () => {
614661
})
615662
})
616663

664+
test('loginRegisteredUserB2C can pass along custom parameters', async () => {
665+
const options = {
666+
body: {c_test: 'custom parameter'}
667+
}
668+
const credentials = {
669+
username: 'test',
670+
password: 'test'
671+
}
672+
const auth = new Auth(config)
673+
await auth.loginRegisteredUserB2C({...credentials, options})
674+
// We don't need to verify the first and third parameters as they correspond to the SLAS client and mandatory parameters
675+
// The second argument is credentials, including the client secret
676+
// The fourth argument is custom parameters
677+
// We only want to see that the custom parameters were included in the fourth argument
678+
expect(helpers.loginRegisteredUserB2C).toHaveBeenCalledWith(
679+
expect.anything(),
680+
expect.objectContaining(credentials),
681+
expect.anything(),
682+
options
683+
)
684+
})
685+
617686
test('loginIDPUser calls isomorphic loginIDPUser', async () => {
618687
const auth = new Auth(config)
619688
await auth.loginIDPUser({redirectURI: 'redirectURI', code: 'test'})
@@ -634,10 +703,18 @@ describe('Auth', () => {
634703

635704
test('authorizeIDP calls isomorphic authorizeIDP', async () => {
636705
const auth = new Auth(config)
637-
await auth.authorizeIDP({redirectURI: 'redirectURI', hint: 'test'})
706+
await auth.authorizeIDP({
707+
redirectURI: 'redirectURI',
708+
hint: 'test',
709+
c_customParam: 'customParam'
710+
})
638711
expect(helpers.authorizeIDP).toHaveBeenCalled()
639712
const functionArg = (helpers.authorizeIDP as jest.Mock).mock.calls[0][1]
640-
expect(functionArg).toMatchObject({redirectURI: 'redirectURI', hint: 'test'})
713+
expect(functionArg).toMatchObject({
714+
redirectURI: 'redirectURI',
715+
hint: 'test',
716+
c_customParam: 'customParam'
717+
})
641718
})
642719

643720
test('authorizeIDP adds clientSecret to parameters when using private client', async () => {
@@ -698,6 +775,7 @@ describe('Auth', () => {
698775
expect(helpers.loginGuestUser).toHaveBeenCalled()
699776
})
700777
test('updateCustomerPassword calls registered login', async () => {
778+
jest.spyOn(ShopperCustomers.prototype, 'updateCustomerPassword').mockImplementation()
701779
const auth = new Auth(config)
702780
await auth.updateCustomerPassword({
703781
customer: baseCustomer,

packages/commerce-sdk-react/src/auth/index.ts

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ import {
2121
onClient,
2222
getDefaultCookieAttributes,
2323
isAbsoluteUrl,
24-
stringToBase64
24+
stringToBase64,
25+
extractCustomParameters
2526
} from '../utils'
2627
import {
2728
MOBIFY_PATH,
@@ -71,6 +72,15 @@ type AuthorizePasswordlessParams = Parameters<Helpers['authorizePasswordless']>[
7172
type LoginPasswordlessParams = Parameters<Helpers['getPasswordLessAccessToken']>[2]
7273
type LoginRegisteredUserB2CCredentials = Parameters<Helpers['loginRegisteredUserB2C']>[1]
7374

75+
/**
76+
* This is a temporary type until we can make a breaking change and modify the signature for
77+
* loginRegisteredUserB2C so that it takes in a body rather than just credentials
78+
*
79+
*/
80+
type LoginRegisteredUserCredentialsWithCustomParams = LoginRegisteredUserB2CCredentials & {
81+
options?: {body: helpers.CustomRequestBody}
82+
}
83+
7484
/**
7585
* The extended field is not from api response, we manually store the auth type,
7686
* so we don't need to make another API call when we already have the data.
@@ -792,7 +802,7 @@ class Auth {
792802
* A wrapper method for commerce-sdk-isomorphic helper: loginGuestUser.
793803
*
794804
*/
795-
async loginGuestUser() {
805+
async loginGuestUser(parameters?: helpers.CustomQueryParameters) {
796806
if (this.clientSecret && onClient() && this.clientSecret !== SLAS_SECRET_PLACEHOLDER) {
797807
this.logWarning(SLAS_SECRET_WARNING_MSG)
798808
}
@@ -812,7 +822,9 @@ class Auth {
812822
{
813823
redirectURI: this.redirectURI,
814824
dnt: dntPref,
815-
...(usid && {usid})
825+
...(usid && {usid}),
826+
// custom parameters are sent only into the /authorize endpoint.
827+
...parameters
816828
}
817829
] as const
818830
const callback = this.clientSecret
@@ -837,10 +849,9 @@ class Auth {
837849
*
838850
*/
839851
async register(body: ShopperCustomersTypes.CustomerRegistration) {
840-
const {
841-
customer: {login},
842-
password
843-
} = body
852+
const {customer, password, ...parameters} = body
853+
const {login} = customer
854+
const customParameters = extractCustomParameters(parameters)
844855

845856
// login is optional field from isomorphic library
846857
// type CustomerRegistration
@@ -849,24 +860,38 @@ class Auth {
849860
throw new Error('Customer registration is missing login field.')
850861
}
851862

863+
// The registerCustomer endpoint currently does not support custom parameters
864+
// so we make sure not to send any custom params here
852865
const res = await this.shopperCustomersClient.registerCustomer({
853866
headers: {
854867
authorization: `Bearer ${this.get('access_token')}`
855868
},
856-
body
869+
body: {
870+
customer,
871+
password
872+
}
857873
})
858874
await this.loginRegisteredUserB2C({
859875
username: login,
860-
password
876+
password,
877+
options: {
878+
body: customParameters
879+
}
861880
})
862881
return res
863882
}
864883

865884
/**
866885
* A wrapper method for commerce-sdk-isomorphic helper: loginRegisteredUserB2C.
867886
*
887+
* Note: This uses the type LoginRegisteredUserCredentialsWithCustomParams rather than LoginRegisteredUserB2CCredentials
888+
* as a workaround to allow custom parameters through because the login.mutateAsync hook will only pass through a single
889+
* 'body' argument into this function.
890+
*
891+
* In the next major version release, we should modify this method so that it's input is a body containing credentials,
892+
* similar to the input for the register function.
868893
*/
869-
async loginRegisteredUserB2C(credentials: LoginRegisteredUserB2CCredentials) {
894+
async loginRegisteredUserB2C(credentials: LoginRegisteredUserCredentialsWithCustomParams) {
870895
if (this.clientSecret && onClient() && this.clientSecret !== SLAS_SECRET_PLACEHOLDER) {
871896
this.logWarning(SLAS_SECRET_WARNING_MSG)
872897
}
@@ -877,14 +902,16 @@ class Auth {
877902
const token = await helpers.loginRegisteredUserB2C(
878903
this.client,
879904
{
880-
...credentials,
905+
username: credentials.username,
906+
password: credentials.password,
881907
clientSecret: this.clientSecret
882908
},
883909
{
884910
redirectURI,
885911
dnt: dntPref,
886912
...(usid && {usid})
887-
}
913+
},
914+
credentials.options
888915
)
889916
this.handleTokenResponse(token, isGuest)
890917
if (onClient()) {
@@ -1066,12 +1093,14 @@ class Auth {
10661093
async authorizeIDP(parameters: AuthorizeIDPParams) {
10671094
const redirectURI = parameters.redirectURI || this.redirectURI
10681095
const usid = this.get('usid')
1096+
const customParameters = extractCustomParameters(parameters)
10691097
const {url, codeVerifier} = await helpers.authorizeIDP(
10701098
this.client,
10711099
{
10721100
redirectURI,
10731101
hint: parameters.hint,
1074-
...(usid && {usid})
1102+
...(usid && {usid}),
1103+
...customParameters
10751104
},
10761105
this.isPrivate
10771106
)

packages/commerce-sdk-react/src/hooks/useAuthHelper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ const cacheUpdateMatrix: CacheUpdateMatrix = {
107107
loginGuestUser: noop,
108108
logout() {
109109
return {
110-
remove: [{queryKey: ['/commerce-sdk-react']}]
110+
invalidate: [{queryKey: ['/commerce-sdk-react']}]
111111
}
112112
},
113113
register: noop,

packages/commerce-sdk-react/src/utils.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,20 @@ describe('Utils', () => {
1515
const isURL = utils.isAbsoluteUrl(url)
1616
expect(isURL).toBe(expected)
1717
})
18+
test('extractCustomParameters only returns custom parameters', () => {
19+
const parameters = {
20+
c_param1: 'this is a custom',
21+
param1: 'this is not a custom',
22+
c_param2: 1,
23+
param2: 2,
24+
param3: false,
25+
c_param3: true
26+
}
27+
const customParameters = utils.extractCustomParameters(parameters)
28+
expect(customParameters).toEqual({
29+
c_param1: 'this is a custom',
30+
c_param2: 1,
31+
c_param3: true
32+
})
33+
})
1834
})

packages/commerce-sdk-react/src/utils.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import Cookies, {CookieAttributes} from 'js-cookie'
99
import {IFRAME_HOST_ALLOW_LIST} from './constant'
10+
import {helpers} from 'commerce-sdk-isomorphic'
1011

1112
/** Utility to determine if you are on the browser (client) or not. */
1213
export const onClient = (): boolean => typeof window !== 'undefined'
@@ -143,3 +144,20 @@ export const stringToBase64 =
143144
typeof window === 'object' && typeof window.document === 'object'
144145
? btoa
145146
: (unencoded: string): string => Buffer.from(unencoded).toString('base64')
147+
148+
/**
149+
* Extracts custom parameters from a set of SCAPI parameters
150+
*
151+
* Custom parameters are identified by the 'c_' prefix before their key
152+
*
153+
* @param parameters object containing all parameters for a SCAPI / SLAS call
154+
* @returns new object containing only custom parameters
155+
*/
156+
export const extractCustomParameters = (
157+
parameters: {[key: string]: string | number | boolean | string[] | number[]} | null
158+
): helpers.CustomQueryParameters | helpers.CustomRequestBody => {
159+
if (typeof parameters !== 'object' || parameters === null) {
160+
throw new Error('Invalid input. Expecting an object as an input.')
161+
}
162+
return Object.fromEntries(Object.entries(parameters).filter(([key]) => key.startsWith('c_')))
163+
}

0 commit comments

Comments
 (0)