@salesforce/commerce-sdk-react 5.2.0-nightly-20260430084253 → 5.2.0-preview.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 +2 -4
- package/auth/index.d.ts +60 -87
- package/auth/index.js +240 -91
- package/components/StorefrontPreview/storefront-preview.js +7 -3
- package/constant.d.ts +1 -0
- package/constant.js +4 -1
- package/hooks/helpers.d.ts +1 -16
- package/hooks/helpers.js +25 -12
- package/hooks/useAuthorizationHeader.js +11 -6
- package/hooks/useUsid.d.ts +1 -0
- package/hooks/useUsid.js +3 -1
- package/package.json +5 -5
- package/provider.d.ts +3 -0
- package/provider.js +41 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
## v5.2.0-
|
|
2
|
-
## v5.2.0-dev (Apr 30, 2026)
|
|
3
|
-
## v3.18.0-nightly-20260430084253 (Apr 30, 2026)
|
|
4
|
-
## v5.2.0-dev (Mar 20, 2026)
|
|
1
|
+
## v5.2.0-preview.0 (May 01, 2026)
|
|
5
2
|
- Allow auth related cookies domain to be set via config [#3782](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3782)
|
|
3
|
+
- Add support for HttpOnly session cookies [#3804](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3804)
|
|
6
4
|
|
|
7
5
|
## v5.1.1 (Mar 20, 2026)
|
|
8
6
|
- Update storefront preview to support base paths [#3614](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3614)
|
package/auth/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ interface AuthConfig extends ApiClientConfigParams {
|
|
|
9
9
|
proxy: string;
|
|
10
10
|
headers?: Record<string, string>;
|
|
11
11
|
privateClientProxyEndpoint?: string;
|
|
12
|
+
publicClientProxyEndpoint?: string;
|
|
12
13
|
fetchOptions?: FetchOptions;
|
|
13
14
|
fetchedToken?: string;
|
|
14
15
|
enablePWAKitPrivateClient?: boolean;
|
|
@@ -21,6 +22,8 @@ interface AuthConfig extends ApiClientConfigParams {
|
|
|
21
22
|
refreshTokenGuestCookieTTL?: number;
|
|
22
23
|
hybridAuthEnabled?: boolean;
|
|
23
24
|
cookieDomain?: string;
|
|
25
|
+
/** When true, session tokens are set as HttpOnly cookies */
|
|
26
|
+
enableHttpOnlySessionCookies?: boolean;
|
|
24
27
|
}
|
|
25
28
|
/**
|
|
26
29
|
* Body type for loginRegisteredUserB2C - aligns with register function pattern
|
|
@@ -70,12 +73,20 @@ export type AuthData = Prettify<RemoveStringIndex<TokenResponse> & {
|
|
|
70
73
|
idp_access_token: string;
|
|
71
74
|
}>;
|
|
72
75
|
/** A shopper could be guest or registered, so we store the refresh tokens individually. */
|
|
73
|
-
type AuthDataKeys = Exclude<keyof AuthData, 'refresh_token'> | 'refresh_token_guest' | 'refresh_token_registered' | 'access_token_sfra' | typeof DNT_COOKIE_NAME | typeof DWSID_COOKIE_NAME | 'code_verifier' | 'uido' | 'idp_refresh_token' | 'dnt';
|
|
76
|
+
type AuthDataKeys = Exclude<keyof AuthData, 'refresh_token'> | 'refresh_token_guest' | 'refresh_token_registered' | 'access_token_sfra' | typeof DNT_COOKIE_NAME | typeof DWSID_COOKIE_NAME | 'code_verifier' | 'uido' | 'idp_refresh_token' | 'dnt' | 'cc-at-expires' | 'cc-at-dnt' | 'cc-nx-exists';
|
|
74
77
|
type DntOptions = {
|
|
75
78
|
includeDefaults: boolean;
|
|
76
79
|
};
|
|
77
80
|
export declare const DEFAULT_SLAS_REFRESH_TOKEN_REGISTERED_TTL: number;
|
|
78
81
|
export declare const DEFAULT_SLAS_REFRESH_TOKEN_GUEST_TTL: number;
|
|
82
|
+
/**
|
|
83
|
+
* Module-level map for deduplicating concurrent refresh token requests across Auth instances.
|
|
84
|
+
* React may recreate Auth instances on re-renders (due to unstable useMemo deps like `headers`),
|
|
85
|
+
* so instance-level dedup via `this.pendingToken` is insufficient. This map ensures only one
|
|
86
|
+
* in-flight refresh request exists per siteId+clientId combination.
|
|
87
|
+
* @internal — exported for test access only
|
|
88
|
+
*/
|
|
89
|
+
export declare const pendingRefreshTokens: Map<string, Promise<AuthData>>;
|
|
79
90
|
/**
|
|
80
91
|
* This class is used to handle shopper authentication.
|
|
81
92
|
* It is responsible for initializing shopper session, manage access
|
|
@@ -88,7 +99,6 @@ declare class Auth {
|
|
|
88
99
|
private client;
|
|
89
100
|
private shopperCustomersClient;
|
|
90
101
|
private redirectURI;
|
|
91
|
-
private pendingToken;
|
|
92
102
|
private stores;
|
|
93
103
|
private fetchedToken;
|
|
94
104
|
private clientSecret;
|
|
@@ -101,10 +111,17 @@ declare class Auth {
|
|
|
101
111
|
private refreshTokenGuestCookieTTL;
|
|
102
112
|
private refreshTrustedAgentHandler;
|
|
103
113
|
private hybridAuthEnabled;
|
|
114
|
+
private enableHttpOnlySessionCookies;
|
|
104
115
|
constructor(config: AuthConfig);
|
|
105
116
|
get(name: AuthDataKeys): string;
|
|
106
117
|
private set;
|
|
107
118
|
private delete;
|
|
119
|
+
/**
|
|
120
|
+
* Returns the DNT value from the current access token, or undefined if
|
|
121
|
+
* no access token is available. In HttpOnly mode, reads from the
|
|
122
|
+
* cc-at-dnt companion cookie; otherwise parses the JWT directly.
|
|
123
|
+
*/
|
|
124
|
+
private getDntFromAccessToken;
|
|
108
125
|
/**
|
|
109
126
|
* Return the value of the DNT cookie or undefined if it is not set.
|
|
110
127
|
* The DNT cookie being undefined means that there is a necessity to
|
|
@@ -129,6 +146,27 @@ declare class Auth {
|
|
|
129
146
|
* Used to validate JWT token expiration.
|
|
130
147
|
*/
|
|
131
148
|
private isTokenExpired;
|
|
149
|
+
/**
|
|
150
|
+
* Returns whether a refresh token exists in an HttpOnly cookie. Since JavaScript cannot
|
|
151
|
+
* read HttpOnly cookies, we check the non-HttpOnly indicator cookie (cc-nx-exists) that
|
|
152
|
+
* is set alongside the refresh token with the same expiry.
|
|
153
|
+
*/
|
|
154
|
+
private hasHttpOnlyRefreshToken;
|
|
155
|
+
/**
|
|
156
|
+
* Clears the non-HttpOnly access token expiry cookie (cc-at-expires).
|
|
157
|
+
*
|
|
158
|
+
* This is needed when SCAPI returns a 401 because the HttpOnly access token cookie
|
|
159
|
+
* (cc-at_{siteId}) was deleted externally while the expiry cookie remained valid.
|
|
160
|
+
* Clearing the expiry cookie ensures isAccessTokenExpired() returns true, so
|
|
161
|
+
* subsequent calls to ready() will trigger a refresh instead of assuming the token
|
|
162
|
+
* is still valid.
|
|
163
|
+
*/
|
|
164
|
+
clearAccessTokenExpiry(): void;
|
|
165
|
+
/**
|
|
166
|
+
* Returns whether the access token is expired. When enableHttpOnlySessionCookies is true,
|
|
167
|
+
* uses cc-at-expires cookie from store; otherwise decodes the JWT from getAccessToken().
|
|
168
|
+
*/
|
|
169
|
+
private isAccessTokenExpired;
|
|
132
170
|
/**
|
|
133
171
|
* Returns the SLAS access token or an empty string if the access token
|
|
134
172
|
* is not found in local store or if SFRA wants PWA to trigger refresh token login.
|
|
@@ -186,45 +224,13 @@ declare class Auth {
|
|
|
186
224
|
* store the data in storage.
|
|
187
225
|
*/
|
|
188
226
|
private handleTokenResponse;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
id_token: string;
|
|
192
|
-
refresh_token: string;
|
|
193
|
-
expires_in: number;
|
|
194
|
-
refresh_token_expires_in: number;
|
|
195
|
-
token_type: "Bearer";
|
|
196
|
-
usid: string;
|
|
197
|
-
customer_id: string;
|
|
198
|
-
enc_user_id: string;
|
|
199
|
-
idp_access_token: string;
|
|
200
|
-
idp_refresh_token?: string | undefined;
|
|
201
|
-
dnt?: string | undefined;
|
|
202
|
-
} & {
|
|
203
|
-
[key: string]: any;
|
|
204
|
-
}>;
|
|
227
|
+
private get refreshDedupKey();
|
|
228
|
+
refreshAccessToken(): Promise<AuthData>;
|
|
205
229
|
/**
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
* It returns the queue.
|
|
209
|
-
*
|
|
210
|
-
* @Internal
|
|
230
|
+
* Internal implementation of the refresh flow. Called only via refreshAccessToken()
|
|
231
|
+
* which wraps it in the module-level pendingRefreshTokens map for deduplication.
|
|
211
232
|
*/
|
|
212
|
-
|
|
213
|
-
access_token: string;
|
|
214
|
-
id_token: string;
|
|
215
|
-
refresh_token: string;
|
|
216
|
-
expires_in: number;
|
|
217
|
-
refresh_token_expires_in: number;
|
|
218
|
-
token_type: "Bearer";
|
|
219
|
-
usid: string;
|
|
220
|
-
customer_id: string;
|
|
221
|
-
enc_user_id: string;
|
|
222
|
-
idp_access_token: string;
|
|
223
|
-
idp_refresh_token?: string | undefined;
|
|
224
|
-
dnt?: string | undefined;
|
|
225
|
-
} & {
|
|
226
|
-
[key: string]: any;
|
|
227
|
-
}>;
|
|
233
|
+
private _refreshAccessToken;
|
|
228
234
|
logWarning: (msg: string) => void;
|
|
229
235
|
/**
|
|
230
236
|
* This method extracts the status and message from a ResponseError that is returned
|
|
@@ -257,22 +263,7 @@ declare class Auth {
|
|
|
257
263
|
* 3. If we have valid TAOB access token - refresh TAOB token flow
|
|
258
264
|
* 4. PKCE flow
|
|
259
265
|
*/
|
|
260
|
-
ready(): Promise<
|
|
261
|
-
access_token: string;
|
|
262
|
-
id_token: string;
|
|
263
|
-
refresh_token: string;
|
|
264
|
-
expires_in: number;
|
|
265
|
-
refresh_token_expires_in: number;
|
|
266
|
-
token_type: "Bearer";
|
|
267
|
-
usid: string;
|
|
268
|
-
customer_id: string;
|
|
269
|
-
enc_user_id: string;
|
|
270
|
-
idp_access_token: string;
|
|
271
|
-
idp_refresh_token?: string | undefined;
|
|
272
|
-
dnt?: string | undefined;
|
|
273
|
-
} & {
|
|
274
|
-
[key: string]: any;
|
|
275
|
-
}>;
|
|
266
|
+
ready(): Promise<AuthData>;
|
|
276
267
|
/**
|
|
277
268
|
* Creates a function that only executes after a session is initialized.
|
|
278
269
|
* @param fn Function that needs to wait until the session is initialized.
|
|
@@ -283,22 +274,7 @@ declare class Auth {
|
|
|
283
274
|
* A wrapper method for commerce-sdk-isomorphic helper: loginGuestUser.
|
|
284
275
|
*
|
|
285
276
|
*/
|
|
286
|
-
loginGuestUser(parameters?: helpers.CustomQueryParameters): Promise<
|
|
287
|
-
access_token: string;
|
|
288
|
-
id_token: string;
|
|
289
|
-
refresh_token: string;
|
|
290
|
-
expires_in: number;
|
|
291
|
-
refresh_token_expires_in: number;
|
|
292
|
-
token_type: "Bearer";
|
|
293
|
-
usid: string;
|
|
294
|
-
customer_id: string;
|
|
295
|
-
enc_user_id: string;
|
|
296
|
-
idp_access_token: string;
|
|
297
|
-
idp_refresh_token?: string | undefined;
|
|
298
|
-
dnt?: string | undefined;
|
|
299
|
-
} & {
|
|
300
|
-
[key: string]: any;
|
|
301
|
-
}>;
|
|
277
|
+
loginGuestUser(parameters?: helpers.CustomQueryParameters): Promise<AuthData>;
|
|
302
278
|
/**
|
|
303
279
|
* This is a wrapper method for ShopperCustomer API registerCustomer endpoint.
|
|
304
280
|
*
|
|
@@ -333,6 +309,7 @@ declare class Auth {
|
|
|
333
309
|
authType?: ("guest" | "registered") | undefined;
|
|
334
310
|
birthday?: string | undefined;
|
|
335
311
|
companyName?: string | undefined;
|
|
312
|
+
crmContactId?: string | undefined;
|
|
336
313
|
creationDate?: string | undefined;
|
|
337
314
|
currentPassword?: string | undefined;
|
|
338
315
|
customerId?: string | undefined;
|
|
@@ -454,22 +431,7 @@ declare class Auth {
|
|
|
454
431
|
* A wrapper method for commerce-sdk-isomorphic helper: logout.
|
|
455
432
|
*
|
|
456
433
|
*/
|
|
457
|
-
logout(): Promise<
|
|
458
|
-
access_token: string;
|
|
459
|
-
id_token: string;
|
|
460
|
-
refresh_token: string;
|
|
461
|
-
expires_in: number;
|
|
462
|
-
refresh_token_expires_in: number;
|
|
463
|
-
token_type: "Bearer";
|
|
464
|
-
usid: string;
|
|
465
|
-
customer_id: string;
|
|
466
|
-
enc_user_id: string;
|
|
467
|
-
idp_access_token: string;
|
|
468
|
-
idp_refresh_token?: string | undefined;
|
|
469
|
-
dnt?: string | undefined;
|
|
470
|
-
} & {
|
|
471
|
-
[key: string]: any;
|
|
472
|
-
}>;
|
|
434
|
+
logout(): Promise<AuthData>;
|
|
473
435
|
/**
|
|
474
436
|
* Handle updating customer password and re-log in after the access token is invalidated.
|
|
475
437
|
*
|
|
@@ -512,6 +474,17 @@ declare class Auth {
|
|
|
512
474
|
*
|
|
513
475
|
*/
|
|
514
476
|
resetPassword(parameters: ShopperLoginTypes.resetPasswordBodyType): Promise<void>;
|
|
477
|
+
/**
|
|
478
|
+
* Get the current USID for Storefront Preview by forcing a SLAS refresh.
|
|
479
|
+
*
|
|
480
|
+
* Works for both guest and registered shoppers: when an existing refresh
|
|
481
|
+
* token is present (guest or registered, legacy or HttpOnly), the SLAS
|
|
482
|
+
* response provides a fresh USID. When no refresh token is present,
|
|
483
|
+
* `refreshAccessToken()` falls through to a guest login, which also yields
|
|
484
|
+
* a fresh USID. Preview can therefore always obtain a USID without
|
|
485
|
+
* requiring the shopper to sign in.
|
|
486
|
+
*/
|
|
487
|
+
getUsidForPreview(): Promise<string>;
|
|
515
488
|
/**
|
|
516
489
|
* Decode SLAS JWT and extract information such as customer id, usid, etc.
|
|
517
490
|
*
|
package/auth/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.default = exports.DEFAULT_SLAS_REFRESH_TOKEN_REGISTERED_TTL = exports.DEFAULT_SLAS_REFRESH_TOKEN_GUEST_TTL = void 0;
|
|
6
|
+
exports.pendingRefreshTokens = exports.default = exports.DEFAULT_SLAS_REFRESH_TOKEN_REGISTERED_TTL = exports.DEFAULT_SLAS_REFRESH_TOKEN_GUEST_TTL = void 0;
|
|
7
7
|
var _commerceSdkIsomorphic = require("commerce-sdk-isomorphic");
|
|
8
8
|
var _jwtDecode = require("jwt-decode");
|
|
9
9
|
var _storage = require("./storage");
|
|
@@ -139,11 +139,32 @@ const DATA_MAP = {
|
|
|
139
139
|
uido: {
|
|
140
140
|
storageType: 'local',
|
|
141
141
|
key: 'uido'
|
|
142
|
+
},
|
|
143
|
+
'cc-at-expires': {
|
|
144
|
+
storageType: 'cookie',
|
|
145
|
+
key: 'cc-at-expires'
|
|
146
|
+
},
|
|
147
|
+
'cc-at-dnt': {
|
|
148
|
+
storageType: 'cookie',
|
|
149
|
+
key: 'cc-at-dnt'
|
|
150
|
+
},
|
|
151
|
+
'cc-nx-exists': {
|
|
152
|
+
storageType: 'cookie',
|
|
153
|
+
key: 'cc-nx-exists'
|
|
142
154
|
}
|
|
143
155
|
};
|
|
144
156
|
const DEFAULT_SLAS_REFRESH_TOKEN_REGISTERED_TTL = exports.DEFAULT_SLAS_REFRESH_TOKEN_REGISTERED_TTL = 90 * 24 * 60 * 60;
|
|
145
157
|
const DEFAULT_SLAS_REFRESH_TOKEN_GUEST_TTL = exports.DEFAULT_SLAS_REFRESH_TOKEN_GUEST_TTL = 30 * 24 * 60 * 60;
|
|
146
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Module-level map for deduplicating concurrent refresh token requests across Auth instances.
|
|
161
|
+
* React may recreate Auth instances on re-renders (due to unstable useMemo deps like `headers`),
|
|
162
|
+
* so instance-level dedup via `this.pendingToken` is insufficient. This map ensures only one
|
|
163
|
+
* in-flight refresh request exists per siteId+clientId combination.
|
|
164
|
+
* @internal — exported for test access only
|
|
165
|
+
*/
|
|
166
|
+
const pendingRefreshTokens = exports.pendingRefreshTokens = new Map();
|
|
167
|
+
|
|
147
168
|
/**
|
|
148
169
|
* This class is used to handle shopper authentication.
|
|
149
170
|
* It is responsible for initializing shopper session, manage access
|
|
@@ -157,7 +178,7 @@ class Auth {
|
|
|
157
178
|
// Special proxy endpoint for injecting SLAS private client secret.
|
|
158
179
|
// We prioritize config.privateClientProxyEndpoint since that allows us to use the new envBasePath feature
|
|
159
180
|
this.client = new _commerceSdkIsomorphic.ShopperLogin({
|
|
160
|
-
proxy: config.enablePWAKitPrivateClient ? config.privateClientProxyEndpoint : config.proxy,
|
|
181
|
+
proxy: config.enablePWAKitPrivateClient ? config.privateClientProxyEndpoint : config.enableHttpOnlySessionCookies ? config.publicClientProxyEndpoint : config.proxy,
|
|
161
182
|
headers: config.headers || {},
|
|
162
183
|
parameters: {
|
|
163
184
|
clientId: config.clientId,
|
|
@@ -238,6 +259,7 @@ class Auth {
|
|
|
238
259
|
this.isPrivate = !!this.clientSecret;
|
|
239
260
|
this.passwordlessLoginCallbackURI = config.passwordlessLoginCallbackURI || '';
|
|
240
261
|
this.hybridAuthEnabled = config.hybridAuthEnabled || false;
|
|
262
|
+
this.enableHttpOnlySessionCookies = config.enableHttpOnlySessionCookies ?? false;
|
|
241
263
|
}
|
|
242
264
|
get(name) {
|
|
243
265
|
const {
|
|
@@ -266,6 +288,22 @@ class Auth {
|
|
|
266
288
|
storage.delete(key);
|
|
267
289
|
}
|
|
268
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Returns the DNT value from the current access token, or undefined if
|
|
293
|
+
* no access token is available. In HttpOnly mode, reads from the
|
|
294
|
+
* cc-at-dnt companion cookie; otherwise parses the JWT directly.
|
|
295
|
+
*/
|
|
296
|
+
getDntFromAccessToken() {
|
|
297
|
+
if (this.enableHttpOnlySessionCookies && (0, _utils.onClient)()) {
|
|
298
|
+
return this.get('cc-at-dnt') || undefined;
|
|
299
|
+
}
|
|
300
|
+
const accessToken = this.getAccessToken();
|
|
301
|
+
if (accessToken) {
|
|
302
|
+
return this.parseSlasJWT(accessToken).dnt;
|
|
303
|
+
}
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
|
|
269
307
|
/**
|
|
270
308
|
* Return the value of the DNT cookie or undefined if it is not set.
|
|
271
309
|
* The DNT cookie being undefined means that there is a necessity to
|
|
@@ -282,14 +320,8 @@ class Auth {
|
|
|
282
320
|
getDnt(options) {
|
|
283
321
|
const dntCookieVal = this.get(_constant.DNT_COOKIE_NAME);
|
|
284
322
|
let dntCookieStatus = undefined;
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
if (accessToken) {
|
|
288
|
-
const {
|
|
289
|
-
dnt
|
|
290
|
-
} = this.parseSlasJWT(accessToken);
|
|
291
|
-
isInSync = dnt === dntCookieVal;
|
|
292
|
-
}
|
|
323
|
+
const accessTokenDnt = this.getDntFromAccessToken();
|
|
324
|
+
const isInSync = accessTokenDnt === undefined || accessTokenDnt === dntCookieVal;
|
|
293
325
|
if (dntCookieVal !== '1' && dntCookieVal !== '0' || !isInSync) {
|
|
294
326
|
this.delete(_constant.DNT_COOKIE_NAME);
|
|
295
327
|
} else {
|
|
@@ -322,15 +354,8 @@ class Auth {
|
|
|
322
354
|
_this.set(_constant.DNT_COOKIE_NAME, dntCookieVal, _objectSpread(_objectSpread({}, (0, _utils.getDefaultCookieAttributes)()), {}, {
|
|
323
355
|
secure: true
|
|
324
356
|
}));
|
|
325
|
-
const
|
|
326
|
-
if (
|
|
327
|
-
const {
|
|
328
|
-
dnt
|
|
329
|
-
} = _this.parseSlasJWT(accessToken);
|
|
330
|
-
if (dnt !== dntCookieVal) {
|
|
331
|
-
yield _this.refreshAccessToken();
|
|
332
|
-
}
|
|
333
|
-
} else {
|
|
357
|
+
const accessTokenDnt = _this.getDntFromAccessToken();
|
|
358
|
+
if (accessTokenDnt === undefined || accessTokenDnt !== dntCookieVal) {
|
|
334
359
|
yield _this.refreshAccessToken();
|
|
335
360
|
}
|
|
336
361
|
if (preference !== null) {
|
|
@@ -387,6 +412,46 @@ class Auth {
|
|
|
387
412
|
return validTimeSeconds <= tokenAgeSeconds;
|
|
388
413
|
}
|
|
389
414
|
|
|
415
|
+
/**
|
|
416
|
+
* Returns whether a refresh token exists in an HttpOnly cookie. Since JavaScript cannot
|
|
417
|
+
* read HttpOnly cookies, we check the non-HttpOnly indicator cookie (cc-nx-exists) that
|
|
418
|
+
* is set alongside the refresh token with the same expiry.
|
|
419
|
+
*/
|
|
420
|
+
hasHttpOnlyRefreshToken() {
|
|
421
|
+
return this.enableHttpOnlySessionCookies && (0, _utils.onClient)() && this.get('cc-nx-exists') === '1';
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Clears the non-HttpOnly access token expiry cookie (cc-at-expires).
|
|
426
|
+
*
|
|
427
|
+
* This is needed when SCAPI returns a 401 because the HttpOnly access token cookie
|
|
428
|
+
* (cc-at_{siteId}) was deleted externally while the expiry cookie remained valid.
|
|
429
|
+
* Clearing the expiry cookie ensures isAccessTokenExpired() returns true, so
|
|
430
|
+
* subsequent calls to ready() will trigger a refresh instead of assuming the token
|
|
431
|
+
* is still valid.
|
|
432
|
+
*/
|
|
433
|
+
clearAccessTokenExpiry() {
|
|
434
|
+
this.delete('cc-at-expires');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Returns whether the access token is expired. When enableHttpOnlySessionCookies is true,
|
|
439
|
+
* uses cc-at-expires cookie from store; otherwise decodes the JWT from getAccessToken().
|
|
440
|
+
*/
|
|
441
|
+
isAccessTokenExpired() {
|
|
442
|
+
if (this.enableHttpOnlySessionCookies && (0, _utils.onClient)()) {
|
|
443
|
+
const expiresAt = this.get('cc-at-expires');
|
|
444
|
+
if (expiresAt == null || expiresAt === '') return true;
|
|
445
|
+
const expiresAtSec = Number(expiresAt);
|
|
446
|
+
if (Number.isNaN(expiresAtSec)) return true;
|
|
447
|
+
const bufferSeconds = 60;
|
|
448
|
+
return Date.now() / 1000 >= expiresAtSec - bufferSeconds;
|
|
449
|
+
}
|
|
450
|
+
// Server (SSR) or httpOnly disabled: decode JWT from stored token
|
|
451
|
+
const token = this.getAccessToken();
|
|
452
|
+
return !token || this.isTokenExpired(token);
|
|
453
|
+
}
|
|
454
|
+
|
|
390
455
|
/**
|
|
391
456
|
* Returns the SLAS access token or an empty string if the access token
|
|
392
457
|
* is not found in local store or if SFRA wants PWA to trigger refresh token login.
|
|
@@ -543,79 +608,144 @@ class Auth {
|
|
|
543
608
|
handleTokenResponse(res, isGuest) {
|
|
544
609
|
// Delete the SFRA auth token cookie if it exists
|
|
545
610
|
this.clearSFRAAuthToken();
|
|
546
|
-
this.set('access_token', res.access_token);
|
|
547
611
|
this.set('customer_id', res.customer_id);
|
|
548
612
|
this.set('enc_user_id', res.enc_user_id);
|
|
549
613
|
this.set('expires_in', `${res.expires_in}`);
|
|
550
614
|
this.set('id_token', res.id_token);
|
|
551
|
-
this.set('idp_access_token', res.idp_access_token);
|
|
552
615
|
this.set('token_type', res.token_type);
|
|
553
616
|
this.set('customer_type', isGuest ? 'guest' : 'registered');
|
|
554
|
-
const refreshTokenKey = isGuest ? 'refresh_token_guest' : 'refresh_token_registered';
|
|
555
617
|
const refreshTokenTTLValue = this.getRefreshTokenCookieTTLValue(res.refresh_token_expires_in, isGuest);
|
|
556
|
-
if (res.access_token) {
|
|
557
|
-
const {
|
|
558
|
-
uido
|
|
559
|
-
} = this.parseSlasJWT(res.access_token);
|
|
560
|
-
this.set('uido', uido);
|
|
561
|
-
}
|
|
562
|
-
const expiresDate = this.convertSecondsToDate(refreshTokenTTLValue);
|
|
563
618
|
this.set('refresh_token_expires_in', refreshTokenTTLValue.toString());
|
|
564
|
-
this.
|
|
565
|
-
|
|
566
|
-
});
|
|
567
|
-
this.set('usid', res.usid, {
|
|
619
|
+
const expiresDate = this.convertSecondsToDate(refreshTokenTTLValue);
|
|
620
|
+
this.set('usid', res.usid ?? '', {
|
|
568
621
|
expires: expiresDate
|
|
569
622
|
});
|
|
623
|
+
if (this.enableHttpOnlySessionCookies && (0, _utils.onClient)()) {
|
|
624
|
+
// Browser: skip token storage, httpOnly cookies handle it
|
|
625
|
+
const uidoFromCookie = this.stores['cookie'].get('uido');
|
|
626
|
+
if (uidoFromCookie) this.set('uido', uidoFromCookie);
|
|
627
|
+
} else {
|
|
628
|
+
// Server (SSR) or httpOnly disabled: store tokens normally
|
|
629
|
+
this.set('access_token', res.access_token);
|
|
630
|
+
this.set('idp_access_token', res.idp_access_token);
|
|
631
|
+
if (res.access_token) {
|
|
632
|
+
const {
|
|
633
|
+
uido
|
|
634
|
+
} = this.parseSlasJWT(res.access_token);
|
|
635
|
+
this.set('uido', uido);
|
|
636
|
+
}
|
|
637
|
+
const refreshTokenKey = isGuest ? 'refresh_token_guest' : 'refresh_token_registered';
|
|
638
|
+
this.set(refreshTokenKey, res.refresh_token, {
|
|
639
|
+
expires: expiresDate
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
get refreshDedupKey() {
|
|
644
|
+
const params = this.client.clientConfig.parameters;
|
|
645
|
+
return `refresh:${params.siteId}:${params.clientId}`;
|
|
570
646
|
}
|
|
571
647
|
refreshAccessToken() {
|
|
572
648
|
var _this2 = this;
|
|
573
649
|
return _asyncToGenerator(function* () {
|
|
574
|
-
|
|
650
|
+
// Dedup uses a module-level map (not an instance field) because React may recreate
|
|
651
|
+
// the Auth instance on re-renders, giving each instance its own state. The map is
|
|
652
|
+
// keyed by siteId+clientId so different sites/clients remain independent.
|
|
653
|
+
// On the server (SSR), each request is isolated — skip dedup entirely.
|
|
654
|
+
if (!(0, _utils.onClient)()) {
|
|
655
|
+
return yield _this2._refreshAccessToken();
|
|
656
|
+
}
|
|
657
|
+
const key = _this2.refreshDedupKey;
|
|
658
|
+
const existing = pendingRefreshTokens.get(key);
|
|
659
|
+
if (existing) {
|
|
660
|
+
yield existing;
|
|
661
|
+
return _this2.data;
|
|
662
|
+
}
|
|
663
|
+
const promise = _this2._refreshAccessToken().finally(() => {
|
|
664
|
+
pendingRefreshTokens.delete(key);
|
|
665
|
+
});
|
|
666
|
+
pendingRefreshTokens.set(key, promise);
|
|
667
|
+
return yield promise;
|
|
668
|
+
})();
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Internal implementation of the refresh flow. Called only via refreshAccessToken()
|
|
673
|
+
* which wraps it in the module-level pendingRefreshTokens map for deduplication.
|
|
674
|
+
*/
|
|
675
|
+
_refreshAccessToken() {
|
|
676
|
+
var _this3 = this;
|
|
677
|
+
return _asyncToGenerator(function* () {
|
|
678
|
+
const dntPref = _this3.getDnt({
|
|
575
679
|
includeDefaults: true
|
|
576
680
|
});
|
|
577
|
-
const refreshTokenRegistered =
|
|
578
|
-
const refreshTokenGuest =
|
|
681
|
+
const refreshTokenRegistered = _this3.get('refresh_token_registered');
|
|
682
|
+
const refreshTokenGuest = _this3.get('refresh_token_guest');
|
|
579
683
|
const refreshToken = refreshTokenRegistered || refreshTokenGuest;
|
|
580
|
-
|
|
684
|
+
|
|
685
|
+
// When HttpOnly session cookies are enabled on the client, the refresh token is in an
|
|
686
|
+
// HttpOnly cookie that JavaScript cannot read. We check the non-HttpOnly indicator
|
|
687
|
+
// cookie (cc-nx-exists) to avoid a wasted round-trip when the refresh token is absent.
|
|
688
|
+
// If cc-nx-exists is also missing (e.g. cleared by the user), the proxy layer will
|
|
689
|
+
// catch the missing refresh token and return a 401, falling through to guest login.
|
|
690
|
+
if (refreshToken || !refreshToken && _this3.hasHttpOnlyRefreshToken()) {
|
|
581
691
|
try {
|
|
582
|
-
|
|
583
|
-
|
|
692
|
+
const isGuest = _this3.get('customer_type') !== 'registered';
|
|
693
|
+
// Signal the proxy that this is a refresh token request so it can
|
|
694
|
+
// inject the HttpOnly refresh token cookie as the sfdc_refresh_token header.
|
|
695
|
+
if (_this3.enableHttpOnlySessionCookies) {
|
|
696
|
+
_this3.client.clientConfig.headers[_constant.X_GRANT_TYPE] = 'refresh_token';
|
|
697
|
+
}
|
|
698
|
+
const token = yield _commerceSdkIsomorphic.helpers.refreshAccessToken({
|
|
699
|
+
slasClient: _this3.client,
|
|
584
700
|
parameters: {
|
|
585
|
-
refreshToken,
|
|
701
|
+
refreshToken: refreshToken || '',
|
|
586
702
|
dnt: dntPref
|
|
587
703
|
},
|
|
588
704
|
credentials: {
|
|
589
|
-
clientSecret:
|
|
590
|
-
}
|
|
591
|
-
|
|
705
|
+
clientSecret: _this3.clientSecret
|
|
706
|
+
},
|
|
707
|
+
enableHttpOnlySessionCookies: _this3.enableHttpOnlySessionCookies
|
|
708
|
+
});
|
|
709
|
+
_this3.handleTokenResponse(token, isGuest);
|
|
710
|
+
return _this3.data;
|
|
592
711
|
} catch (error) {
|
|
593
|
-
// If the refresh token is invalid, we need to re-login the user
|
|
712
|
+
// If the refresh token is invalid, we need to re-login the user.
|
|
594
713
|
if (error instanceof Error && 'response' in error) {
|
|
595
714
|
// commerce-sdk-isomorphic throws a `ResponseError`, but doesn't export the class.
|
|
596
715
|
// We can't use `instanceof`, so instead we just check for the `response` property
|
|
597
716
|
// and assume it is a fetch Response.
|
|
598
717
|
const json = yield error['response'].json();
|
|
599
718
|
if (json.message === 'invalid refresh_token') {
|
|
600
|
-
//
|
|
601
|
-
|
|
719
|
+
// In a multi-tab scenario, another tab may have already consumed the
|
|
720
|
+
// one-time-use refresh token and stored fresh tokens. Re-check storage
|
|
721
|
+
// before clearing — if a valid access token exists, use it instead of
|
|
722
|
+
// wiping the other tab's work and falling back to guest login.
|
|
723
|
+
if (!_this3.isAccessTokenExpired()) {
|
|
724
|
+
return _this3.data;
|
|
725
|
+
}
|
|
726
|
+
// No valid token found — clean up storage and restart the login flow.
|
|
727
|
+
_this3.clearStorage();
|
|
602
728
|
}
|
|
603
729
|
}
|
|
730
|
+
} finally {
|
|
731
|
+
delete _this3.client.clientConfig.headers[_constant.X_GRANT_TYPE];
|
|
604
732
|
}
|
|
605
733
|
}
|
|
606
734
|
|
|
607
735
|
// refresh flow for TAOB
|
|
608
|
-
const accessToken =
|
|
609
|
-
if (
|
|
736
|
+
const accessToken = _this3.getAccessToken();
|
|
737
|
+
if (_this3.isAccessTokenExpired()) {
|
|
610
738
|
try {
|
|
611
739
|
const {
|
|
612
740
|
isGuest,
|
|
613
741
|
usid,
|
|
614
742
|
loginId,
|
|
615
743
|
isAgent
|
|
616
|
-
} =
|
|
744
|
+
} = _this3.parseSlasJWT(accessToken);
|
|
617
745
|
if (isAgent) {
|
|
618
|
-
|
|
746
|
+
const token = yield _this3.refreshTrustedAgent(loginId, usid);
|
|
747
|
+
_this3.handleTokenResponse(token, isGuest);
|
|
748
|
+
return _this3.data;
|
|
619
749
|
}
|
|
620
750
|
} catch (e) {
|
|
621
751
|
/* catch invalid jwt */
|
|
@@ -626,39 +756,14 @@ class Auth {
|
|
|
626
756
|
// use it, we will be stuck in a fail loop
|
|
627
757
|
let token;
|
|
628
758
|
try {
|
|
629
|
-
token = yield
|
|
759
|
+
token = yield _this3.loginGuestUser();
|
|
630
760
|
} catch (e) {
|
|
631
|
-
|
|
632
|
-
token = yield
|
|
761
|
+
_this3.clearStorage();
|
|
762
|
+
token = yield _this3.loginGuestUser();
|
|
633
763
|
}
|
|
634
764
|
return token;
|
|
635
765
|
})();
|
|
636
766
|
}
|
|
637
|
-
|
|
638
|
-
/**
|
|
639
|
-
* This method queues the requests and handles the SLAS token response.
|
|
640
|
-
*
|
|
641
|
-
* It returns the queue.
|
|
642
|
-
*
|
|
643
|
-
* @Internal
|
|
644
|
-
*/
|
|
645
|
-
queueRequest(fn, isGuest) {
|
|
646
|
-
var _this3 = this;
|
|
647
|
-
return _asyncToGenerator(function* () {
|
|
648
|
-
const queue = _this3.pendingToken ?? Promise.resolve();
|
|
649
|
-
_this3.pendingToken = queue.then(/*#__PURE__*/_asyncToGenerator(function* () {
|
|
650
|
-
const token = yield fn();
|
|
651
|
-
_this3.handleTokenResponse(token, isGuest);
|
|
652
|
-
// Q: Why don't we just return token? Why re-construct the same object again?
|
|
653
|
-
// A: because a user could open multiple tabs and the data in memory could be out-dated
|
|
654
|
-
// We must always grab the data from the storage (cookie/localstorage) directly
|
|
655
|
-
return _this3.data;
|
|
656
|
-
})).finally(() => {
|
|
657
|
-
_this3.pendingToken = undefined;
|
|
658
|
-
});
|
|
659
|
-
return yield _this3.pendingToken;
|
|
660
|
-
})();
|
|
661
|
-
}
|
|
662
767
|
logWarning = msg => {
|
|
663
768
|
if (!this.silenceWarnings) {
|
|
664
769
|
this.logger.warn(msg);
|
|
@@ -681,7 +786,7 @@ class Auth {
|
|
|
681
786
|
* @Internal
|
|
682
787
|
*/
|
|
683
788
|
extractResponseError = (() => function () {
|
|
684
|
-
var
|
|
789
|
+
var _ref = _asyncToGenerator(function* (error) {
|
|
685
790
|
// the regular error.message will return only the generic status code message
|
|
686
791
|
// ie. 'Bad Request' for 400. We need to drill specifically into the ResponseError
|
|
687
792
|
// to get a more descriptive error message from SLAS
|
|
@@ -697,7 +802,7 @@ class Auth {
|
|
|
697
802
|
throw error;
|
|
698
803
|
});
|
|
699
804
|
return function (_x) {
|
|
700
|
-
return
|
|
805
|
+
return _ref.apply(this, arguments);
|
|
701
806
|
};
|
|
702
807
|
}())();
|
|
703
808
|
|
|
@@ -751,11 +856,14 @@ class Auth {
|
|
|
751
856
|
_this4.set('customer_type', isGuest ? 'guest' : 'registered');
|
|
752
857
|
return _this4.data;
|
|
753
858
|
}
|
|
754
|
-
if (
|
|
755
|
-
|
|
859
|
+
if ((0, _utils.onClient)()) {
|
|
860
|
+
const pendingRefresh = pendingRefreshTokens.get(_this4.refreshDedupKey);
|
|
861
|
+
if (pendingRefresh) {
|
|
862
|
+
yield pendingRefresh;
|
|
863
|
+
return _this4.data;
|
|
864
|
+
}
|
|
756
865
|
}
|
|
757
|
-
|
|
758
|
-
if (accessToken && !_this4.isTokenExpired(accessToken)) {
|
|
866
|
+
if (!_this4.isAccessTokenExpired()) {
|
|
759
867
|
return _this4.data;
|
|
760
868
|
}
|
|
761
869
|
return yield _this4.refreshAccessToken();
|
|
@@ -810,9 +918,16 @@ class Auth {
|
|
|
810
918
|
usid
|
|
811
919
|
}), parameters)
|
|
812
920
|
};
|
|
813
|
-
const
|
|
921
|
+
const enableHttpOnlySessionCookies = _this6.enableHttpOnlySessionCookies;
|
|
922
|
+
const callback = _this6.clientSecret ? () => _commerceSdkIsomorphic.helpers.loginGuestUserPrivate(_objectSpread(_objectSpread({}, guestPrivateArgs), {}, {
|
|
923
|
+
enableHttpOnlySessionCookies
|
|
924
|
+
})) : () => _commerceSdkIsomorphic.helpers.loginGuestUser(_objectSpread(_objectSpread({}, guestPublicArgs), {}, {
|
|
925
|
+
enableHttpOnlySessionCookies
|
|
926
|
+
}));
|
|
814
927
|
try {
|
|
815
|
-
|
|
928
|
+
const token = yield callback();
|
|
929
|
+
_this6.handleTokenResponse(token, isGuest);
|
|
930
|
+
return _this6.data;
|
|
816
931
|
} catch (error) {
|
|
817
932
|
// We catch the error here to do logging but we still need to
|
|
818
933
|
// throw an error to stop the login flow from continuing.
|
|
@@ -908,7 +1023,8 @@ class Auth {
|
|
|
908
1023
|
}, usid && {
|
|
909
1024
|
usid
|
|
910
1025
|
}),
|
|
911
|
-
body: customParameters
|
|
1026
|
+
body: customParameters,
|
|
1027
|
+
enableHttpOnlySessionCookies: _this8.enableHttpOnlySessionCookies
|
|
912
1028
|
};
|
|
913
1029
|
const token = yield _commerceSdkIsomorphic.helpers.loginRegisteredUserB2C(loginParams);
|
|
914
1030
|
_this8.handleTokenResponse(token, isGuest);
|
|
@@ -1013,14 +1129,23 @@ class Auth {
|
|
|
1013
1129
|
var _this10 = this;
|
|
1014
1130
|
return _asyncToGenerator(function* () {
|
|
1015
1131
|
if (_this10.get('customer_type') === 'registered') {
|
|
1016
|
-
|
|
1017
|
-
void _commerceSdkIsomorphic.helpers.logout({
|
|
1132
|
+
const logoutPromise = _commerceSdkIsomorphic.helpers.logout({
|
|
1018
1133
|
slasClient: _this10.client,
|
|
1019
1134
|
parameters: {
|
|
1020
1135
|
accessToken: _this10.get('access_token'),
|
|
1021
1136
|
refreshToken: _this10.get('refresh_token_registered')
|
|
1022
1137
|
}
|
|
1023
1138
|
});
|
|
1139
|
+
if (_this10.enableHttpOnlySessionCookies) {
|
|
1140
|
+
// When HttpOnly cookies are enabled, the proxy expires session cookies
|
|
1141
|
+
// on the logout response. We must await so the browser processes the
|
|
1142
|
+
// Set-Cookie headers before guest login sets new cookies.
|
|
1143
|
+
try {
|
|
1144
|
+
yield logoutPromise;
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
_this10.logger.warn(`SLAS logout failed: ${error instanceof Error ? error.message : String(error)}. The error is ignored and session cookies are still cleared by the proxy.`);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1024
1149
|
}
|
|
1025
1150
|
_this10.clearStorage();
|
|
1026
1151
|
return yield _this10.ready();
|
|
@@ -1142,7 +1267,8 @@ class Auth {
|
|
|
1142
1267
|
dnt: dntPref
|
|
1143
1268
|
}, usid && {
|
|
1144
1269
|
usid
|
|
1145
|
-
})
|
|
1270
|
+
}),
|
|
1271
|
+
enableHttpOnlySessionCookies: _this13.enableHttpOnlySessionCookies
|
|
1146
1272
|
});
|
|
1147
1273
|
const isGuest = false;
|
|
1148
1274
|
_this13.handleTokenResponse(token, isGuest);
|
|
@@ -1223,7 +1349,8 @@ class Auth {
|
|
|
1223
1349
|
usid
|
|
1224
1350
|
}), parameters.register_customer !== undefined && {
|
|
1225
1351
|
register_customer: typeof parameters.register_customer === 'boolean' ? String(parameters.register_customer) : parameters.register_customer
|
|
1226
|
-
})
|
|
1352
|
+
}),
|
|
1353
|
+
enableHttpOnlySessionCookies: _this15.enableHttpOnlySessionCookies
|
|
1227
1354
|
});
|
|
1228
1355
|
const isGuest = false;
|
|
1229
1356
|
_this15.handleTokenResponse(token, isGuest);
|
|
@@ -1313,6 +1440,28 @@ class Auth {
|
|
|
1313
1440
|
})();
|
|
1314
1441
|
}
|
|
1315
1442
|
|
|
1443
|
+
/**
|
|
1444
|
+
* Get the current USID for Storefront Preview by forcing a SLAS refresh.
|
|
1445
|
+
*
|
|
1446
|
+
* Works for both guest and registered shoppers: when an existing refresh
|
|
1447
|
+
* token is present (guest or registered, legacy or HttpOnly), the SLAS
|
|
1448
|
+
* response provides a fresh USID. When no refresh token is present,
|
|
1449
|
+
* `refreshAccessToken()` falls through to a guest login, which also yields
|
|
1450
|
+
* a fresh USID. Preview can therefore always obtain a USID without
|
|
1451
|
+
* requiring the shopper to sign in.
|
|
1452
|
+
*/
|
|
1453
|
+
getUsidForPreview() {
|
|
1454
|
+
var _this18 = this;
|
|
1455
|
+
return _asyncToGenerator(function* () {
|
|
1456
|
+
yield _this18.refreshAccessToken();
|
|
1457
|
+
const usid = _this18.get('usid');
|
|
1458
|
+
if (!usid) {
|
|
1459
|
+
throw new Error('SLAS refresh did not return a USID');
|
|
1460
|
+
}
|
|
1461
|
+
return usid;
|
|
1462
|
+
})();
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1316
1465
|
/**
|
|
1317
1466
|
* Decode SLAS JWT and extract information such as customer id, usid, etc.
|
|
1318
1467
|
*
|
|
@@ -35,8 +35,8 @@ const PWA_KIT_PATH_PREFIX = '/__pwa-kit/';
|
|
|
35
35
|
/**
|
|
36
36
|
* Runtime Admin always prepends envBasePath to /__pwa-kit/ paths (e.g. /test/__pwa-kit/refresh),
|
|
37
37
|
* but when showBasePath is false, React Router has no basename and expects /__pwa-kit/refresh.
|
|
38
|
-
*
|
|
39
|
-
* This ensures that regardless of the showBasePath setting, these paths are normalized to
|
|
38
|
+
*
|
|
39
|
+
* This ensures that regardless of the showBasePath setting, these paths are normalized to
|
|
40
40
|
* remove the base path.
|
|
41
41
|
*/
|
|
42
42
|
function normalizePwaKitPath(pathOrLocation) {
|
|
@@ -92,10 +92,14 @@ const StorefrontPreview = ({
|
|
|
92
92
|
const {
|
|
93
93
|
siteId
|
|
94
94
|
} = (0, _hooks.useConfig)();
|
|
95
|
+
const {
|
|
96
|
+
getUsidForPreview
|
|
97
|
+
} = (0, _hooks.useUsid)();
|
|
95
98
|
(0, _react.useEffect)(() => {
|
|
96
99
|
if (enabled && isHostTrusted) {
|
|
97
100
|
window.STOREFRONT_PREVIEW = _objectSpread(_objectSpread({}, window.STOREFRONT_PREVIEW), {}, {
|
|
98
101
|
getToken,
|
|
102
|
+
getUsid: getUsidForPreview,
|
|
99
103
|
onContextChange,
|
|
100
104
|
siteId,
|
|
101
105
|
experimentalUnsafeNavigate: (path, action = 'push', ...args) => {
|
|
@@ -106,7 +110,7 @@ const StorefrontPreview = ({
|
|
|
106
110
|
}
|
|
107
111
|
});
|
|
108
112
|
}
|
|
109
|
-
}, [enabled, getToken, onContextChange, siteId, getBasePath]);
|
|
113
|
+
}, [enabled, getToken, getUsidForPreview, onContextChange, siteId, getBasePath]);
|
|
110
114
|
(0, _react.useEffect)(() => {
|
|
111
115
|
if (enabled && isHostTrusted) {
|
|
112
116
|
// In Storefront Preview mode, add cache breaker for all SCAPI's requests.
|
package/constant.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export declare const EXCLUDE_COOKIE_SUFFIX: string[];
|
|
|
20
20
|
* Use the header key below to send dwsid value with SCAPI/OCAPI requests.
|
|
21
21
|
*/
|
|
22
22
|
export declare const SERVER_AFFINITY_HEADER_KEY = "sfdc_dwsid";
|
|
23
|
+
export declare const X_GRANT_TYPE = "x-grant-type";
|
|
23
24
|
export declare const CLIENT_KEYS: {
|
|
24
25
|
readonly SHOPPER_BASKETS: "shopperBaskets";
|
|
25
26
|
readonly SHOPPER_BASKETS_V2: "shopperBasketsV2";
|
package/constant.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.SLAS_SECRET_WARNING_MSG = exports.SLAS_SECRET_PLACEHOLDER = exports.SLAS_SECRET_OVERRIDE_MSG = exports.SLAS_REFRESH_TOKEN_COOKIE_TTL_OVERRIDE_MSG = exports.SERVER_AFFINITY_HEADER_KEY = exports.IFRAME_HOST_ALLOW_LIST = exports.EXCLUDE_COOKIE_SUFFIX = exports.DWSID_COOKIE_NAME = exports.DNT_COOKIE_NAME = exports.CLIENT_KEYS = void 0;
|
|
6
|
+
exports.X_GRANT_TYPE = exports.SLAS_SECRET_WARNING_MSG = exports.SLAS_SECRET_PLACEHOLDER = exports.SLAS_SECRET_OVERRIDE_MSG = exports.SLAS_REFRESH_TOKEN_COOKIE_TTL_OVERRIDE_MSG = exports.SERVER_AFFINITY_HEADER_KEY = exports.IFRAME_HOST_ALLOW_LIST = exports.EXCLUDE_COOKIE_SUFFIX = exports.DWSID_COOKIE_NAME = exports.DNT_COOKIE_NAME = exports.CLIENT_KEYS = void 0;
|
|
7
7
|
/*
|
|
8
8
|
* Copyright (c) 2025, Salesforce, Inc.
|
|
9
9
|
* All rights reserved.
|
|
@@ -36,6 +36,9 @@ const EXCLUDE_COOKIE_SUFFIX = exports.EXCLUDE_COOKIE_SUFFIX = [DWSID_COOKIE_NAME
|
|
|
36
36
|
* Use the header key below to send dwsid value with SCAPI/OCAPI requests.
|
|
37
37
|
*/
|
|
38
38
|
const SERVER_AFFINITY_HEADER_KEY = exports.SERVER_AFFINITY_HEADER_KEY = 'sfdc_dwsid';
|
|
39
|
+
|
|
40
|
+
// Custom header sent by the SDK to signal a refresh token request to the proxy.
|
|
41
|
+
const X_GRANT_TYPE = exports.X_GRANT_TYPE = 'x-grant-type';
|
|
39
42
|
const CLIENT_KEYS = exports.CLIENT_KEYS = {
|
|
40
43
|
SHOPPER_BASKETS: 'shopperBaskets',
|
|
41
44
|
SHOPPER_BASKETS_V2: 'shopperBasketsV2',
|
package/hooks/helpers.d.ts
CHANGED
|
@@ -9,22 +9,7 @@ import { CustomEndpointArg, OptionalCustomEndpointClientConfig, TMutationVariabl
|
|
|
9
9
|
* @param error - the error
|
|
10
10
|
* @returns a new guest access token
|
|
11
11
|
*/
|
|
12
|
-
export declare const handleInvalidToken: (error: any, auth: Auth, logger: Logger) => Promise<
|
|
13
|
-
access_token: string;
|
|
14
|
-
id_token: string;
|
|
15
|
-
refresh_token: string;
|
|
16
|
-
expires_in: number;
|
|
17
|
-
refresh_token_expires_in: number;
|
|
18
|
-
token_type: "Bearer";
|
|
19
|
-
usid: string;
|
|
20
|
-
customer_id: string;
|
|
21
|
-
enc_user_id: string;
|
|
22
|
-
idp_access_token: string;
|
|
23
|
-
idp_refresh_token?: string | undefined;
|
|
24
|
-
dnt?: string | undefined;
|
|
25
|
-
} & {
|
|
26
|
-
[key: string]: any;
|
|
27
|
-
}>;
|
|
12
|
+
export declare const handleInvalidToken: (error: any, auth: Auth, logger: Logger) => Promise<import("../auth").AuthData>;
|
|
28
13
|
/**
|
|
29
14
|
* A helper function for preparing a call to the SCAPI custom API endpoint
|
|
30
15
|
*/
|
package/hooks/helpers.js
CHANGED
|
@@ -4,20 +4,19 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.handleInvalidToken = exports.generateCustomEndpointOptions = void 0;
|
|
7
|
+
var _utils = require("../utils");
|
|
7
8
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
8
9
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
9
10
|
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
10
11
|
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
11
12
|
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
12
13
|
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
13
|
-
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
14
|
-
/*
|
|
14
|
+
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; } /*
|
|
15
15
|
* Copyright (c) 2024, Salesforce, Inc.
|
|
16
16
|
* All rights reserved.
|
|
17
17
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
18
18
|
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
19
19
|
*/
|
|
20
|
-
|
|
21
20
|
/**
|
|
22
21
|
* A helper function for handling bad responses from SCAPI when an invalid access token is used.
|
|
23
22
|
*
|
|
@@ -27,16 +26,30 @@ function _asyncToGenerator(n) { return function () { var t = this, e = arguments
|
|
|
27
26
|
*/
|
|
28
27
|
const handleInvalidToken = exports.handleInvalidToken = /*#__PURE__*/function () {
|
|
29
28
|
var _ref = _asyncToGenerator(function* (error, auth, logger) {
|
|
30
|
-
var _error$response, _error$
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
var _error$response, _error$response3, _error$response4;
|
|
30
|
+
// The proxy returns a 400 with this message when the HttpOnly access token cookie
|
|
31
|
+
// (cc-at_{siteId}) is missing. This can happen if the cookie was deleted externally
|
|
32
|
+
// (e.g. via dev tools) while the non-HttpOnly expiry cookie (cc-at-expires) remained
|
|
33
|
+
// valid, causing isAccessTokenExpired() to incorrectly report the token as not expired.
|
|
34
|
+
// Clear the stale expiry cookie and trigger a token refresh.
|
|
35
|
+
if ((error === null || error === void 0 ? void 0 : (_error$response = error.response) === null || _error$response === void 0 ? void 0 : _error$response.status) === 400) {
|
|
36
|
+
var _error$response2;
|
|
37
|
+
const response = yield error === null || error === void 0 ? void 0 : (_error$response2 = error.response) === null || _error$response2 === void 0 ? void 0 : _error$response2.json();
|
|
38
|
+
if ((response === null || response === void 0 ? void 0 : response.message) === 'access_token_cookie_missing') {
|
|
39
|
+
logger.warn('Access token cookie missing. Clearing expiry and refreshing token.');
|
|
40
|
+
auth.clearAccessTokenExpiry();
|
|
41
|
+
return yield auth.refreshAccessToken();
|
|
42
|
+
}
|
|
33
43
|
}
|
|
34
|
-
|
|
35
|
-
if ((response === null || response === void 0 ? void 0 : response.detail) !== 'Customer credentials changed after token was issued.') {
|
|
44
|
+
if ((error === null || error === void 0 ? void 0 : (_error$response3 = error.response) === null || _error$response3 === void 0 ? void 0 : _error$response3.status) !== 401) {
|
|
36
45
|
throw error;
|
|
37
46
|
}
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
const response = yield error === null || error === void 0 ? void 0 : (_error$response4 = error.response) === null || _error$response4 === void 0 ? void 0 : _error$response4.json();
|
|
48
|
+
if ((response === null || response === void 0 ? void 0 : response.detail) === 'Customer credentials changed after token was issued.') {
|
|
49
|
+
logger.info('Login was invalidated. Clearing login state.');
|
|
50
|
+
return yield auth.logout();
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
40
53
|
});
|
|
41
54
|
return function handleInvalidToken(_x, _x2, _x3) {
|
|
42
55
|
return _ref.apply(this, arguments);
|
|
@@ -62,9 +75,9 @@ const generateCustomEndpointOptions = (options, config, access_token, args) => {
|
|
|
62
75
|
return _objectSpread(_objectSpread({}, options), {}, {
|
|
63
76
|
options: _objectSpread(_objectSpread({}, options.options), {}, {
|
|
64
77
|
method: ((_options$options = options.options) === null || _options$options === void 0 ? void 0 : _options$options.method) || 'GET',
|
|
65
|
-
headers: _objectSpread(_objectSpread(_objectSpread({
|
|
78
|
+
headers: _objectSpread(_objectSpread(_objectSpread(_objectSpread({}, config.enableHttpOnlySessionCookies && (0, _utils.onClient)() ? {} : {
|
|
66
79
|
Authorization: `Bearer ${access_token}`
|
|
67
|
-
}, globalHeaders), (_options$options2 = options.options) === null || _options$options2 === void 0 ? void 0 : _options$options2.headers), args !== null && args !== void 0 && args.headers ? args.headers : {})
|
|
80
|
+
}), globalHeaders), (_options$options2 = options.options) === null || _options$options2 === void 0 ? void 0 : _options$options2.headers), args !== null && args !== void 0 && args.headers ? args.headers : {})
|
|
68
81
|
}),
|
|
69
82
|
clientConfig: _objectSpread(_objectSpread({}, globalClientConfig), options.clientConfig || {})
|
|
70
83
|
});
|
|
@@ -7,6 +7,7 @@ exports.useAuthorizationHeader = void 0;
|
|
|
7
7
|
var _useAuthContext = _interopRequireDefault(require("./useAuthContext"));
|
|
8
8
|
var _useConfig = _interopRequireDefault(require("./useConfig"));
|
|
9
9
|
var _helpers = require("./helpers");
|
|
10
|
+
var _utils = require("../utils");
|
|
10
11
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
12
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
12
13
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
@@ -38,21 +39,25 @@ const useAuthorizationHeader = method => {
|
|
|
38
39
|
const {
|
|
39
40
|
access_token
|
|
40
41
|
} = yield auth.ready();
|
|
42
|
+
// When HttpOnly session cookies are enabled on the client, the proxy injects the
|
|
43
|
+
// Authorization header from the cookie — skip adding it here.
|
|
44
|
+
const authHeaders = config.enableHttpOnlySessionCookies && (0, _utils.onClient)() ? {} : {
|
|
45
|
+
Authorization: `Bearer ${access_token}`
|
|
46
|
+
};
|
|
41
47
|
return yield method(_objectSpread(_objectSpread({}, options), {}, {
|
|
42
|
-
headers: _objectSpread({
|
|
43
|
-
Authorization: `Bearer ${access_token}`
|
|
44
|
-
}, options.headers)
|
|
48
|
+
headers: _objectSpread(_objectSpread({}, authHeaders), options.headers)
|
|
45
49
|
})).catch(/*#__PURE__*/function () {
|
|
46
50
|
var _ref2 = _asyncToGenerator(function* (error) {
|
|
47
51
|
const {
|
|
48
52
|
access_token
|
|
49
53
|
} = yield (0, _helpers.handleInvalidToken)(error, auth, logger);
|
|
54
|
+
const retryAuthHeaders = config.enableHttpOnlySessionCookies && (0, _utils.onClient)() ? {} : {
|
|
55
|
+
Authorization: `Bearer ${access_token}`
|
|
56
|
+
};
|
|
50
57
|
|
|
51
58
|
// Retry again after resetting auth state
|
|
52
59
|
return yield method(_objectSpread(_objectSpread({}, options), {}, {
|
|
53
|
-
headers: _objectSpread({
|
|
54
|
-
Authorization: `Bearer ${access_token}`
|
|
55
|
-
}, options.headers)
|
|
60
|
+
headers: _objectSpread(_objectSpread({}, retryAuthHeaders), options.headers)
|
|
56
61
|
}));
|
|
57
62
|
});
|
|
58
63
|
return function (_x2) {
|
package/hooks/useUsid.d.ts
CHANGED
package/hooks/useUsid.js
CHANGED
|
@@ -35,9 +35,11 @@ const useUsid = () => {
|
|
|
35
35
|
const getUsidWhenReady = () => auth.ready().then(({
|
|
36
36
|
usid
|
|
37
37
|
}) => usid);
|
|
38
|
+
const getUsidForPreview = () => auth.getUsidForPreview();
|
|
38
39
|
return {
|
|
39
40
|
usid,
|
|
40
|
-
getUsidWhenReady
|
|
41
|
+
getUsidWhenReady,
|
|
42
|
+
getUsidForPreview
|
|
41
43
|
};
|
|
42
44
|
};
|
|
43
45
|
var _default = exports.default = useUsid;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/commerce-sdk-react",
|
|
3
|
-
"version": "5.2.0-
|
|
3
|
+
"version": "5.2.0-preview.0",
|
|
4
4
|
"description": "A library that provides react hooks for fetching data from Commerce Cloud",
|
|
5
5
|
"homepage": "https://github.com/SalesforceCommerceCloud/pwa-kit/tree/develop/packages/ecom-react-hooks#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@salesforce/storefront-next-runtime": "0.1.1",
|
|
44
|
-
"commerce-sdk-isomorphic": "5.
|
|
44
|
+
"commerce-sdk-isomorphic": "5.2.0",
|
|
45
45
|
"js-cookie": "^3.0.1",
|
|
46
46
|
"jwt-decode": "^4.0.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@salesforce/pwa-kit-dev": "3.18.0-
|
|
49
|
+
"@salesforce/pwa-kit-dev": "3.18.0-preview.0",
|
|
50
50
|
"@tanstack/react-query": "^4.28.0",
|
|
51
51
|
"@testing-library/jest-dom": "^5.16.5",
|
|
52
52
|
"@testing-library/react": "^14.0.0",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@types/react-helmet": "~6.1.6",
|
|
62
62
|
"@types/react-router-dom": "~5.3.3",
|
|
63
63
|
"cross-env": "^5.2.1",
|
|
64
|
-
"internal-lib-build": "3.18.0-
|
|
64
|
+
"internal-lib-build": "3.18.0-preview.0",
|
|
65
65
|
"jsonwebtoken": "^9.0.0",
|
|
66
66
|
"nock": "^13.3.0",
|
|
67
67
|
"nodemon": "^2.0.22",
|
|
@@ -97,5 +97,5 @@
|
|
|
97
97
|
"publishConfig": {
|
|
98
98
|
"directory": "dist"
|
|
99
99
|
},
|
|
100
|
-
"gitHead": "
|
|
100
|
+
"gitHead": "75dd6afbf5269009754337c2cddc5bc0c508e3b3"
|
|
101
101
|
}
|
package/provider.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface CommerceApiProviderProps extends ApiClientConfigParams {
|
|
|
19
19
|
fetchedToken?: string;
|
|
20
20
|
enablePWAKitPrivateClient?: boolean;
|
|
21
21
|
privateClientProxyEndpoint?: string;
|
|
22
|
+
publicClientProxyEndpoint?: string;
|
|
22
23
|
clientSecret?: string;
|
|
23
24
|
silenceWarnings?: boolean;
|
|
24
25
|
logger?: Logger;
|
|
@@ -31,6 +32,8 @@ export interface CommerceApiProviderProps extends ApiClientConfigParams {
|
|
|
31
32
|
hybridAuthEnabled?: boolean;
|
|
32
33
|
cookieDomain?: string;
|
|
33
34
|
pageDesignerParams?: PageDesignerParams;
|
|
35
|
+
/** When true, proxy returns tokens in HttpOnly cookies. */
|
|
36
|
+
enableHttpOnlySessionCookies?: boolean;
|
|
34
37
|
}
|
|
35
38
|
/**
|
|
36
39
|
* @internal
|
package/provider.js
CHANGED
|
@@ -108,6 +108,7 @@ const CommerceApiProvider = props => {
|
|
|
108
108
|
fetchedToken,
|
|
109
109
|
enablePWAKitPrivateClient,
|
|
110
110
|
privateClientProxyEndpoint,
|
|
111
|
+
publicClientProxyEndpoint,
|
|
111
112
|
clientSecret,
|
|
112
113
|
silenceWarnings,
|
|
113
114
|
logger,
|
|
@@ -119,11 +120,32 @@ const CommerceApiProvider = props => {
|
|
|
119
120
|
disableAuthInit = false,
|
|
120
121
|
hybridAuthEnabled = false,
|
|
121
122
|
cookieDomain,
|
|
122
|
-
pageDesignerParams = {}
|
|
123
|
+
pageDesignerParams = {},
|
|
124
|
+
enableHttpOnlySessionCookies = false
|
|
123
125
|
} = props;
|
|
124
126
|
|
|
125
127
|
// Set the logger based on provided configuration, or default to the console object if no logger is provided
|
|
126
128
|
const configLogger = logger || console;
|
|
129
|
+
|
|
130
|
+
// Stabilize object references that may be recreated on every render (e.g. inline
|
|
131
|
+
// `headers={{...}}` or `logger={createLogger(...)}` in the parent component).
|
|
132
|
+
// Without this, the Auth useMemo would recreate the Auth instance on every render,
|
|
133
|
+
// causing unnecessary useEffect re-runs, context re-renders, and breaking
|
|
134
|
+
// request deduplication.
|
|
135
|
+
const headersKey = JSON.stringify(headers);
|
|
136
|
+
const stableHeaders = (0, _react.useMemo)(() => headers, [headersKey]);
|
|
137
|
+
const loggerRef = (0, _react.useRef)(configLogger);
|
|
138
|
+
loggerRef.current = configLogger;
|
|
139
|
+
// Logger identity is not meaningful — keep the first instance for reference stability.
|
|
140
|
+
// The ref ensures the Auth instance always calls the latest logger.
|
|
141
|
+
const stableLogger = (0, _react.useMemo)(() => loggerRef.current, []);
|
|
142
|
+
|
|
143
|
+
// When HttpOnly cookies are enabled, ensure fetch credentials allow cookies to be sent.
|
|
144
|
+
const effectiveFetchOptions = (0, _react.useMemo)(() => {
|
|
145
|
+
return enableHttpOnlySessionCookies && (!(fetchOptions !== null && fetchOptions !== void 0 && fetchOptions.credentials) || fetchOptions.credentials === 'omit') ? _objectSpread(_objectSpread({}, fetchOptions), {}, {
|
|
146
|
+
credentials: 'same-origin'
|
|
147
|
+
}) : fetchOptions;
|
|
148
|
+
}, [enableHttpOnlySessionCookies, fetchOptions]);
|
|
127
149
|
const auth = (0, _react.useMemo)(() => {
|
|
128
150
|
return new _auth.default({
|
|
129
151
|
clientId,
|
|
@@ -132,22 +154,23 @@ const CommerceApiProvider = props => {
|
|
|
132
154
|
siteId,
|
|
133
155
|
proxy,
|
|
134
156
|
redirectURI,
|
|
135
|
-
headers,
|
|
136
|
-
fetchOptions,
|
|
157
|
+
headers: stableHeaders,
|
|
158
|
+
fetchOptions: effectiveFetchOptions,
|
|
137
159
|
fetchedToken,
|
|
138
160
|
enablePWAKitPrivateClient,
|
|
139
161
|
privateClientProxyEndpoint,
|
|
162
|
+
publicClientProxyEndpoint,
|
|
140
163
|
clientSecret,
|
|
141
164
|
silenceWarnings,
|
|
142
|
-
logger:
|
|
165
|
+
logger: stableLogger,
|
|
143
166
|
defaultDnt,
|
|
144
167
|
passwordlessLoginCallbackURI,
|
|
145
168
|
refreshTokenRegisteredCookieTTL,
|
|
146
169
|
refreshTokenGuestCookieTTL,
|
|
147
170
|
hybridAuthEnabled,
|
|
148
|
-
|
|
171
|
+
enableHttpOnlySessionCookies
|
|
149
172
|
});
|
|
150
|
-
}, [clientId, organizationId, shortCode, siteId, proxy, redirectURI,
|
|
173
|
+
}, [clientId, organizationId, shortCode, siteId, proxy, redirectURI, stableHeaders, effectiveFetchOptions, fetchedToken, enablePWAKitPrivateClient, privateClientProxyEndpoint, publicClientProxyEndpoint, clientSecret, silenceWarnings, stableLogger, defaultDnt, passwordlessLoginCallbackURI, refreshTokenRegisteredCookieTTL, refreshTokenGuestCookieTTL, apiClients, hybridAuthEnabled, cookieDomain, enableHttpOnlySessionCookies]);
|
|
151
174
|
const dwsid = auth.get(_constant.DWSID_COOKIE_NAME);
|
|
152
175
|
const serverAffinityHeader = {};
|
|
153
176
|
if (dwsid) {
|
|
@@ -157,7 +180,7 @@ const CommerceApiProvider = props => {
|
|
|
157
180
|
return _objectSpread(_objectSpread({}, options), {}, {
|
|
158
181
|
headers: _objectSpread(_objectSpread({}, options.headers), serverAffinityHeader),
|
|
159
182
|
throwOnBadResponse: true,
|
|
160
|
-
fetchOptions: _objectSpread(_objectSpread({}, options.fetchOptions),
|
|
183
|
+
fetchOptions: _objectSpread(_objectSpread({}, options.fetchOptions), effectiveFetchOptions)
|
|
161
184
|
});
|
|
162
185
|
};
|
|
163
186
|
const updatedClients = (0, _react.useMemo)(() => {
|
|
@@ -192,8 +215,14 @@ const CommerceApiProvider = props => {
|
|
|
192
215
|
currency
|
|
193
216
|
},
|
|
194
217
|
throwOnBadResponse: true,
|
|
195
|
-
fetchOptions
|
|
218
|
+
fetchOptions: effectiveFetchOptions
|
|
196
219
|
};
|
|
220
|
+
|
|
221
|
+
// Determine the proxy endpoint for ShopperLogin based on the client mode:
|
|
222
|
+
// - Private client mode uses a dedicated private proxy endpoint
|
|
223
|
+
// - HttpOnly session cookies mode uses a public proxy endpoint
|
|
224
|
+
// - Otherwise, fall back to the default proxy
|
|
225
|
+
const shopperLoginProxy = enablePWAKitPrivateClient ? privateClientProxyEndpoint : enableHttpOnlySessionCookies ? publicClientProxyEndpoint : config.proxy;
|
|
197
226
|
return {
|
|
198
227
|
shopperBaskets: new _commerceSdkIsomorphic.ShopperBaskets(config),
|
|
199
228
|
shopperBasketsV2: new _commerceSdkIsomorphic.ShopperBasketsV2(config),
|
|
@@ -204,7 +233,7 @@ const CommerceApiProvider = props => {
|
|
|
204
233
|
shopperExperience: new _commerceSdkIsomorphic.ShopperExperience(config),
|
|
205
234
|
shopperGiftCertificates: new _commerceSdkIsomorphic.ShopperGiftCertificates(config),
|
|
206
235
|
shopperLogin: new _commerceSdkIsomorphic.ShopperLogin(_objectSpread(_objectSpread({}, config), {}, {
|
|
207
|
-
proxy:
|
|
236
|
+
proxy: shopperLoginProxy
|
|
208
237
|
})),
|
|
209
238
|
shopperOrders: new _commerceSdkIsomorphic.ShopperOrders(config),
|
|
210
239
|
shopperPayments: new _commerceSdkIsomorphic.ShopperPayments(config),
|
|
@@ -214,7 +243,7 @@ const CommerceApiProvider = props => {
|
|
|
214
243
|
shopperSeo: new _commerceSdkIsomorphic.ShopperSEO(config),
|
|
215
244
|
shopperStores: new _commerceSdkIsomorphic.ShopperStores(config)
|
|
216
245
|
};
|
|
217
|
-
}, [clientId, organizationId, shortCode, siteId, proxy,
|
|
246
|
+
}, [clientId, organizationId, shortCode, siteId, proxy, effectiveFetchOptions, locale, currency, headers === null || headers === void 0 ? void 0 : headers['correlation-id'], apiClients, enablePWAKitPrivateClient, privateClientProxyEndpoint, publicClientProxyEndpoint, enableHttpOnlySessionCookies]);
|
|
218
247
|
|
|
219
248
|
// Initialize the session
|
|
220
249
|
(0, _react.useEffect)(() => {
|
|
@@ -240,7 +269,8 @@ const CommerceApiProvider = props => {
|
|
|
240
269
|
passwordlessLoginCallbackURI,
|
|
241
270
|
refreshTokenRegisteredCookieTTL,
|
|
242
271
|
refreshTokenGuestCookieTTL,
|
|
243
|
-
pageDesignerParams
|
|
272
|
+
pageDesignerParams,
|
|
273
|
+
enableHttpOnlySessionCookies
|
|
244
274
|
}
|
|
245
275
|
}, /*#__PURE__*/_react.default.createElement(CommerceApiContext.Provider, {
|
|
246
276
|
value: updatedClients
|