diff --git a/docs/concepts/hybrid-approach.md b/docs/concepts/hybrid-approach.md index c8574d6781..5edbe608e9 100644 --- a/docs/concepts/hybrid-approach.md +++ b/docs/concepts/hybrid-approach.md @@ -16,7 +16,7 @@ A possible scenario would be to have the shopping experience with all its SEO op - ICM 7.10.32.16-LTS or 7.10.38.6-LTS - PWA 2.3.0 -> **NOTE:** The feature is based on the assumption that the PWA and the ICM can read and write each other's cookies. That means that both cookies must have the same domain and the same path. Therefore, the feature only works if the PWA and the ICM are running in the same domain. +> **NOTE:** The feature is based on the assumption that the PWA and the ICM can read and write each other's cookies. That means that cookies written by the PWA and ICM must have the same domain and the same path. This works since all Responsive Starter Store requests and responses are proxied through the PWA SSR simulating a common domain. ## Architectural Concept @@ -55,7 +55,7 @@ The server-side rendering process must be started with `SSR_HYBRID=1`. In addition, the PWA must be run with secure URLs as well. To achieve this locally, set the environment variable `SSL=1` and provide a valid certificate (see [SSR Startup](../guides/ssr-startup.md#running-with-https)). -> :warning: **Don't use this option for production environments**, as those should not use the local certificates provide via the `dist`folder. +> :warning: **Don't use this option for production environments**, as those should not use the local certificates provide via the `dist` folder. > :warning: **Only for development environments**: It might be necessary to set `TRUST_ICM=1` if the used development ICM is deployed with an insecure certificate. @@ -88,24 +88,18 @@ However, `pwa` and `icmBuild` are used in the client application where [named ca ## PWA Adaptions With version 0.23.0 the PWA was changed to no longer reuse the Responsive Starter Store application types but rather be based upon the newly introduced headless application type for REST Clients - `intershop.REST`. -This application type is completely independent of the Responsive Starter Store and is not suitable for storefront setups using the hybrid approach. -For this reason, the PWA must be adapted to work with the Responsive Starter Store again. +This application type is completely independent of the Responsive Starter Store. +For this reason, the PWA must be configured to know which application it has to use to work with the Responsive Starter Store again (`hybridApplication`). -**Migration Steps to prepare the PWA for the hybrid approach with the Responsive Starter Store** +**Steps to prepare the PWA for the hybrid approach with the Responsive Starter Store** - Use a current PWA version -- Revert the following Git commits: - -| commit | comment | -| ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | -| [50dc72ef0](https://github.com/intershop/intershop-pwa/commit/50dc72ef083d6bee3c33edebef275b85762db618) | feat: switch to the new headless REST application type CMS content model (#302) | -| [741454c8c](https://github.com/intershop/intershop-pwa/commit/741454c8c839dd001a3943236172d75ffd05541d) | feat: switch to the new headless REST application type applications demo content (#302) | - -- Configure the correct `icmApplication` setting -- Add needed PWA specific Content Includes in the Responsive Starter Store - - Via `componentEntryPointDefinitions` in the ICM project source code +- Configure `icmApplication` setting to denote the `intershop.REST` based application used by the PWA (this is in the demo scenario just `rest`). +- Configure `hybridApplication` setting to denote the Responsive Starter Store application (this is usually `-`). - Follow the Hybrid configuration setup +> **NOTE:** If for some reason the CMS content of the Responsive Starter Store should directly be reused in the PWA in a hybrid approach, the PWA needs some code adaptions and has to use the same application as the Responsive Starter Store. For more details see the older version of this documentation - [Hybrid Approach - PWA Adaptions (3.0.0)](https://github.com/intershop/intershop-pwa/blob/3.0.0/docs/concepts/hybrid-approach.md#pwa-adaptions). + # Further References - [Guide - Building and Running Server-Side Rendering](../guides/ssr-startup.md) diff --git a/nginx/templates/multi-channel.conf.tmpl b/nginx/templates/multi-channel.conf.tmpl index 427bf87c87..7e2be4b7ec 100644 --- a/nginx/templates/multi-channel.conf.tmpl +++ b/nginx/templates/multi-channel.conf.tmpl @@ -5,7 +5,7 @@ {{- $application := "" }}{{ if (has . "application") }}{{ $application = join ( slice ";application" .application ) "=" }}{{ end }} {{- $identityProvider := "" }}{{ if (has . "identityProvider") }}{{ $identityProvider = .identityProvider }}{{ end }} {{- $lang := "default" }}{{ if (has . "lang") }}{{ $lang = .lang }}{{ end }} - {{- $currency := "" }}{{ if (has . "currency") }}{{ $currency = join ( slice ";currency" .currency ) "=" }}{{ end }} + {{- $currency := "" }}{{ if (has . "currency") }}{{ $currency = .currency }}{{ end }} {{- $features := "" }}{{ if (has . "features") }}{{ $features = join ( slice ";features" .features ) "=" }}{{ end }} {{- $addFeatures := "" }}{{ if (has . "addFeatures") }}{{ $addFeatures = join ( slice ";addFeatures" .addFeatures ) "=" }}{{ end }} {{- $theme := "" }}{{ if (has . "theme") }}{{ $theme = join ( slice ";theme" .theme ) "=" }}{{ end }} diff --git a/server.ts b/server.ts index 8ba8272976..bcfc7ca1ca 100644 --- a/server.ts +++ b/server.ts @@ -6,7 +6,14 @@ import { join } from 'path'; import * as robots from 'express-robots-txt'; import * as fs from 'fs'; import * as proxy from 'express-http-proxy'; -import { AppServerModule, ICM_WEB_URL, HYBRID_MAPPING_TABLE, environment, APP_BASE_HREF } from './src/main.server'; +import { + AppServerModule, + ICM_WEB_URL, + HYBRID_MAPPING_TABLE, + environment, + APP_BASE_HREF, + ICM_CONFIG_MATCH, +} from './src/main.server'; import { ngExpressEngine } from '@nguniversal/express-engine'; import { getDeployURLFromEnv, setDeployUrlInFile } from './src/ssr/deploy-url'; @@ -192,8 +199,16 @@ export function app() { for (const entry of HYBRID_MAPPING_TABLE) { const icmUrlRegex = new RegExp(entry.icm); const pwaUrlRegex = new RegExp(entry.pwa); - if (icmUrlRegex.exec(url) && entry.handledBy === 'pwa') { - newUrl = url.replace(icmUrlRegex, `/${entry.pwaBuild}`); + const icmMatchArray = icmUrlRegex.exec(url); + if (icmMatchArray && entry.handledBy === 'pwa') { + const config: { [is: string]: string } = { + ...icmMatchArray.groups, + application: environment.icmApplication || '-', + }; + // Rewrite configuration part of incoming ICM url + const _url = url.replace(new RegExp(ICM_CONFIG_MATCH), buildICMWebURL(config)); + // Build pwa URL based on equally named-groups of ICM url + newUrl = _url.replace(icmUrlRegex, `/${entry.pwaBuild}`); break; } else if (pwaUrlRegex.exec(url) && entry.handledBy === 'icm') { const config: { [is: string]: string } = {}; @@ -216,15 +231,9 @@ export function app() { config.channel = environment.icmChannel; } - if (/;application=[^;]*/.test(url)) { - config.application = /;application=([^;]*)/.exec(url)[1]; - } else { - config.application = environment.icmApplication || '-'; - } + config.application = environment.hybridApplication || environment.icmApplication; - const build = [ICM_WEB_URL, entry.icmBuild] - .join('/') - .replace(/\$<(\w+)>/g, (match, group) => config[group] || match); + const build = [buildICMWebURL(config), entry.icmBuild].join('/'); newUrl = url.replace(pwaUrlRegex, build).replace(/;.*/g, ''); break; } @@ -239,6 +248,9 @@ export function app() { } }; + const buildICMWebURL = (config: { [is: string]: string } = {}): string => + ICM_WEB_URL.replace(/\$<(\w+)>/g, (match, group) => config[group] || match); + if (process.env.SSR_HYBRID) { server.use('*', hybridRedirect); } diff --git a/src/app/core/store/core/configuration/configuration.reducer.ts b/src/app/core/store/core/configuration/configuration.reducer.ts index 72805ce2df..4dcbbdaf6f 100644 --- a/src/app/core/store/core/configuration/configuration.reducer.ts +++ b/src/app/core/store/core/configuration/configuration.reducer.ts @@ -13,6 +13,7 @@ export interface ConfigurationState { serverStatic?: string; channel?: string; application?: string; + hybridApplication?: string; identityProvider?: string; identityProviders?: { [id: string]: { type?: string; [key: string]: unknown } }; features?: string[]; @@ -34,6 +35,7 @@ const initialState: ConfigurationState = { serverStatic: undefined, channel: undefined, application: undefined, + hybridApplication: undefined, features: undefined, addFeatures: [], defaultLocale: environment.defaultLocale, diff --git a/src/app/core/store/core/configuration/configuration.selectors.ts b/src/app/core/store/core/configuration/configuration.selectors.ts index af3b6053ff..4211f3ef4a 100644 --- a/src/app/core/store/core/configuration/configuration.selectors.ts +++ b/src/app/core/store/core/configuration/configuration.selectors.ts @@ -9,7 +9,12 @@ import { ConfigurationState } from './configuration.reducer'; export const getConfigurationState = createSelector(getCoreState, state => state.configuration); -export const getICMApplication = createSelector(getConfigurationState, state => state.application || '-'); +const getICMApplication = createSelector(getConfigurationState, state => state.application || '-'); + +export const getResponsiveStarterStoreApplication = createSelector( + getConfigurationState, + state => state.hybridApplication || '-' +); export const getICMServerURL = createSelector(getConfigurationState, state => state.baseURL && state.server ? `${state.baseURL}/${state.server}` : undefined diff --git a/src/app/core/store/hybrid/hybrid.selectors.ts b/src/app/core/store/hybrid/hybrid.selectors.ts index 5dd9dae85f..5be7445456 100644 --- a/src/app/core/store/hybrid/hybrid.selectors.ts +++ b/src/app/core/store/hybrid/hybrid.selectors.ts @@ -4,7 +4,7 @@ import { getConfigurationState, getCurrentCurrency, getCurrentLocale, - getICMApplication, + getResponsiveStarterStoreApplication, } from 'ish-core/store/core/configuration'; import { ICM_WEB_URL } from '../../../../hybrid/default-url-mapping-table'; @@ -13,7 +13,7 @@ export const getICMWebURL = createSelector( getConfigurationState, getCurrentLocale, getCurrentCurrency, - getICMApplication, + getResponsiveStarterStoreApplication, (state, locale, currency, application) => ICM_WEB_URL.replace('$', state.channel) .replace('$', locale) diff --git a/src/environments/environment.model.ts b/src/environments/environment.model.ts index 8af61559cd..d748e9355d 100644 --- a/src/environments/environment.model.ts +++ b/src/environments/environment.model.ts @@ -14,6 +14,7 @@ export interface Environment { icmServerStatic: string; icmChannel: string; icmApplication?: string; + hybridApplication?: string; // array of REST path expressions that should always be mocked apiMockPaths?: string[]; @@ -138,6 +139,7 @@ export const ENVIRONMENT_DEFAULTS: Omit = { icmServer: 'INTERSHOP/rest/WFS', icmServerStatic: 'INTERSHOP/static/WFS', icmApplication: 'rest', + hybridApplication: '-', identityProvider: 'ICM', /* FEATURE TOGGLES */ diff --git a/src/hybrid/default-url-mapping-table.ts b/src/hybrid/default-url-mapping-table.ts index 5eb87f1b89..6dc6275d1f 100644 --- a/src/hybrid/default-url-mapping-table.ts +++ b/src/hybrid/default-url-mapping-table.ts @@ -1,4 +1,4 @@ -const ICM_CONFIG_MATCH = `^/INTERSHOP/web/WFS/(?[\\w-]+)/(?[\\w-]+)/(?[\\w-]+)/[\\w-]+`; +export const ICM_CONFIG_MATCH = `^/INTERSHOP/web/WFS/(?[\\w-]+)/(?[\\w-]+)/(?[\\w-]+)/(?[\\w-]+)`; const PWA_CONFIG_BUILD = ';channel=$;lang=$;application=$'; @@ -99,7 +99,7 @@ export const HYBRID_MAPPING_TABLE: HybridMappingEntry[] = [ pwaBuild: `account${PWA_CONFIG_BUILD}`, pwa: '^/account.*$', icmBuild: 'ViewUserAccount-Start', - handledBy: 'icm', + handledBy: 'pwa', }, { id: 'Register', diff --git a/src/main.server.ts b/src/main.server.ts index 5f74055b5d..0b65bab542 100644 --- a/src/main.server.ts +++ b/src/main.server.ts @@ -11,5 +11,5 @@ if (PRODUCTION_MODE) { export { AppServerModule } from './app/app.server.module'; export { environment } from './environments/environment'; -export { HYBRID_MAPPING_TABLE, ICM_WEB_URL } from './hybrid/default-url-mapping-table'; +export { HYBRID_MAPPING_TABLE, ICM_WEB_URL, ICM_CONFIG_MATCH } from './hybrid/default-url-mapping-table'; export { APP_BASE_HREF } from '@angular/common';