diff --git a/projects/core/src/features-config/feature-toggles/config/feature-toggles.ts b/projects/core/src/features-config/feature-toggles/config/feature-toggles.ts index 2e200668478..11b9ee7b3d8 100644 --- a/projects/core/src/features-config/feature-toggles/config/feature-toggles.ts +++ b/projects/core/src/features-config/feature-toggles/config/feature-toggles.ts @@ -612,6 +612,15 @@ export interface FeatureTogglesInterface { * Affects: `ReturnOrderComponent` */ enableReturnOrderReturnableQuantityConsigmentFallback?: boolean; + /** + * Enables the `withCredentials` flag for OCC requests. + * + * When enabled, each request will include authentication cookies (such as the `ROUTE` cookie), ensuring session stickiness between the client and the same backend node. + * This is critical for maintaining performance and consistency under load-balanced environments. + * + * Affects: `WithCredentialsInterceptor` + */ + occWithCredentialsByDefault?: boolean; } export const defaultFeatureToggles: Required = { @@ -692,4 +701,5 @@ export const defaultFeatureToggles: Required = { navigationMenuCloseOnSameLinkClick: false, enableQuotePurchaseOrderNumber: false, enableReturnOrderReturnableQuantityConsigmentFallback: false, + occWithCredentialsByDefault: false, }; diff --git a/projects/core/src/occ/base-occ.module.ts b/projects/core/src/occ/base-occ.module.ts index 5d416793cd0..aab9e23d33f 100644 --- a/projects/core/src/occ/base-occ.module.ts +++ b/projects/core/src/occ/base-occ.module.ts @@ -6,13 +6,13 @@ import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { ModuleWithProviders, NgModule } from '@angular/core'; +import { provideDefaultConfigFactory } from '../config/config-providers'; import { provideConfigValidator } from '../config/config-validator/config-validator'; -import { defaultOccConfig } from './config/default-occ-config'; -import { occConfigValidator } from './config/occ-config-validator'; -import { WithCredentialsInterceptor } from './interceptors/with-credentials.interceptor'; import { CmsOccModule } from './adapters/cms/cms-occ.module'; import { SiteContextOccModule } from './adapters/site-context/site-context-occ.module'; -import { provideDefaultConfig } from '../config/config-providers'; +import { defaultOccConfigFactory } from './config/default-occ-config-factory'; +import { occConfigValidator } from './config/occ-config-validator'; +import { WithCredentialsInterceptor } from './interceptors/with-credentials.interceptor'; @NgModule({ imports: [CmsOccModule, SiteContextOccModule], @@ -27,8 +27,8 @@ export class BaseOccModule { useExisting: WithCredentialsInterceptor, multi: true, }, - provideDefaultConfig(defaultOccConfig), provideConfigValidator(occConfigValidator), + provideDefaultConfigFactory(defaultOccConfigFactory), ], }; } diff --git a/projects/core/src/occ/config/default-occ-config-factory.spec.ts b/projects/core/src/occ/config/default-occ-config-factory.spec.ts new file mode 100644 index 00000000000..e7cd6cb535a --- /dev/null +++ b/projects/core/src/occ/config/default-occ-config-factory.spec.ts @@ -0,0 +1,54 @@ +import { TestBed } from '@angular/core/testing'; +import { provideFeatureToggles } from '@spartacus/core'; +import { defaultOccConfig } from './default-occ-config'; +import { defaultOccConfigFactory } from './default-occ-config-factory'; + +describe('defaultOccConfigFactory', () => { + it('should not modify useWithCredentials when no feature toggle is provided', () => { + TestBed.configureTestingModule({ + providers: [], + }); + + const result = TestBed.runInInjectionContext(defaultOccConfigFactory); + + expect(result).toEqual(defaultOccConfig); + expect(result.backend?.occ?.useWithCredentials).toBeUndefined(); + }); + + it('should not modify useWithCredentials when feature toggle is disabled', () => { + TestBed.configureTestingModule({ + providers: [ + provideFeatureToggles({ enableWithCredentialsByDefault: false }), + ], + }); + + const result = TestBed.runInInjectionContext(defaultOccConfigFactory); + + expect(result).toEqual(defaultOccConfig); + expect(result.backend?.occ?.useWithCredentials).toBeUndefined(); + }); + + it('should set useWithCredentials to true when feature toggle is enabled', () => { + TestBed.configureTestingModule({ + providers: [ + provideFeatureToggles({ enableWithCredentialsByDefault: true }), + ], + }); + + const result = TestBed.runInInjectionContext(defaultOccConfigFactory); + + const expectedConfig = { + ...defaultOccConfig, + backend: { + ...defaultOccConfig.backend, + occ: { + ...defaultOccConfig.backend?.occ, + useWithCredentials: true, + }, + }, + }; + + expect(result).toEqual(expectedConfig); + expect(result.backend?.occ?.useWithCredentials).toBe(true); + }); +}); diff --git a/projects/core/src/occ/config/default-occ-config-factory.ts b/projects/core/src/occ/config/default-occ-config-factory.ts new file mode 100644 index 00000000000..9e960a2b8b4 --- /dev/null +++ b/projects/core/src/occ/config/default-occ-config-factory.ts @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2025 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { inject } from '@angular/core'; +import { FeatureToggles } from '@spartacus/core'; +import { defaultOccConfig } from './default-occ-config'; +import { OccConfig } from './occ-config'; + +export function defaultOccConfigFactory(): OccConfig { + const { occWithCredentialsByDefault } = inject(FeatureToggles); + const config = { ...defaultOccConfig }; + + if (occWithCredentialsByDefault) { + if (config.backend?.occ) { + config.backend.occ.useWithCredentials = true; + } + } + return config; +} diff --git a/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts b/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts index ee75e2de1ef..b4d4f594b60 100644 --- a/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts +++ b/projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts @@ -377,6 +377,7 @@ if (environment.cpq) { navigationMenuCloseOnSameLinkClick: true, enableQuotePurchaseOrderNumber: false, enableReturnOrderReturnableQuantityConsigmentFallback: true, + occWithCredentialsByDefault: false, }; return appFeatureToggles; }),