@salesforce/lds-runtime-aura 1.442.0 → 1.444.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.
@@ -22,9 +22,9 @@ import { instrument, getRecordAvatarsAdapterFactory, getRecordAdapterFactory, co
22
22
  import { getInstrumentation } from 'o11y/client';
23
23
  import { findExecutableOperation, buildGraphQLInputExtension, addTypenameToDocument } from 'force/luvioGraphqlNormalization';
24
24
  import { print, resolveAndValidateGraphQLConfig, toGraphQLErrorResponse } from 'force/luvioOnestoreGraphqlParser';
25
- import { setServices } from 'force/luvioServiceProvisioner1';
25
+ import getServices, { setServices } from 'force/luvioServiceProvisioner1';
26
26
  import { assertIsValid, JsonSchemaViolationError, MissingRequiredPropertyError } from 'force/luvioJsonschemaValidate5';
27
- import { dispatchGlobalEvent, executeGlobalControllerRawResponse } from 'aura';
27
+ import { dispatchGlobalEvent as dispatchGlobalEvent$1, executeGlobalControllerRawResponse } from 'aura';
28
28
  import auraNetworkAdapter, { dispatchAuraAction, defaultActionConfig, instrument as instrument$1, forceRecordTransactionsDisabled as forceRecordTransactionsDisabled$1, ldsNetworkAdapterInstrument, CrudEventState, CrudEventType, UIAPI_RECORDS_PATH, UIAPI_RELATED_LIST_RECORDS_BATCH_PATH, UIAPI_RELATED_LIST_RECORDS_PATH } from 'force/ldsNetwork';
29
29
  import { ThirdPartyTracker } from 'instrumentation:thirdPartyTracker';
30
30
  import { markStart, markEnd, counter, registerCacheStats, perfStart, perfEnd, registerPeriodicLogger, interaction, timer, mark } from 'instrumentation/service';
@@ -36,7 +36,6 @@ import { instrument as instrument$5 } from '@lwc/state';
36
36
  import { withRegistration, register, setDefaultLuvio } from 'force/ldsEngine';
37
37
  import applyPredictionRequestLimit from '@salesforce/gate/lds.pdl.applyRequestLimit';
38
38
  import { pageScopedCache } from 'instrumentation/utility';
39
- import { createStorage, clearStorages } from 'force/ldsDurableStorage';
40
39
  import lightningConnectEnabled from '@salesforce/gate/ui.services.LightningConnect.enabled';
41
40
  import bypassAppRestrictionEnabled from '@salesforce/gate/ui.services.LightningConnect.BypassAppRestriction.enabled';
42
41
  import csrfValidationEnabled from '@salesforce/gate/ui.services.LightningConnect.CsrfValidation.enabled';
@@ -47,6 +46,7 @@ import useHttpUiapiOneRuntimePublic from '@salesforce/gate/lds.useHttpUiapiOneRu
47
46
  import useHttpUiapiOneRuntimePrivate from '@salesforce/gate/lds.useHttpUiapiOneRuntimePrivate';
48
47
  import disableCreateContentDocumentAndVersionHTTPLexRuntime from '@salesforce/gate/lds.lex.http.disableCreateContentDocumentAndVersion';
49
48
  import useHotspotLimit from '@salesforce/gate/lds.pdl.useHotspotLimit';
49
+ import { createStorage, clearStorages } from 'force/ldsDurableStorage';
50
50
  import { registerSubRequestNetworkAdapter } from 'force/ldsNetwork';
51
51
 
52
52
  const { create: create$1, freeze, keys: keys$2, entries: entries$1 } = Object;
@@ -234,6 +234,19 @@ var HttpStatusCode$1 = /* @__PURE__ */ ((HttpStatusCode2) => {
234
234
  HttpStatusCode2[HttpStatusCode2["GatewayTimeout"] = 504] = "GatewayTimeout";
235
235
  return HttpStatusCode2;
236
236
  })(HttpStatusCode$1 || {});
237
+ function getFetchResponseFromAuraError(err2) {
238
+ if (err2.data !== void 0 && err2.data.statusCode !== void 0) {
239
+ let data = {};
240
+ data = err2.data;
241
+ if (err2.id !== void 0) {
242
+ data.id = err2.id;
243
+ }
244
+ return new FetchResponse(data.statusCode, data);
245
+ }
246
+ return new FetchResponse(500, {
247
+ error: err2.message
248
+ });
249
+ }
237
250
  async function coerceResponseToFetchResponse(response) {
238
251
  const { status } = response;
239
252
  const responseHeaders = {};
@@ -388,7 +401,7 @@ class AuraNetworkCommand extends NetworkCommand$1 {
388
401
  );
389
402
  }
390
403
  coerceAuraErrors(auraErrors) {
391
- return toError(auraErrors[0]);
404
+ return new UserVisibleError(getFetchResponseFromAuraError(auraErrors[0]));
392
405
  }
393
406
  /**
394
407
  * Customize how non-2xx fetch fallback responses are converted into errors.
@@ -573,7 +586,10 @@ function deepEquals$1(x, y) {
573
586
  }
574
587
  for (let i = 0; i < xkeys.length; ++i) {
575
588
  const key = xkeys[i];
576
- if (!deepEquals$1(x[key], y[key])) {
589
+ if (!deepEquals$1(
590
+ x[key],
591
+ y[key]
592
+ )) {
577
593
  return false;
578
594
  }
579
595
  }
@@ -2644,7 +2660,7 @@ function buildServiceDescriptor$d(luvio) {
2644
2660
  },
2645
2661
  };
2646
2662
  }
2647
- // version: 1.442.0-e47893165a
2663
+ // version: 1.444.0-a7f42f9edf
2648
2664
 
2649
2665
  class AuraGraphQLNormalizedCacheControlCommand extends AuraNormalizedCacheControlCommand {
2650
2666
  constructor(config, documentRootType, services) {
@@ -2982,7 +2998,7 @@ function buildServiceDescriptor$9(notifyRecordUpdateAvailable, getNormalizedLuvi
2982
2998
  },
2983
2999
  };
2984
3000
  }
2985
- // version: 1.442.0-e47893165a
3001
+ // version: 1.444.0-a7f42f9edf
2986
3002
 
2987
3003
  class RetryService {
2988
3004
  constructor(defaultRetryPolicy) {
@@ -3134,6 +3150,50 @@ function buildServiceDescriptor$8(defaultRetryPolicy) {
3134
3150
  };
3135
3151
  }
3136
3152
 
3153
+ class BasicRenewableResourceManager {
3154
+ constructor(config) {
3155
+ this.config = config;
3156
+ }
3157
+ get() {
3158
+ return this.config.storage.get().then((cached) => cached !== void 0 ? cached : this.fetchAndStore());
3159
+ }
3160
+ refresh(staleValue) {
3161
+ if (staleValue === void 0) {
3162
+ return this.fetchAndStore();
3163
+ }
3164
+ return this.config.storage.get().then(
3165
+ (current) => current !== void 0 && current !== staleValue ? current : this.fetchAndStore()
3166
+ );
3167
+ }
3168
+ clear() {
3169
+ return this.config.storage.clear();
3170
+ }
3171
+ /**
3172
+ * Single in-flight fetch shared by all concurrent `get`/`refresh` callers.
3173
+ * Overwrites storage on a successful fetch of a defined value only; a
3174
+ * rejected fetch or a resolved-`undefined` leaves the cached value intact.
3175
+ */
3176
+ fetchAndStore() {
3177
+ if (this.inFlightFetch) return this.inFlightFetch;
3178
+ const pending = this.config.fetch().then(
3179
+ (fetched) => fetched === void 0 ? fetched : this.config.storage.set(fetched).then(() => fetched)
3180
+ );
3181
+ this.inFlightFetch = pending;
3182
+ const releaseSlot = () => {
3183
+ if (this.inFlightFetch === pending) this.inFlightFetch = void 0;
3184
+ };
3185
+ pending.then(releaseSlot, releaseSlot);
3186
+ return pending;
3187
+ }
3188
+ }
3189
+ function buildRenewableResourceManagerDescriptor(type, service) {
3190
+ return {
3191
+ version: "1.0",
3192
+ service,
3193
+ type
3194
+ };
3195
+ }
3196
+
3137
3197
  function isUserVisibleError(error) {
3138
3198
  return error instanceof Error && "type" in error && error.type === "user-visible";
3139
3199
  }
@@ -3364,7 +3424,10 @@ class GraphQLImperativeBindingsService {
3364
3424
  const options = {
3365
3425
  acceptedOperations: ["query"]
3366
3426
  };
3367
- const result = resolveAndValidateGraphQLConfig(params[0], options);
3427
+ const result = resolveAndValidateGraphQLConfig(
3428
+ params[0],
3429
+ options
3430
+ );
3368
3431
  if (result?.isErr()) {
3369
3432
  return result.error;
3370
3433
  }
@@ -3561,7 +3624,10 @@ class GraphQLMutationBindingsService {
3561
3624
  const options = {
3562
3625
  acceptedOperations: ["mutation"]
3563
3626
  };
3564
- const result2 = resolveAndValidateGraphQLConfig(params[0], options);
3627
+ const result2 = resolveAndValidateGraphQLConfig(
3628
+ params[0],
3629
+ options
3630
+ );
3565
3631
  if (result2?.isErr()) {
3566
3632
  return {
3567
3633
  data: void 0,
@@ -4623,7 +4689,7 @@ var TypeCheckShapes;
4623
4689
  TypeCheckShapes[TypeCheckShapes["Integer"] = 3] = "Integer";
4624
4690
  TypeCheckShapes[TypeCheckShapes["Unsupported"] = 4] = "Unsupported";
4625
4691
  })(TypeCheckShapes || (TypeCheckShapes = {}));
4626
- // engine version: 0.160.5-e6ada846
4692
+ // engine version: 0.161.0-fe06f180
4627
4693
 
4628
4694
  const { keys: keys$1 } = Object;
4629
4695
 
@@ -4718,6 +4784,10 @@ const SALESFORCE_API_BASE_URI_FLAG = 'api.salesforce.com';
4718
4784
  const X_REQUEST_ID_HEADER = 'x-request-id';
4719
4785
  const SFAPController = 'SalesforceApiPlatformController';
4720
4786
  const SFAPJwtMethod = 'getSFAPLightningJwtService';
4787
+ // Parameterized mint: the POST signature's auto-generated Aura method
4788
+ // (`@ConnectSignature(..., generateAuraMethod = true)` on the SFAP Lightning JWT
4789
+ // Service Connect resource). Takes a single `requestBody` named param.
4790
+ const SFAPJwtPostMethod = 'postSFAPLightningJwtService';
4721
4791
  /**
4722
4792
  * We expect jwt info and baseUri to be present in the response.
4723
4793
  *
@@ -4864,6 +4934,144 @@ function generateRequestId$1() {
4864
4934
  }
4865
4935
  }
4866
4936
 
4937
+ /**
4938
+ * Normalize SFAP-shaped params into the opaque `JwtMintParams` bag that callers
4939
+ * hand to `JwtManager.getJwt(params)`. Scopes are sorted because they are
4940
+ * semantically a set — without this, `['a', 'b']` and `['b', 'a']` would cache
4941
+ * as separate entries (OneStore treats arrays as ordered under stable-JSON
4942
+ * serialization; see ADR "JWT Parameterization" §2).
4943
+ *
4944
+ * MANDATORY CONSUMER ENTRY POINT. Every consumer that mints a parameterized
4945
+ * SFAP JWT MUST assemble its params through this function before calling the
4946
+ * manager. The cache key is derived by `JwtManager` from the caller's params
4947
+ * (`cacheKeyFor(params)`) *before* the resolver runs — so the resolver cannot
4948
+ * normalize after the fact. Set-stable caching therefore depends on the caller
4949
+ * routing params through here. Do NOT sort inside the resolver: that would only
4950
+ * reorder the wire body, not the cache key, and mutating the caller's params
4951
+ * mid-call would desync the in-flight-dedup key from the stored-token key.
4952
+ */
4953
+ /**
4954
+ * Validate and narrow an opaque `JwtMintParams` bag to the SFAP-shaped subset the
4955
+ * resolver forwards. Returns `{ error }` (surfaced as a resolver rejection) for a
4956
+ * malformed bag rather than throwing.
4957
+ */
4958
+ function coerceToSfapParams(params) {
4959
+ const scopes = params.scopes;
4960
+ const dynamicParams = params.dynamicParams;
4961
+ if (scopes !== undefined && !isStringArray(scopes)) {
4962
+ return { error: 'SFAP JWT params.scopes must be a string[] when provided.' };
4963
+ }
4964
+ if (dynamicParams !== undefined && !isStringRecord(dynamicParams)) {
4965
+ return {
4966
+ error: 'SFAP JWT params.dynamicParams must be a Record<string, string> when provided.',
4967
+ };
4968
+ }
4969
+ return { params: { scopes, dynamicParams } };
4970
+ }
4971
+ function isStringArray(value) {
4972
+ return Array.isArray(value) && value.every((s) => typeof s === 'string');
4973
+ }
4974
+ function isStringRecord(value) {
4975
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
4976
+ return false;
4977
+ }
4978
+ return Object.values(value).every((v) => typeof v === 'string');
4979
+ }
4980
+ /**
4981
+ * Build the SFAP mint request body from the coerced params: `scopes` joined into a
4982
+ * single space-delimited string, `dynamicParams` mapped to `dynamicParameters.items`.
4983
+ * Empty scopes / dynamic params are omitted.
4984
+ */
4985
+ function buildRequestBody(params) {
4986
+ const body = {};
4987
+ if (params.scopes && params.scopes.length > 0) {
4988
+ body.scopes = params.scopes.join(' ');
4989
+ }
4990
+ if (params.dynamicParams) {
4991
+ const items = Object.entries(params.dynamicParams).map(([name, value]) => ({ name, value }));
4992
+ if (items.length > 0) {
4993
+ body.dynamicParameters = { items };
4994
+ }
4995
+ }
4996
+ return body;
4997
+ }
4998
+
4999
+ /**
5000
+ * Parameterized SFAP JWT resolver that mints over **Aura transport** instead of a
5001
+ * direct HTTP `fetch`.
5002
+ *
5003
+ * It dispatches the SFAP Lightning JWT Service's parameterized POST via its
5004
+ * auto-generated Aura controller method
5005
+ * `SalesforceApiPlatformController.postSFAPLightningJwtService` (generated by
5006
+ * `@ConnectSignature(..., generateAuraMethod = true)` on the Connect resource).
5007
+ * The mint inputs ride as the single `requestBody` named param, in the same
5008
+ * `{ scopes, dynamicParameters: { items } }` shape the HTTP resolver POSTs — built
5009
+ * by the shared {@link buildRequestBody}, so the two transports stay in lockstep.
5010
+ *
5011
+ * Why Aura (not the HTTP resolver): the mint endpoint is a same-origin core
5012
+ * resource, and routing it over Aura keeps session/CSRF handling inside the Aura
5013
+ * stack rather than issuing a credentialed cross-cutting `fetch` from the client.
5014
+ * This mirrors the legacy parameterless `platformSfapJwtResolver` in
5015
+ * `network-sfap.ts`, which already mints over Aura via the `getSFAPLightningJwtService`
5016
+ * generated method — this is the parameterized sibling of that call.
5017
+ *
5018
+ * A `JwtResolver` is invoked directly by `JwtManager` (not through Luvio's
5019
+ * `appRouter`/`ResourceRequest` pipeline), so the correct mechanism is a direct
5020
+ * named-controller `dispatchAuraAction`, not the `auraNetworkAdapter`/connect-route
5021
+ * table. The SFAP JWT endpoint is not registered as a connect-over-Aura route, and
5022
+ * a resolver has no `ResourceRequest` for the router to look up.
5023
+ */
5024
+ class ParameterizedSfapJwtAuraResolver {
5025
+ getJwt(params) {
5026
+ return new Promise((resolve, reject) => {
5027
+ if (params === undefined) {
5028
+ // Misuse: the dispatching resolver routes parameterless calls to the
5029
+ // legacy resolver. Reject so production fails loudly here.
5030
+ reject('ParameterizedSfapJwtAuraResolver requires JwtMintParams. The legacy parameterless path should be served by platformSfapJwtResolver.');
5031
+ return;
5032
+ }
5033
+ const coerced = coerceToSfapParams(params);
5034
+ if ('error' in coerced) {
5035
+ reject(coerced.error);
5036
+ return;
5037
+ }
5038
+ // The mint inputs become the single `requestBody` named param of the
5039
+ // generated Aura method (Aura binds top-level keys to the controller
5040
+ // method's named args). Reuses the HTTP resolver's body builder so both
5041
+ // transports send an identical shape.
5042
+ const requestBody = buildRequestBody(coerced.params);
5043
+ // No fetchImpl / requestInterceptor / CSRF / credentials handling here —
5044
+ // the Aura stack owns session + CSRF. Matches the legacy resolver's call.
5045
+ dispatchAuraAction(`${SFAPController}.${SFAPJwtPostMethod}`, { requestBody }, defaultActionConfig)
5046
+ .then((response) => {
5047
+ const body = response.body;
5048
+ if (!body || typeof body.jwt !== 'string' || typeof body.baseUri !== 'string') {
5049
+ // Never serialize the body into the error — it may carry a JWT.
5050
+ reject('SFAP JWT response missing required fields (jwt, baseUri)');
5051
+ return;
5052
+ }
5053
+ resolve({ jwt: body.jwt, extraInfo: { baseUri: body.baseUri } });
5054
+ })
5055
+ .catch((error) => {
5056
+ // Error mapping ported from the legacy platformSfapJwtResolver
5057
+ // (network-sfap.ts): plain Errors carry a message; non-500
5058
+ // AuraFetchResponses are ConnectInJava errors with a typed body;
5059
+ // 500s carry an { error } body.
5060
+ if (error instanceof Error) {
5061
+ reject(error.message);
5062
+ return;
5063
+ }
5064
+ const { status } = error;
5065
+ if (status !== HttpStatusCode$2.ServerError) {
5066
+ reject(error.body.message);
5067
+ return;
5068
+ }
5069
+ reject(error.body.error);
5070
+ });
5071
+ });
5072
+ }
5073
+ }
5074
+
4867
5075
  /**
4868
5076
  * Observability / Critical Availability Program (230+)
4869
5077
  *
@@ -5614,7 +5822,9 @@ class PrioritizedConfigService {
5614
5822
  // ConfigService.set
5615
5823
  set(priority, setter) {
5616
5824
  this.validated = false;
5617
- return setter(this.prioritizedConfigData[priority]);
5825
+ return setter(
5826
+ this.prioritizedConfigData[priority]
5827
+ );
5618
5828
  }
5619
5829
  // returns the highest priority value for a given path, undefined if no value
5620
5830
  // is defined for the path at any priority
@@ -5673,9 +5883,36 @@ function getEnvironmentSetting(name) {
5673
5883
  }
5674
5884
  return undefined;
5675
5885
  }
5676
- // version: 1.442.0-d73ebfc396
5886
+ // version: 1.444.0-86f5a57eb3
5887
+
5888
+ /**
5889
+ * Helpers for reaching the Aura framework from the LDS Aura runtime.
5890
+ *
5891
+ * `window.$A` is only present when the runtime is hosted inside LEX; in tests
5892
+ * and non-Aura runtimes it is absent. Centralizing the guarded access keeps the
5893
+ * `$A` lookup in one place instead of duplicating the `environmentHasAura`
5894
+ * check across modules.
5895
+ */
5896
+ /**
5897
+ * Fires an Aura application-level event (e.g. `aura:invalidSession`). Thin
5898
+ * pass-through to the `aura` framework module so callers depend on this util
5899
+ * rather than importing `aura` directly.
5900
+ */
5901
+ function dispatchGlobalEvent(...args) {
5902
+ dispatchGlobalEvent$1(...args);
5903
+ }
5904
+ /**
5905
+ * Returns `$A.clientService` when running inside an Aura environment, or
5906
+ * `undefined` otherwise. Defensive: never throws.
5907
+ */
5908
+ function getAuraClientService() {
5909
+ if (typeof window === 'undefined' || typeof window.$A === 'undefined') {
5910
+ return undefined;
5911
+ }
5912
+ return window.$A.clientService;
5913
+ }
5677
5914
 
5678
- const environmentHasAura = typeof window !== 'undefined' && typeof window.$A !== 'undefined';
5915
+ const auraClientService = getAuraClientService();
5679
5916
  const defaultConfig = {
5680
5917
  maxAllowedParallelXHRCounts: 9,
5681
5918
  forceRecordTransactionsDisabled: false,
@@ -5686,10 +5923,11 @@ const configServiceDescriptor = buildServiceDescriptor$1({
5686
5923
  default: defaultConfig,
5687
5924
  });
5688
5925
  const configService = configServiceDescriptor.service;
5689
- if (environmentHasAura) {
5926
+ if (auraClientService?.maxAllowedParallelXHRCounts) {
5690
5927
  configService.set('lex', (config) => {
5691
5928
  config.maxAllowedParallelXHRCounts =
5692
- window['$A'].clientService.maxAllowedParallelXHRCounts();
5929
+ auraClientService.maxAllowedParallelXHRCounts?.() ??
5930
+ defaultConfig.maxAllowedParallelXHRCounts;
5693
5931
  config.forceRecordTransactionsDisabled = getEnvironmentSetting(EnvironmentSettings.ForceRecordTransactionsDisabled);
5694
5932
  });
5695
5933
  }
@@ -5933,34 +6171,54 @@ function buildLuvioPageScopedCacheRequestInterceptor() {
5933
6171
  };
5934
6172
  }
5935
6173
 
5936
- function buildLexRuntimeAuthExpirationRedirectResponseInterceptor(logger) {
6174
+ // Two distinct session-expiration cases on the LEX runtime path:
6175
+ // - 401 + INVALID_SESSION_ID — auth token rejected
6176
+ // - 403 + `x-sfdc-csrf-failure: true` — CSRF token rejected, session is dead
6177
+ // Both fire `aura:invalidSession`, which routes the user back to login.
6178
+ //
6179
+ // 403 access denials (INSUFFICIENT_ACCESS / INSUFFICIENT_ACCESS_OR_READONLY) are
6180
+ // intentionally NOT handled here — `aura:noAccess` without a `redirectURL`
6181
+ // triggers AuraClientService.hardRefresh() which is too heavy-handed for routine
6182
+ // "feature not enabled for this org" responses. The wire layer surfaces those
6183
+ // as normal errors; onestore PR #838 (`aura-network-command`) handles the
6184
+ // gack-noise on the Aura action path.
6185
+ function isSessionExpired(status, headers, body) {
6186
+ if (status === 403) {
6187
+ return headers && headers['x-sfdc-csrf-failure'] === 'true';
6188
+ }
6189
+ if (status === 401) {
6190
+ // Connect REST returns errors as `[{ errorCode, ... }, ...]`; Aura Shape A
6191
+ // uses `{ errorCode, ... }`. This interceptor runs before
6192
+ // `buildLuvioErrorBodyNormalizationInterceptor`, so handle both.
6193
+ const errorCode = Array.isArray(body)
6194
+ ? body[0] && body[0].errorCode
6195
+ : body && body.errorCode;
6196
+ return errorCode === 'INVALID_SESSION_ID';
6197
+ }
6198
+ return false;
6199
+ }
6200
+ function buildLexRuntimeSessionExpirationResponseInterceptor(logger) {
5937
6201
  return async (response) => {
5938
- if (response.status === 401) {
5939
- try {
5940
- const coercedResponse = (await coerceResponseToFetchResponse(response.clone()));
5941
- if (coercedResponse.body.errorCode === 'INVALID_SESSION_ID') {
5942
- logger.warn(`Received ${response.status} status code from LEX runtime service`);
5943
- // Fire the event asynchronously, similar to the legacy setTimeout pattern
5944
- window.setTimeout(() => {
5945
- dispatchGlobalEvent('aura:invalidSession');
5946
- }, 0);
5947
- }
5948
- }
5949
- catch (error) {
5950
- logger.warn(`Error parsing response from LEX runtime service: ${error}`);
6202
+ const { status } = response;
6203
+ if (status !== 401 && status !== 403)
6204
+ return response;
6205
+ try {
6206
+ const coerced = (await coerceResponseToFetchResponse(response.clone()));
6207
+ if (isSessionExpired(status, coerced.headers, coerced.body)) {
6208
+ logger.warn(`Received ${status} status code from LEX runtime service`);
6209
+ window.setTimeout(() => dispatchGlobalEvent('aura:invalidSession'), 0);
5951
6210
  }
5952
6211
  }
6212
+ catch (error) {
6213
+ logger.warn(`Error parsing response from LEX runtime service: ${error}`);
6214
+ }
5953
6215
  return response;
5954
6216
  };
5955
6217
  }
5956
- function buildLexRuntimeLuvioAuthExpirationRedirectResponseInterceptor() {
6218
+ function buildLexRuntimeLuvioSessionExpirationResponseInterceptor() {
5957
6219
  return async (response) => {
5958
- if (response.status === 401) {
5959
- if (response.body.errorCode === 'INVALID_SESSION_ID') {
5960
- window.setTimeout(() => {
5961
- dispatchGlobalEvent('aura:invalidSession');
5962
- }, 0);
5963
- }
6220
+ if (isSessionExpired(response.status, response.headers, response.body)) {
6221
+ window.setTimeout(() => dispatchGlobalEvent('aura:invalidSession'), 0);
5964
6222
  }
5965
6223
  return response;
5966
6224
  };
@@ -6119,146 +6377,91 @@ function buildLexRuntimeLuvio5xxStatusResponseInterceptor() {
6119
6377
  };
6120
6378
  }
6121
6379
 
6122
- const CSRF_TOKEN_KEY = 'salesforce_csrf_token';
6123
- const CSRF_STORAGE_NAME = 'ldsCSRFToken';
6124
- const BASE_URI = '/services/data/v68.0';
6125
- const UI_API_BASE_URI = `${BASE_URI}/ui-api`;
6126
- const CSRF_TOKEN_ENDPOINT = `${UI_API_BASE_URI}/session/csrf`;
6127
- const CSRF_STORAGE_CONFIG = {
6128
- name: CSRF_STORAGE_NAME,
6129
- persistent: true,
6130
- secure: true,
6131
- maxSize: 1024,
6132
- expiration: 24 * 60 * 60,
6133
- clearOnInit: false,
6134
- debugLogging: false,
6135
- };
6136
6380
  /**
6137
- * Manages CSRF token fetching and caching for secure requests.
6138
- * Implements a singleton pattern to ensure consistent token management across the application.
6381
+ * Normalizes Connect REST error envelopes into the Aura Shape A
6382
+ * (ConnectInJava) shape, so consumers can read `response.body.errorCode`
6383
+ * regardless of transport.
6384
+ *
6385
+ * - HTTP error envelope (this path): `[{ errorCode, message }, ...]`
6386
+ * - Aura Shape A: `{ errorCode, message, statusCode, ... }`
6387
+ *
6388
+ * The full array is preserved at `body.enhancedErrorInfo.allErrors` since
6389
+ * Connect can return multiple errors per response.
6390
+ *
6391
+ * Aura Shape B (`{ error: "..." }`) is an Aura-only fallback that never
6392
+ * appears on the HTTP path, so no normalization is needed for it here.
6393
+ *
6394
+ * Ordering: must run AFTER any other interceptor that inspects the raw
6395
+ * error body shape (e.g. the 5xx interceptor's ErrorId extraction reads
6396
+ * `body[0].message` from the array form).
6139
6397
  */
6140
- class CsrfTokenManager {
6141
- constructor() {
6142
- this.tokenPromise = null;
6143
- this.refreshInFlight = null;
6144
- // Initialize AuraStorage
6145
- this.storage = createStorage(CSRF_STORAGE_CONFIG);
6146
- }
6147
- static getInstance() {
6148
- if (!CsrfTokenManager.instance) {
6149
- CsrfTokenManager.instance = new CsrfTokenManager();
6150
- }
6151
- return CsrfTokenManager.instance;
6152
- }
6153
- /**
6154
- * Obtain a CSRF token, either from AuraStorage or by fetching a fresh one.
6155
- *
6156
- * @private
6157
- */
6158
- async loadOrFetchToken() {
6159
- // First try to get token from AuraStorage
6160
- if (this.storage) {
6161
- try {
6162
- const cachedToken = await this.storage.get(CSRF_TOKEN_KEY);
6163
- if (typeof cachedToken === 'string' && cachedToken) {
6164
- return cachedToken;
6165
- }
6166
- }
6167
- catch {
6168
- // If storage read fails, continue to fetch
6169
- }
6170
- }
6171
- // No cached token, fetch from server
6172
- return this.fetchFreshToken();
6173
- }
6174
- /**
6175
- * Call API endpoint to acquire a CSRF token and cache it.
6176
- *
6177
- * @private
6178
- */
6179
- async fetchFreshToken() {
6180
- try {
6181
- const response = await fetch(CSRF_TOKEN_ENDPOINT, {
6182
- method: 'GET',
6183
- credentials: 'same-origin',
6184
- });
6185
- if (!response.ok) {
6186
- return undefined;
6187
- }
6188
- const data = await response.json();
6189
- const token = data.csrfToken;
6190
- if (token && this.storage) {
6191
- // Cache the token in AuraStorage
6192
- try {
6193
- await this.storage.set(CSRF_TOKEN_KEY, token);
6194
- }
6195
- catch {
6196
- // Non-fatal: token is still available even if caching fails
6197
- }
6198
- }
6199
- return token;
6200
- }
6201
- catch {
6202
- return undefined;
6203
- }
6204
- }
6205
- /**
6206
- * Returns the current token value as a Promise.
6207
- * Lazy-loads the token on first call (from cache or by fetching).
6208
- */
6209
- async getToken() {
6210
- // Lazy initialization: only fetch token when actually needed
6211
- if (!this.tokenPromise) {
6212
- this.tokenPromise = this.loadOrFetchToken();
6213
- }
6214
- return this.tokenPromise;
6215
- }
6216
- /**
6217
- * Obtains and returns a new token value as a promise.
6218
- * This will clear the cached token and fetch a fresh one.
6219
- *
6220
- * Concurrent calls coalesce onto a single in-flight refresh — important when
6221
- * multiple requests fail with INVALID_ACCESS_TOKEN simultaneously and each
6222
- * retry policy independently calls refreshToken(). Without this, every caller
6223
- * triggers its own /session/csrf round-trip.
6224
- */
6225
- refreshToken() {
6226
- if (this.refreshInFlight) {
6227
- return this.refreshInFlight;
6398
+ function buildLuvioErrorBodyNormalizationInterceptor() {
6399
+ return async (response) => {
6400
+ if (!response.ok && Array.isArray(response.body)) {
6401
+ const allErrors = response.body;
6402
+ response.body = {
6403
+ ...allErrors[0],
6404
+ statusCode: response.status,
6405
+ enhancedErrorInfo: {
6406
+ allErrors,
6407
+ },
6408
+ };
6228
6409
  }
6229
- const refresh = (async () => {
6230
- if (this.storage) {
6231
- try {
6232
- await this.storage.remove(CSRF_TOKEN_KEY);
6233
- }
6234
- catch {
6235
- // Non-fatal: continue with refresh even if clear fails
6236
- }
6237
- }
6238
- return this.fetchFreshToken();
6239
- })();
6240
- this.refreshInFlight = refresh;
6241
- this.tokenPromise = refresh;
6242
- // Clear the in-flight reference once settled (success or failure) so
6243
- // subsequent token expirations can trigger a fresh refresh.
6244
- refresh.finally(() => {
6245
- if (this.refreshInFlight === refresh) {
6246
- this.refreshInFlight = null;
6247
- }
6410
+ return response;
6411
+ };
6412
+ }
6413
+
6414
+ /**
6415
+ * Service name the CSRF token manager is registered under by
6416
+ * `initializeOneStore` (see `buildRenewableResourceManagerDescriptor`).
6417
+ */
6418
+ const CSRF_TOKEN_MANAGER_SERVICE = 'csrfTokenManager';
6419
+ const CSRF_TOKEN_MANAGER_REQUEST = {
6420
+ [CSRF_TOKEN_MANAGER_SERVICE]: {
6421
+ type: CSRF_TOKEN_MANAGER_SERVICE,
6422
+ version: '1.0',
6423
+ },
6424
+ };
6425
+ let cached;
6426
+ /**
6427
+ * Resolves the CSRF token manager from the service provisioner.
6428
+ *
6429
+ * The manager is registered during `initializeOneStore`, which runs before any
6430
+ * request flows; the interceptors and retry policies that call this resolve
6431
+ * lazily (per request / per retry), so the service is always available by then.
6432
+ *
6433
+ * The resolved promise is memoized so resolution happens once rather than per
6434
+ * request. A rejection is NOT memoized — `getServices` rejects when the service
6435
+ * is unavailable, and caching that would permanently wedge CSRF handling — so a
6436
+ * later call retries the resolution.
6437
+ */
6438
+ function getCsrfTokenManager() {
6439
+ if (!cached) {
6440
+ cached = Promise.resolve(getServices(CSRF_TOKEN_MANAGER_REQUEST))
6441
+ .then((services) => services[CSRF_TOKEN_MANAGER_SERVICE])
6442
+ .catch((error) => {
6443
+ cached = undefined;
6444
+ throw error;
6248
6445
  });
6249
- return refresh;
6250
- }
6251
- /**
6252
- * Reset the singleton instance (useful for testing).
6253
- * @internal
6254
- */
6255
- static resetInstance() {
6256
- CsrfTokenManager.instance = null;
6257
6446
  }
6447
+ return cached;
6258
6448
  }
6259
- CsrfTokenManager.instance = null;
6260
6449
 
6261
6450
  const CSRF_TOKEN_HEADER = 'X-CSRF-Token';
6451
+ /**
6452
+ * Resolves the CSRF token manager and returns the current token. Returns
6453
+ * `undefined` if the manager cannot be resolved (service not yet provisioned)
6454
+ * or has no token, so a request is never blocked by token resolution.
6455
+ */
6456
+ async function getCsrfToken() {
6457
+ try {
6458
+ const manager = await getCsrfTokenManager();
6459
+ return await manager.get();
6460
+ }
6461
+ catch {
6462
+ return undefined;
6463
+ }
6464
+ }
6262
6465
  /**
6263
6466
  * Checks if all required gates are enabled for CSRF token interceptor.
6264
6467
  *
@@ -6301,7 +6504,6 @@ function isCsrfMethod(method) {
6301
6504
  * @returns A RequestInterceptor function for FetchParameters
6302
6505
  */
6303
6506
  function buildCsrfTokenInterceptor() {
6304
- const csrfTokenManager = CsrfTokenManager.getInstance();
6305
6507
  return async (fetchArgs) => {
6306
6508
  // Check if all required gates are enabled before running
6307
6509
  if (!areCsrfGatesEnabled()) {
@@ -6318,7 +6520,7 @@ function buildCsrfTokenInterceptor() {
6318
6520
  }
6319
6521
  // Only add CSRF token for mutating operations
6320
6522
  if (isCsrfMethod(method)) {
6321
- const token = await csrfTokenManager.getToken();
6523
+ const token = await getCsrfToken();
6322
6524
  if (token) {
6323
6525
  // eslint-disable-next-line no-param-reassign
6324
6526
  fetchArgs = setHeader(CSRF_TOKEN_HEADER, token, fetchArgs);
@@ -6335,7 +6537,6 @@ function buildCsrfTokenInterceptor() {
6335
6537
  * @returns A request interceptor function for Luvio ResourceRequest objects
6336
6538
  */
6337
6539
  function buildLuvioCsrfTokenInterceptor() {
6338
- const csrfTokenManager = CsrfTokenManager.getInstance();
6339
6540
  return async (resourceRequest) => {
6340
6541
  // Check if all required gates are enabled before running
6341
6542
  if (!areCsrfGatesEnabled()) {
@@ -6349,7 +6550,7 @@ function buildLuvioCsrfTokenInterceptor() {
6349
6550
  if (isCsrfMethod(resourceRequest.method)) {
6350
6551
  // Don't overwrite existing CSRF token header if it already exists
6351
6552
  if (!resourceRequest.headers[CSRF_TOKEN_HEADER]) {
6352
- const token = await csrfTokenManager.getToken();
6553
+ const token = await getCsrfToken();
6353
6554
  if (token) {
6354
6555
  resourceRequest.headers[CSRF_TOKEN_HEADER] = token;
6355
6556
  }
@@ -6389,6 +6590,39 @@ function buildLuvioEntityEncodingInterceptor() {
6389
6590
  };
6390
6591
  }
6391
6592
 
6593
+ const FIRST_PARTY_HEADER = 'X-Salesforce-First-Party';
6594
+ const FIRST_PARTY_VALUE = 'platform-ui';
6595
+ /**
6596
+ * Builds a request interceptor that adds the LDS first party header to every
6597
+ * outbound request.
6598
+ *
6599
+ * @returns A RequestInterceptor function for FetchParameters
6600
+ */
6601
+ function buildFirstPartyHeaderInterceptor() {
6602
+ return async (fetchArgs) => {
6603
+ const returnedFetchArgs = setHeader(FIRST_PARTY_HEADER, FIRST_PARTY_VALUE, fetchArgs);
6604
+ return resolvedPromiseLike$2(returnedFetchArgs);
6605
+ };
6606
+ }
6607
+ /**
6608
+ * Builds a Luvio request interceptor that adds the LDS first party header to
6609
+ * every outbound `ResourceRequest`. See {@link buildFirstPartyHeaderInterceptor} for
6610
+ * the header semantics.
6611
+ *
6612
+ * @returns A request interceptor for Luvio ResourceRequest objects
6613
+ */
6614
+ function buildLuvioFirstPartyHeaderInterceptor() {
6615
+ return async (resourceRequest) => {
6616
+ if (!resourceRequest.headers) {
6617
+ resourceRequest.headers = {};
6618
+ }
6619
+ if (!resourceRequest.headers[FIRST_PARTY_HEADER]) {
6620
+ resourceRequest.headers[FIRST_PARTY_HEADER] = FIRST_PARTY_VALUE;
6621
+ }
6622
+ return resolvedPromiseLike$2(resourceRequest);
6623
+ };
6624
+ }
6625
+
6392
6626
  function createInstrumentationIdContext() {
6393
6627
  return () => ({
6394
6628
  instrumentationId: generateRequestId(),
@@ -6618,7 +6852,6 @@ class LuvioCsrfTokenRetryPolicy extends RetryPolicy {
6618
6852
  constructor(config = DEFAULT_CONFIG$3) {
6619
6853
  super();
6620
6854
  this.config = config;
6621
- this.csrfTokenManager = CsrfTokenManager.getInstance();
6622
6855
  }
6623
6856
  setRequestContext(context) {
6624
6857
  this.requestContext = context;
@@ -6640,11 +6873,20 @@ class LuvioCsrfTokenRetryPolicy extends RetryPolicy {
6640
6873
  if (!this.requestContext) {
6641
6874
  return;
6642
6875
  }
6643
- const newToken = await this.csrfTokenManager.refreshToken();
6876
+ let newToken;
6877
+ try {
6878
+ const manager = await getCsrfTokenManager();
6879
+ newToken = await manager.refresh();
6880
+ }
6881
+ catch {
6882
+ // Manager unavailable — drop the stale token below so the request
6883
+ // interceptor refetches on the retry.
6884
+ newToken = undefined;
6885
+ }
6644
6886
  const req = this.requestContext.request;
6645
6887
  const { [CSRF_TOKEN_HEADER]: _stale, ...remainingHeaders } = req.headers ?? {};
6646
6888
  // If refresh failed, drop the stale token entirely so the request interceptor
6647
- // will fetch a fresh one via getToken() on the retry.
6889
+ // will fetch a fresh one via get() on the retry.
6648
6890
  const headers = newToken
6649
6891
  ? { ...remainingHeaders, [CSRF_TOKEN_HEADER]: newToken }
6650
6892
  : remainingHeaders;
@@ -7149,12 +7391,14 @@ const composedFetchNetworkAdapter = {
7149
7391
  buildLuvioTransportMarksSendInterceptor(),
7150
7392
  buildLuvioPageScopedCacheRequestInterceptor(),
7151
7393
  buildLuvioCsrfTokenInterceptor(),
7394
+ buildLuvioFirstPartyHeaderInterceptor(),
7152
7395
  buildLuvioEntityEncodingInterceptor(),
7153
7396
  ],
7154
7397
  retry: buildLuvioFetchRetryInterceptor(),
7155
7398
  response: [
7156
7399
  buildLexRuntimeLuvio5xxStatusResponseInterceptor(),
7157
- buildLexRuntimeLuvioAuthExpirationRedirectResponseInterceptor(),
7400
+ buildLexRuntimeLuvioSessionExpirationResponseInterceptor(),
7401
+ buildLuvioErrorBodyNormalizationInterceptor(),
7158
7402
  buildLuvioTransportMarksReceiveInterceptor(),
7159
7403
  buildLuvioActionMarksReceiveInterceptor(),
7160
7404
  ],
@@ -7199,7 +7443,6 @@ class CsrfTokenRetryPolicy extends RetryPolicy {
7199
7443
  constructor(config = DEFAULT_CONFIG$1) {
7200
7444
  super();
7201
7445
  this.config = config;
7202
- this.csrfTokenManager = CsrfTokenManager.getInstance();
7203
7446
  }
7204
7447
  /**
7205
7448
  * Allows the fetch service to pass mutable request context.
@@ -7251,7 +7494,15 @@ class CsrfTokenRetryPolicy extends RetryPolicy {
7251
7494
  */
7252
7495
  async prepareRetry(_result, _context) {
7253
7496
  // Refresh the CSRF token (we already know this is a CSRF error from shouldRetry)
7254
- const newToken = await this.csrfTokenManager.refreshToken();
7497
+ let newToken;
7498
+ try {
7499
+ const manager = await getCsrfTokenManager();
7500
+ newToken = await manager.refresh();
7501
+ }
7502
+ catch {
7503
+ // Manager unavailable — the retry will fail again, which is expected.
7504
+ newToken = undefined;
7505
+ }
7255
7506
  if (!newToken || !this.requestContext) {
7256
7507
  // If we can't get a new token or don't have request context,
7257
7508
  // the retry will fail again but that's expected
@@ -7312,9 +7563,106 @@ function buildCsrfRetryInterceptor() {
7312
7563
  };
7313
7564
  }
7314
7565
 
7566
+ /**
7567
+ * The context-seed key under which a custom command forwards its JWT mint
7568
+ * parameters. The framework's `buildServiceDescriptor` merges the request init's
7569
+ * `__contextSeed` onto the per-request interceptor context, so this interceptor
7570
+ * reads the params off `context[JWT_MINT_PARAMS_SEED_KEY]`.
7571
+ *
7572
+ * This is a cross-team contract: the custom command writes this key, this
7573
+ * interceptor reads it. It is intentionally an opaque key — OneStore does not
7574
+ * define it and never inspects the param shape; the typed shape lives in the
7575
+ * resolver.
7576
+ */
7577
+ const JWT_MINT_PARAMS_SEED_KEY = 'jwtMintParams';
7578
+ /**
7579
+ * Returns `true` if the fetch arguments already carry an `Authorization` header,
7580
+ * across the three shapes the framework's `setHeader` handles: a `Request`
7581
+ * resource's own headers, an `options.headers` `Headers` instance, or a plain
7582
+ * record. The descriptor's guarded legacy interceptor uses this to skip when the
7583
+ * parameterized interceptor has already authorized the request — avoiding a second
7584
+ * mint and the throw `setHeaderAuthorization` raises on an existing header.
7585
+ */
7586
+ function hasAuthorizationHeader([resource, options]) {
7587
+ if (resource instanceof Request && resource.headers.has('Authorization')) {
7588
+ return true;
7589
+ }
7590
+ const headers = options?.headers;
7591
+ if (headers === undefined) {
7592
+ return false;
7593
+ }
7594
+ if (headers instanceof Headers) {
7595
+ return headers.has('Authorization');
7596
+ }
7597
+ if (Array.isArray(headers)) {
7598
+ return headers.some(([name]) => name.toLowerCase() === 'authorization');
7599
+ }
7600
+ return Reflect.has(headers, 'Authorization');
7601
+ }
7602
+ /**
7603
+ * Builds the request interceptor that bridges the per-request context seed to the
7604
+ * dispatching SFAP JwtManager for the **parameterized** mint path.
7605
+ *
7606
+ * For a parameterized SFAP command, the context carries `jwtMintParams`. This
7607
+ * interceptor reads them, calls `jwtManager.getJwt(params)` (which the dispatching
7608
+ * resolver routes to the parameterized resolver), attaches the `Authorization`
7609
+ * header, and rewrites the request URL via the minted `baseUri`. The presence of
7610
+ * that `Authorization` header is the signal the descriptor's guarded legacy
7611
+ * interceptor uses to skip — so the legacy path does not mint a second time.
7612
+ *
7613
+ * For a legacy parameterless command the context carries no `jwtMintParams`, so
7614
+ * this interceptor **early-returns on its first line** and the request flows
7615
+ * unchanged to the legacy `buildJwtRequestHeaderInterceptor` — guaranteeing zero
7616
+ * behavior change for non-opted-in adapters.
7617
+ *
7618
+ * Lives in `lds-lightning-platform` (not OneStore) beside the existing SFAP/CSRF
7619
+ * interceptors, per the JWT-parameterization ADR §5: OneStore provides only the
7620
+ * generic interceptor mechanism and the opaque context-seed channel; the service-
7621
+ * specific bridge is a runtime-layer concern.
7622
+ *
7623
+ * @param jwtManager - the dispatching SFAP JwtManager
7624
+ * @param jwtRequestModifier - applies the minted `extraInfo.baseUri` to the request URL
7625
+ */
7626
+ function buildJwtParameterizationInterceptor(jwtManager, jwtRequestModifier = (_extraInfo, fetchArgs) => fetchArgs) {
7627
+ return (fetchArgs, context) => {
7628
+ const jwtContext = context;
7629
+ const mintParams = jwtContext?.[JWT_MINT_PARAMS_SEED_KEY];
7630
+ // No mint params → not a parameterized request. Do nothing; the legacy
7631
+ // interceptor handles it. This MUST be the first statement so non-opted-in
7632
+ // SFAP commands observe no work at all.
7633
+ if (mintParams === undefined) {
7634
+ return resolvedPromiseLike$2(fetchArgs);
7635
+ }
7636
+ return resolvedPromiseLike$2(jwtManager.getJwt(mintParams)).then((token) => {
7637
+ const fetchArgsWithAuthorization = setHeaderAuthorization(token, fetchArgs);
7638
+ return token.extraInfo
7639
+ ? jwtRequestModifier(token.extraInfo, fetchArgsWithAuthorization)
7640
+ : fetchArgsWithAuthorization;
7641
+ });
7642
+ };
7643
+ }
7644
+
7315
7645
  const SFAP_BASE_URL = 'api.salesforce.com';
7646
+ function buildDispatchingSfapJwtResolver(legacyResolver, parameterizedResolver) {
7647
+ return {
7648
+ getJwt(params) {
7649
+ if (params === undefined) {
7650
+ return legacyResolver.getJwt();
7651
+ }
7652
+ return parameterizedResolver.getJwt(params);
7653
+ },
7654
+ };
7655
+ }
7656
+ // The parameterized mint is dispatched over **Aura transport**:
7657
+ // the resolver calls the SFAP Lightning JWT Service's auto-generated Aura method
7658
+ // `SalesforceApiPlatformController.postSFAPLightningJwtService`, so session + CSRF
7659
+ // are handled inside the Aura stack. This mirrors the legacy parameterless
7660
+ // `platformSfapJwtResolver`, which already mints over Aura. The minted JWT is then
7661
+ // attached as a Bearer token on the downstream SFAP API request (which stays HTTP).
7662
+ const parameterizedSfapJwtResolver = new ParameterizedSfapJwtAuraResolver();
7663
+ const sfapJwtResolver = buildDispatchingSfapJwtResolver(platformSfapJwtResolver, parameterizedSfapJwtResolver);
7316
7664
  const sfapJwtRepository = new JwtRepository();
7317
- const sfapJwtManager = new JwtManager(sfapJwtRepository, platformSfapJwtResolver);
7665
+ const sfapJwtManager = new JwtManager(sfapJwtRepository, sfapJwtResolver);
7318
7666
  function prefetchSfapJwt() {
7319
7667
  const maybePromise = sfapJwtManager.getJwt();
7320
7668
  if ('then' in maybePromise) {
@@ -7325,8 +7673,12 @@ function prefetchSfapJwt() {
7325
7673
  function buildJwtAuthorizedSfapFetchServiceDescriptor(logger) {
7326
7674
  const jwtAuthorizedFetchService = buildServiceDescriptor$2({
7327
7675
  createContext: createInstrumentationIdContext(),
7328
- request: [buildThirdPartyTrackerRegisterInterceptor(), buildJwtRequestInterceptor(logger)],
7329
- retry: buildCsrfRetryInterceptor(),
7676
+ request: [
7677
+ buildThirdPartyTrackerRegisterInterceptor(),
7678
+ buildJwtParameterizationInterceptor(sfapJwtManager, buildSfapJwtRequestModifier(logger)),
7679
+ // Guarded so it is skipped once the parameterized interceptor handled the request.
7680
+ buildGuardedLegacyJwtRequestInterceptor(logger),
7681
+ ],
7330
7682
  finally: [buildThirdPartyTrackerFinishInterceptor()],
7331
7683
  });
7332
7684
  return {
@@ -7406,8 +7758,13 @@ function buildUnauthorizedFetchServiceDescriptor() {
7406
7758
  tags: { authenticationScopes: '' },
7407
7759
  };
7408
7760
  }
7409
- function buildJwtRequestInterceptor(logger) {
7410
- const jwtRequestModifier = ({ baseUri }, [resource, request]) => {
7761
+ /**
7762
+ * The `JwtRequestModifier` shared by both the legacy and parameterized SFAP
7763
+ * interceptors: it rewrites the request URL's host/protocol to the minted
7764
+ * `extraInfo.baseUri` (only for `api.salesforce.com` resources).
7765
+ */
7766
+ function buildSfapJwtRequestModifier(logger) {
7767
+ return ({ baseUri }, [resource, request]) => {
7411
7768
  if (typeof resource !== 'string' && !(resource instanceof URL)) {
7412
7769
  // istanbul ignore else: this will not be tested in NODE_ENV = production for test coverage
7413
7770
  if (process.env.NODE_ENV !== 'production') {
@@ -7425,8 +7782,22 @@ function buildJwtRequestInterceptor(logger) {
7425
7782
  url.protocol = overrideUrl.protocol;
7426
7783
  return [url, request];
7427
7784
  };
7428
- const jwtRequestHeaderInterceptor = buildJwtRequestHeaderInterceptor(sfapJwtManager, jwtRequestModifier);
7429
- return jwtRequestHeaderInterceptor;
7785
+ }
7786
+ function buildJwtRequestInterceptor(logger) {
7787
+ return buildJwtRequestHeaderInterceptor(sfapJwtManager, buildSfapJwtRequestModifier(logger));
7788
+ }
7789
+ /**
7790
+ * Wraps the legacy `buildJwtRequestHeaderInterceptor` with a pass-through guard:
7791
+ * when the parameterized interceptor has already authorized the request (an
7792
+ * `Authorization` header is present), this returns `args` untouched so the legacy
7793
+ * interceptor does not mint a second time or attempt to set a duplicate
7794
+ * Authorization header (which `setHeaderAuthorization` throws on). For every legacy
7795
+ * (non-parameterized) request no Authorization header is present yet, so the legacy
7796
+ * interceptor runs exactly as before — a strict pass-through.
7797
+ */
7798
+ function buildGuardedLegacyJwtRequestInterceptor(logger) {
7799
+ const legacyInterceptor = buildJwtRequestInterceptor(logger);
7800
+ return (args) => hasAuthorizationHeader(args) ? resolvedPromiseLike$2(args) : legacyInterceptor(args);
7430
7801
  }
7431
7802
 
7432
7803
  const PDL_EXECUTE_ASYNC_OPTIONS = {
@@ -10084,11 +10455,12 @@ function getLexRuntimeDefaultInterceptorConfig(logger) {
10084
10455
  buildTransportMarksSendInterceptor(),
10085
10456
  buildCsrfTokenInterceptor(),
10086
10457
  buildEntityEncodingInterceptor(),
10458
+ buildFirstPartyHeaderInterceptor(),
10087
10459
  ],
10088
10460
  retry: buildCsrfRetryInterceptor(),
10089
10461
  response: [
10090
10462
  buildLexRuntime5xxStatusResponseInterceptor(logger),
10091
- buildLexRuntimeAuthExpirationRedirectResponseInterceptor(logger),
10463
+ buildLexRuntimeSessionExpirationResponseInterceptor(logger),
10092
10464
  buildTransportMarksReceiveInterceptor(),
10093
10465
  buildActionMarksReceiveInterceptor(),
10094
10466
  ],
@@ -10105,7 +10477,7 @@ function buildLexRuntimeAllow5xxFetchServiceDescriptor(logger, retryService) {
10105
10477
  ...config,
10106
10478
  // Omit 5xx interceptor - allow 5xx responses to pass through
10107
10479
  response: [
10108
- buildLexRuntimeAuthExpirationRedirectResponseInterceptor(logger),
10480
+ buildLexRuntimeSessionExpirationResponseInterceptor(logger),
10109
10481
  buildTransportMarksReceiveInterceptor(),
10110
10482
  buildActionMarksReceiveInterceptor(),
10111
10483
  ],
@@ -10195,6 +10567,149 @@ class FetchThrottlingRetryPolicy extends RetryPolicy {
10195
10567
  }
10196
10568
  }
10197
10569
 
10570
+ const CSRF_TOKEN_KEY = 'salesforce_csrf_token';
10571
+ const CSRF_STORAGE_NAME = 'ldsCSRFToken';
10572
+ const BASE_URI = '/services/data/v68.0';
10573
+ const UI_API_BASE_URI = `${BASE_URI}/ui-api`;
10574
+ const CSRF_TOKEN_ENDPOINT = `${UI_API_BASE_URI}/session/csrf`;
10575
+ const CSRF_STORAGE_CONFIG = {
10576
+ name: CSRF_STORAGE_NAME,
10577
+ persistent: true,
10578
+ secure: true,
10579
+ maxSize: 1024,
10580
+ expiration: 24 * 60 * 60,
10581
+ clearOnInit: false,
10582
+ debugLogging: false,
10583
+ };
10584
+ /**
10585
+ * Reads the CSRF token Aura preloads onto the bootstrap payload. When the
10586
+ * Connect Framework CSRF token is minted at template-render time it is exposed
10587
+ * via `$A.clientService.getConnectCsrfToken()`. Reading it here lets the first
10588
+ * state-changing request skip the cold `/ui-api/session/csrf` round trip.
10589
+ *
10590
+ * Defensive against a missing getter or server killswitch: returns `undefined`,
10591
+ * never throws.
10592
+ */
10593
+ function readPreloadedToken() {
10594
+ const clientService = getAuraClientService();
10595
+ if (typeof clientService?.getConnectCsrfToken !== 'function') {
10596
+ return undefined;
10597
+ }
10598
+ try {
10599
+ const token = clientService.getConnectCsrfToken();
10600
+ return typeof token === 'string' && token ? token : undefined;
10601
+ }
10602
+ catch {
10603
+ return undefined;
10604
+ }
10605
+ }
10606
+ /**
10607
+ * Fetches a fresh CSRF token from the server. Resolves `undefined` (not a
10608
+ * rejection) on any non-ok response, missing token, or network/parse error so
10609
+ * the manager treats it as "no value" and leaves any cached token intact. The
10610
+ * CSRF retry policy — not this fetch — owns retry semantics.
10611
+ */
10612
+ async function fetchFreshToken() {
10613
+ try {
10614
+ const response = await fetch(CSRF_TOKEN_ENDPOINT, {
10615
+ method: 'GET',
10616
+ credentials: 'same-origin',
10617
+ });
10618
+ if (!response.ok) {
10619
+ return undefined;
10620
+ }
10621
+ const data = await response.json();
10622
+ const token = data.csrfToken;
10623
+ return typeof token === 'string' && token ? token : undefined;
10624
+ }
10625
+ catch {
10626
+ return undefined;
10627
+ }
10628
+ }
10629
+ /**
10630
+ * Adapts the durable AuraStorage backing store to the {@link ResourceStorage}
10631
+ * contract the renewable-resource manager expects.
10632
+ *
10633
+ * The Aura-preloaded token is written into the store **once at creation** (only
10634
+ * when the store is otherwise empty), so the first `get()` finds it and the
10635
+ * first mutation skips the network. Seeding the store — rather than having
10636
+ * `get()` fall back to the preload on every empty read — means a later
10637
+ * `clear()` (logout / org switch) is not silently resurrected by the preload.
10638
+ *
10639
+ * Every operation awaits the one-time seed so reads and writes observe a
10640
+ * consistent store. Storage read/write failures are non-fatal.
10641
+ */
10642
+ function buildCsrfResourceStorage() {
10643
+ const storage = createStorage(CSRF_STORAGE_CONFIG);
10644
+ // Pre-seed the preloaded token into the store, once, if the store is empty.
10645
+ const seeded = (async () => {
10646
+ const preloadedToken = readPreloadedToken();
10647
+ if (!storage || !preloadedToken) {
10648
+ return;
10649
+ }
10650
+ try {
10651
+ const existing = await storage.get(CSRF_TOKEN_KEY);
10652
+ if (typeof existing !== 'string' || !existing) {
10653
+ await storage.set(CSRF_TOKEN_KEY, preloadedToken);
10654
+ }
10655
+ }
10656
+ catch {
10657
+ // Non-fatal: fall back to fetching a fresh token on first use.
10658
+ }
10659
+ })();
10660
+ return {
10661
+ get: async () => {
10662
+ await seeded;
10663
+ if (storage) {
10664
+ try {
10665
+ const cached = await storage.get(CSRF_TOKEN_KEY);
10666
+ if (typeof cached === 'string' && cached) {
10667
+ return cached;
10668
+ }
10669
+ }
10670
+ catch {
10671
+ // Non-fatal: treat as a cache miss so the manager fetches.
10672
+ }
10673
+ }
10674
+ return undefined;
10675
+ },
10676
+ set: async (value) => {
10677
+ await seeded;
10678
+ if (storage) {
10679
+ try {
10680
+ await storage.set(CSRF_TOKEN_KEY, value);
10681
+ }
10682
+ catch {
10683
+ // Non-fatal: token is still usable even if caching fails.
10684
+ }
10685
+ }
10686
+ },
10687
+ clear: async () => {
10688
+ await seeded;
10689
+ if (storage) {
10690
+ try {
10691
+ await storage.remove(CSRF_TOKEN_KEY);
10692
+ }
10693
+ catch {
10694
+ // Non-fatal: continue even if the clear fails.
10695
+ }
10696
+ }
10697
+ },
10698
+ };
10699
+ }
10700
+ /**
10701
+ * Builds the CSRF token manager: a {@link BasicRenewableResourceManager} that
10702
+ * lazily caches the token in durable storage, coalesces concurrent fetches onto
10703
+ * a single in-flight request, and dedups refresh storms. Registered as a
10704
+ * provisioner service (`csrfTokenManager`) by `initializeOneStore`.
10705
+ */
10706
+ function buildCsrfTokenManager() {
10707
+ return new BasicRenewableResourceManager({
10708
+ fetch: fetchFreshToken,
10709
+ storage: buildCsrfResourceStorage(),
10710
+ });
10711
+ }
10712
+
10198
10713
  /* eslint-disable no-console */
10199
10714
  /**
10200
10715
  * Default storage configuration for the durable cache
@@ -10789,6 +11304,7 @@ function initializeOneStore(luvio) {
10789
11304
  const retryPolicy = new ComposedRetryPolicy([throttlingPolicy, csrfPolicy]);
10790
11305
  const retryServiceDescriptor = buildServiceDescriptor$8(retryPolicy);
10791
11306
  const retryService = retryServiceDescriptor.service;
11307
+ const csrfTokenManagerServiceDescriptor = buildRenewableResourceManagerDescriptor(CSRF_TOKEN_MANAGER_SERVICE, buildCsrfTokenManager());
10792
11308
  const prefetchSfapJwtServiceDescriptor = {
10793
11309
  type: 'prefetchSfapJwt',
10794
11310
  version: '1.0',
@@ -10836,6 +11352,7 @@ function initializeOneStore(luvio) {
10836
11352
  buildLWCGraphQLWireBindingsServiceDescriptor(),
10837
11353
  configServiceDescriptor,
10838
11354
  prefetchSfapJwtServiceDescriptor,
11355
+ csrfTokenManagerServiceDescriptor,
10839
11356
  ];
10840
11357
  setServices(services);
10841
11358
  }
@@ -10855,4 +11372,4 @@ function ldsEngineCreator() {
10855
11372
  }
10856
11373
 
10857
11374
  export { LexRequestStrategy, PdlPrefetcherEventType, PdlRequestPriority, buildPredictorForContext, configService, ldsEngineCreator as default, initializeLDS, initializeOneStore, notifyUpdateAvailableFactory, registerRequestStrategy, saveRequestAsPrediction, subscribeToPrefetcherEvents, unregisterRequestStrategy, whenPredictionsReady };
10858
- // version: 1.442.0-e47893165a
11375
+ // version: 1.444.0-a7f42f9edf