@zengenti/contensis-react-base 4.0.0-beta.6 → 4.0.0-beta.61

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.
Files changed (208) hide show
  1. package/README.md +14 -1
  2. package/cjs/{App-vZrUfVgQ.js → App-TTUKj85f.js} +498 -104
  3. package/cjs/App-TTUKj85f.js.map +1 -0
  4. package/cjs/{ChangePassword.container-ECjEXixF.js → ChangePassword.container-C4Du3Wb1.js} +57 -50
  5. package/cjs/ChangePassword.container-C4Du3Wb1.js.map +1 -0
  6. package/cjs/{SSRContext-DVj_QAC1.js → ContensisDeliveryApi-gN3_MHEl.js} +32 -74
  7. package/cjs/ContensisDeliveryApi-gN3_MHEl.js.map +1 -0
  8. package/cjs/CookieConstants-DfPiWCRZ.js +12 -0
  9. package/cjs/CookieConstants-DfPiWCRZ.js.map +1 -0
  10. package/{esm/CookieHelper.class-FTURFpz3.js → cjs/CookieHelper.class-Det3qfdU.js} +4 -6
  11. package/cjs/CookieHelper.class-Det3qfdU.js.map +1 -0
  12. package/cjs/{RouteLoader-D5Yg7EB5.js → RouteLoader-BM8DyfcF.js} +17 -9
  13. package/cjs/RouteLoader-BM8DyfcF.js.map +1 -0
  14. package/cjs/SSRContext-DotLlTQc.js +116 -0
  15. package/cjs/SSRContext-DotLlTQc.js.map +1 -0
  16. package/cjs/ToJs-BsWqWjdm.js +23 -0
  17. package/cjs/ToJs-BsWqWjdm.js.map +1 -0
  18. package/cjs/{VersionInfo-B_dKCubg.js → VersionInfo-zFPsvS8q.js} +3 -25
  19. package/cjs/VersionInfo-zFPsvS8q.js.map +1 -0
  20. package/cjs/client.js +62 -64
  21. package/cjs/client.js.map +1 -1
  22. package/cjs/contensis-react-base.js +246 -135
  23. package/cjs/contensis-react-base.js.map +1 -1
  24. package/cjs/i18n.js +75 -0
  25. package/cjs/i18n.js.map +1 -0
  26. package/cjs/{ToJs-C9jwV7YB.js → matchGroups-dqONU-vY.js} +2 -22
  27. package/cjs/matchGroups-dqONU-vY.js.map +1 -0
  28. package/cjs/redux.js +8 -6
  29. package/cjs/redux.js.map +1 -1
  30. package/cjs/routing.js +15 -7
  31. package/cjs/routing.js.map +1 -1
  32. package/cjs/{sagas-CbZhaRNd.js → sagas-OfBUtx74.js} +523 -370
  33. package/cjs/sagas-OfBUtx74.js.map +1 -0
  34. package/cjs/search.js +54 -29
  35. package/cjs/search.js.map +1 -1
  36. package/cjs/{selectors-wCs5fHD4.js → selectors-BrxJ8-F8.js} +27 -6
  37. package/cjs/selectors-BrxJ8-F8.js.map +1 -0
  38. package/cjs/selectors-DAQR0uZa.js +18 -0
  39. package/cjs/selectors-DAQR0uZa.js.map +1 -0
  40. package/cjs/slice-5xJMH24n.js +69 -0
  41. package/cjs/slice-5xJMH24n.js.map +1 -0
  42. package/cjs/{store-D07FOXvM.js → store-Dn7vP6G0.js} +52 -4
  43. package/cjs/store-Dn7vP6G0.js.map +1 -0
  44. package/cjs/urls-DGZlAs0y.js +25 -0
  45. package/cjs/urls-DGZlAs0y.js.map +1 -0
  46. package/cjs/user.js +20 -17
  47. package/cjs/user.js.map +1 -1
  48. package/cjs/util-wQwG9vit.js +148 -0
  49. package/cjs/util-wQwG9vit.js.map +1 -0
  50. package/cjs/util.js +80 -22
  51. package/cjs/util.js.map +1 -1
  52. package/cjs/{version-B7XFkBhY.js → version-2FamXHhj.js} +15 -16
  53. package/cjs/version-2FamXHhj.js.map +1 -0
  54. package/cjs/{version-CM-bJ62L.js → version-rFG9Y6_B.js} +2 -2
  55. package/cjs/{version-CM-bJ62L.js.map → version-rFG9Y6_B.js.map} +1 -1
  56. package/esm/{App-DLZweVSp.js → App-DaHtrw85.js} +458 -65
  57. package/esm/App-DaHtrw85.js.map +1 -0
  58. package/esm/{ChangePassword.container-BgzIy8dA.js → ChangePassword.container-CUBtn82K.js} +19 -13
  59. package/esm/ChangePassword.container-CUBtn82K.js.map +1 -0
  60. package/esm/{SSRContext-BE8ElZ3X.js → ContensisDeliveryApi-CvEoOLCl.js} +30 -67
  61. package/esm/ContensisDeliveryApi-CvEoOLCl.js.map +1 -0
  62. package/esm/CookieConstants-DEmbwzYr.js +7 -0
  63. package/esm/CookieConstants-DEmbwzYr.js.map +1 -0
  64. package/{cjs/CookieHelper.class-C3Eqoze9.js → esm/CookieHelper.class-C6rTRl_1.js} +2 -14
  65. package/esm/CookieHelper.class-C6rTRl_1.js.map +1 -0
  66. package/esm/{RouteLoader-xeQBXywk.js → RouteLoader-BwDPahRW.js} +14 -6
  67. package/esm/RouteLoader-BwDPahRW.js.map +1 -0
  68. package/esm/SSRContext-CYxBWky3.js +106 -0
  69. package/esm/SSRContext-CYxBWky3.js.map +1 -0
  70. package/esm/ToJs-BnRRHk6f.js +17 -0
  71. package/esm/ToJs-BnRRHk6f.js.map +1 -0
  72. package/esm/{VersionInfo-Cno7K0OA.js → VersionInfo-By2ZCZOh.js} +4 -24
  73. package/esm/VersionInfo-By2ZCZOh.js.map +1 -0
  74. package/esm/client.js +62 -63
  75. package/esm/client.js.map +1 -1
  76. package/esm/contensis-react-base.js +239 -130
  77. package/esm/contensis-react-base.js.map +1 -1
  78. package/esm/i18n.js +64 -0
  79. package/esm/i18n.js.map +1 -0
  80. package/esm/{ToJs-CNzfvyxJ.js → matchGroups-_w8BwzCC.js} +3 -18
  81. package/esm/matchGroups-_w8BwzCC.js.map +1 -0
  82. package/esm/redux.js +11 -8
  83. package/esm/redux.js.map +1 -1
  84. package/esm/routing.js +14 -7
  85. package/esm/routing.js.map +1 -1
  86. package/esm/{sagas-xJU-zOpn.js → sagas-BZWjx5by.js} +511 -357
  87. package/esm/sagas-BZWjx5by.js.map +1 -0
  88. package/esm/search.js +73 -47
  89. package/esm/search.js.map +1 -1
  90. package/esm/{selectors-DO2ocdOp.js → selectors-8ROQrTd7.js} +25 -7
  91. package/esm/selectors-8ROQrTd7.js.map +1 -0
  92. package/esm/selectors-DcmvOeX2.js +10 -0
  93. package/esm/selectors-DcmvOeX2.js.map +1 -0
  94. package/esm/slice-C6JLQik8.js +63 -0
  95. package/esm/slice-C6JLQik8.js.map +1 -0
  96. package/esm/{store-3u0RzHZ0.js → store-DSjRYsM2.js} +52 -5
  97. package/esm/store-DSjRYsM2.js.map +1 -0
  98. package/esm/urls-tLxo_skx.js +22 -0
  99. package/esm/urls-tLxo_skx.js.map +1 -0
  100. package/esm/user.js +9 -6
  101. package/esm/user.js.map +1 -1
  102. package/esm/util-BafFLYzn.js +136 -0
  103. package/esm/util-BafFLYzn.js.map +1 -0
  104. package/esm/util.js +58 -14
  105. package/esm/util.js.map +1 -1
  106. package/esm/{version-BlsI7hX2.js → version-B75wA6Te.js} +16 -16
  107. package/esm/version-B75wA6Te.js.map +1 -0
  108. package/esm/{version-wnf-TITV.js → version-BQAL8sQO.js} +2 -2
  109. package/esm/{version-wnf-TITV.js.map → version-BQAL8sQO.js.map} +1 -1
  110. package/i18n/package.json +5 -0
  111. package/models/app/pages/VersionInfo/components/VersionInfo.d.ts +1 -1
  112. package/models/app/pages/VersionInfo/components/VersionInfo.styled.d.ts +0 -1
  113. package/models/i18n/index.d.ts +5 -0
  114. package/models/i18n/redux/sagas.d.ts +19 -0
  115. package/models/i18n/redux/selectors.d.ts +11 -0
  116. package/models/i18n/redux/slice.d.ts +198 -0
  117. package/models/i18n/routes.d.ts +8 -0
  118. package/models/i18n/useI18n.hook.d.ts +20 -0
  119. package/models/index.d.ts +1 -0
  120. package/models/models/AppState.d.ts +2 -0
  121. package/models/models/ContentTypeMapping.d.ts +5 -0
  122. package/models/models/Locales.d.ts +11 -0
  123. package/models/models/MatchedRoute.d.ts +5 -1
  124. package/models/models/RouteComponent.d.ts +0 -1
  125. package/models/models/SSRContext.d.ts +4 -4
  126. package/models/models/StaticRoute.d.ts +11 -0
  127. package/models/models/WithEvents.d.ts +8 -0
  128. package/models/models/config/AppConfig.d.ts +2 -0
  129. package/models/models/config/I18n.d.ts +38 -0
  130. package/models/models/config/ServerConfig.d.ts +14 -0
  131. package/models/redux/index.d.ts +2 -1
  132. package/models/redux/sagas/index.d.ts +3 -1
  133. package/models/redux/sagas/injector.d.ts +13 -0
  134. package/models/redux/store/injectors/index.d.ts +26 -0
  135. package/models/redux/store/injectors/inject.d.ts +24 -0
  136. package/models/redux/store/injectors/util.d.ts +2 -0
  137. package/models/redux/store/store.d.ts +13 -4
  138. package/models/redux/util.d.ts +1 -1
  139. package/models/routing/components/RouteLoader.d.ts +3 -3
  140. package/models/routing/httpContext.d.ts +0 -1
  141. package/models/routing/index.d.ts +1 -0
  142. package/models/routing/redux/actions.d.ts +1 -1
  143. package/models/routing/redux/invokeSearch.d.ts +22 -0
  144. package/models/routing/redux/selectors.d.ts +47 -4
  145. package/models/routing/util/expressions.d.ts +1 -1
  146. package/models/routing/util/find-contenttype-mapping.d.ts +3 -1
  147. package/models/search/containers/withListing.d.ts +1 -1
  148. package/models/search/containers/withSearch.d.ts +1 -1
  149. package/models/search/models/Queries.d.ts +3 -5
  150. package/models/search/models/Search.d.ts +43 -13
  151. package/models/search/models/SearchActions.d.ts +61 -18
  152. package/models/search/models/SearchProps.d.ts +11 -10
  153. package/models/search/models/SearchState.d.ts +23 -2
  154. package/models/search/models/SearchUtil.d.ts +3 -3
  155. package/models/search/redux/getIn.d.ts +2 -2
  156. package/models/search/redux/reducers.d.ts +3 -4
  157. package/models/search/redux/sagas.d.ts +13 -14
  158. package/models/search/redux/schema.d.ts +3 -3
  159. package/models/search/redux/selectors.d.ts +64 -42
  160. package/models/search/redux/util.d.ts +10 -1
  161. package/models/search/search/ContensisDeliveryApi.d.ts +6 -26
  162. package/models/search/search/expressions.d.ts +6 -4
  163. package/models/search/search/util.d.ts +9 -7
  164. package/models/search/transformations/state-to-queryparams.mapper.d.ts +1 -1
  165. package/models/server/features/linkdepth-api/search.d.ts +1 -1
  166. package/models/server/features/response-handler/render-stream.d.ts +2 -4
  167. package/models/server/features/static-assets/index.d.ts +4 -3
  168. package/models/server/internalServer.d.ts +1 -2
  169. package/models/server/middleware/subsiteDebug.d.ts +11 -0
  170. package/models/server/root.d.ts +3 -0
  171. package/models/server/util/bundles.d.ts +9 -9
  172. package/models/server/util/jsx.d.ts +2 -14
  173. package/models/user/hocs/withRegistration.d.ts +1 -1
  174. package/models/util/CachedDeliveryApi.d.ts +8 -2
  175. package/models/util/ContensisDeliveryApi.d.ts +2 -4
  176. package/models/util/NoSSR.d.ts +6 -0
  177. package/models/util/SSRContext.d.ts +3 -19
  178. package/models/util/donotuse_useHistory.d.ts +6 -0
  179. package/models/util/errors.d.ts +16 -0
  180. package/models/util/index.d.ts +7 -2
  181. package/models/util/subsite.d.ts +12 -0
  182. package/models/util/urls.d.ts +1 -2
  183. package/models/util/useIsClient.d.ts +6 -0
  184. package/package.json +39 -38
  185. package/cjs/App-vZrUfVgQ.js.map +0 -1
  186. package/cjs/ChangePassword.container-ECjEXixF.js.map +0 -1
  187. package/cjs/CookieHelper.class-C3Eqoze9.js.map +0 -1
  188. package/cjs/RouteLoader-D5Yg7EB5.js.map +0 -1
  189. package/cjs/SSRContext-DVj_QAC1.js.map +0 -1
  190. package/cjs/ToJs-C9jwV7YB.js.map +0 -1
  191. package/cjs/VersionInfo-B_dKCubg.js.map +0 -1
  192. package/cjs/sagas-CbZhaRNd.js.map +0 -1
  193. package/cjs/selectors-wCs5fHD4.js.map +0 -1
  194. package/cjs/store-D07FOXvM.js.map +0 -1
  195. package/cjs/version-B7XFkBhY.js.map +0 -1
  196. package/esm/App-DLZweVSp.js.map +0 -1
  197. package/esm/ChangePassword.container-BgzIy8dA.js.map +0 -1
  198. package/esm/CookieHelper.class-FTURFpz3.js.map +0 -1
  199. package/esm/RouteLoader-xeQBXywk.js.map +0 -1
  200. package/esm/SSRContext-BE8ElZ3X.js.map +0 -1
  201. package/esm/ToJs-CNzfvyxJ.js.map +0 -1
  202. package/esm/VersionInfo-Cno7K0OA.js.map +0 -1
  203. package/esm/sagas-xJU-zOpn.js.map +0 -1
  204. package/esm/selectors-DO2ocdOp.js.map +0 -1
  205. package/esm/store-3u0RzHZ0.js.map +0 -1
  206. package/esm/version-BlsI7hX2.js.map +0 -1
  207. package/models/redux/store/injectors.d.ts +0 -31
  208. package/models/search/search/ToJs.d.ts +0 -4
@@ -2,23 +2,25 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var SSRContext = require('./SSRContext-DVj_QAC1.js');
5
+ var ContensisDeliveryApi = require('./ContensisDeliveryApi-gN3_MHEl.js');
6
6
  var contensisDeliveryApi = require('contensis-delivery-api');
7
7
  var React = require('react');
8
8
  var reactRedux = require('react-redux');
9
+ var slice = require('./slice-5xJMH24n.js');
9
10
  var mapJson = require('jsonpath-mapper');
10
- var sagas = require('./sagas-CbZhaRNd.js');
11
+ var sagas = require('./sagas-OfBUtx74.js');
11
12
  require('reselect');
12
13
  require('immer');
13
14
  require('deep-equal');
14
15
  require('deepmerge');
15
16
  require('query-string');
16
17
  var contensisCoreApi = require('contensis-core-api');
17
- var VersionInfo = require('./VersionInfo-B_dKCubg.js');
18
+ var urls = require('./urls-DGZlAs0y.js');
18
19
  require('isomorphic-fetch');
19
20
  var express = require('express');
20
21
  var http = require('http');
21
22
  var httpProxy = require('http-proxy');
23
+ var App = require('./App-TTUKj85f.js');
22
24
  var fs = require('fs');
23
25
  var path = require('path');
24
26
  var appRootPath = require('app-root-path');
@@ -29,31 +31,38 @@ var styled = require('styled-components');
29
31
  var serialize = require('serialize-javascript');
30
32
  var lodash = require('lodash');
31
33
  var lodashClean = require('lodash-clean');
32
- var CookieHelper_class = require('./CookieHelper.class-C3Eqoze9.js');
34
+ var CookieHelper_class = require('./CookieHelper.class-Det3qfdU.js');
33
35
  var cookiesMiddleware = require('universal-cookie-express');
34
- var store = require('./store-D07FOXvM.js');
35
- var App = require('./App-vZrUfVgQ.js');
36
- var version = require('./version-B7XFkBhY.js');
37
- var selectors = require('./selectors-wCs5fHD4.js');
38
- var RouteLoader = require('./RouteLoader-D5Yg7EB5.js');
36
+ var store = require('./store-Dn7vP6G0.js');
37
+ var version = require('./version-2FamXHhj.js');
38
+ var selectors = require('./selectors-BrxJ8-F8.js');
39
+ var RouteLoader = require('./RouteLoader-BM8DyfcF.js');
39
40
  var stream = require('stream');
40
41
  var server$2 = require('@loadable/server');
41
42
  var chalk = require('chalk');
42
43
  var minifyCssString = require('minify-css-string');
43
44
  var reactCookie = require('react-cookie');
45
+ var reactHelmetAsync = require('react-helmet-async');
44
46
  var server$3 = require('react-router-dom/server');
47
+ var SSRContext = require('./SSRContext-DotLlTQc.js');
48
+ require('./VersionInfo-zFPsvS8q.js');
49
+ require('./CookieConstants-DfPiWCRZ.js');
50
+ require('@reduxjs/toolkit');
45
51
  require('loglevel');
46
52
  require('@redux-saga/core/effects');
53
+ require('./version-rFG9Y6_B.js');
54
+ require('./util-wQwG9vit.js');
55
+ require('./selectors-DAQR0uZa.js');
47
56
  require('./_commonjsHelpers-BJu3ubxk.js');
48
- require('./version-CM-bJ62L.js');
57
+ require('history');
58
+ require('await-to-js');
59
+ require('./ChangePassword.container-C4Du3Wb1.js');
60
+ require('./matchGroups-dqONU-vY.js');
61
+ require('./ToJs-BsWqWjdm.js');
49
62
  require('redux');
50
63
  require('redux-thunk');
51
64
  require('redux-saga');
52
65
  require('redux-injectors-19');
53
- require('history');
54
- require('await-to-js');
55
- require('./ChangePassword.container-ECjEXixF.js');
56
- require('./ToJs-C9jwV7YB.js');
57
66
 
58
67
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
59
68
 
@@ -64,6 +73,7 @@ var http__default = /*#__PURE__*/_interopDefault(http);
64
73
  var httpProxy__default = /*#__PURE__*/_interopDefault(httpProxy);
65
74
  var fs__default = /*#__PURE__*/_interopDefault(fs);
66
75
  var path__default = /*#__PURE__*/_interopDefault(path);
76
+ var appRootPath__default = /*#__PURE__*/_interopDefault(appRootPath);
67
77
  var serialize__default = /*#__PURE__*/_interopDefault(serialize);
68
78
  var cookiesMiddleware__default = /*#__PURE__*/_interopDefault(cookiesMiddleware);
69
79
  var chalk__default = /*#__PURE__*/_interopDefault(chalk);
@@ -240,7 +250,7 @@ const resolveParentEntries = async (parentContentTypeIds, replaceContentTypeIds,
240
250
  });
241
251
  query.fields = params.fields ? [...JSON.parse(params.fields), parentFieldId] : [];
242
252
  if (debug) console.log(`\nResolve parent entries query: \n${JSON.stringify(query.toJSON()).substring(0, 1000)}`);
243
- const parentResults = await SSRContext.cachedSearch.searchUsingPost(query, Number(params.linkDepth || 0), params.projectId);
253
+ const parentResults = await ContensisDeliveryApi.cachedSearch.searchUsingPost(query, Number(params.linkDepth || 0), params.projectId);
244
254
  return mergeResults(results, Util.GetItems(parentResults), replaceContentTypeIds, parentFieldId);
245
255
  };
246
256
 
@@ -286,7 +296,7 @@ class QueryLevelResults {
286
296
  }
287
297
  if (runFirstQuery) {
288
298
  if (this.debug) console.log(`\nLevel ${this.level} - First query: \n${JSON.stringify(query.toJSON()).substring(0, 1000)}`);
289
- this.firstResults = await SSRContext.cachedSearch.searchUsingPost(query, 0, params.projectId);
299
+ this.firstResults = await ContensisDeliveryApi.cachedSearch.searchUsingPost(query, 0, params.projectId);
290
300
 
291
301
  // mapResultsToValidatedLinks
292
302
  for (const linkFieldId of this.linkFieldIds) {
@@ -323,7 +333,7 @@ class QueryLevelResults {
323
333
  }
324
334
  if (runFinalQuery) {
325
335
  if (this.debug) console.log(`\nLevel ${this.level} - Final query: \n${JSON.stringify(query.toJSON()).substring(0, 1000)}`);
326
- this.finalResults = await SSRContext.cachedSearch.searchUsingPost(query, Number(params.linkDepth) || 0, params.projectId);
336
+ this.finalResults = await ContensisDeliveryApi.cachedSearch.searchUsingPost(query, Number(params.linkDepth) || 0, params.projectId);
327
337
  if (this.parent) this.parent.runFinalQuery = true;
328
338
 
329
339
  // mapResultsToValidatedLinks
@@ -464,7 +474,7 @@ class LinkDepthSearchService {
464
474
  };
465
475
  })) || []);
466
476
  if (this.debug) console.log(`\nFinal query: ${derivedIds.reduce((accumulator, object) => accumulator + object.entryIds.length, 0)} derived ids \n${JSON.stringify(query.toJSON()).substring(0, 1000)}`);
467
- const finalQueryResult = await SSRContext.cachedSearch.searchUsingPost(query, Number(params.linkDepth) || 0, params.projectId);
477
+ const finalQueryResult = await ContensisDeliveryApi.cachedSearch.searchUsingPost(query, Number(params.linkDepth) || 0, params.projectId);
468
478
 
469
479
  // Resolve any parent entries
470
480
 
@@ -591,7 +601,7 @@ const makeLinkDepthMiddleware = ({
591
601
  const linkDepthMiddleware = async (req, res) => {
592
602
  try {
593
603
  // Short cache duration copied from canterbury project
594
- VersionInfo.setCachingHeaders(res, {
604
+ urls.setCachingHeaders(res, {
595
605
  cacheControl: 'private',
596
606
  surrogateControl: '10'
597
607
  });
@@ -627,42 +637,66 @@ const makeLinkDepthMiddleware = ({
627
637
  }
628
638
  };
629
639
 
640
+ /**
641
+ * Development proxy for Subsite PoC
642
+ * Catch all routes before they hit CRB handlers
643
+ * and rewrite them to include the subsite base path,
644
+ * this allows us to run the subsite in a subfolder in development
645
+ * In production we will handle this with a path rewrite in the Cloud Dashboard site configuration,
646
+ * @param subsitePath the content base path we will rewrite to
647
+ * @param exceptions an array of path prefixes to ignore when rewriting, useful for ignoring assets that do not live in the subsite base path
648
+ */
649
+ const subsiteDebugMiddleware = (subsitePath, exceptions = []) => (req, res, next) => {
650
+ if (!subsitePath || req.hostname !== 'localhost' || req.path.startsWith('/api/') || exceptions.some(exception => req.path.startsWith(exception))) return next();
651
+ if (!req.path.startsWith(`${subsitePath}/`)) {
652
+ console.warn(`[subsite-debug-middleware] Rewriting (${subsitePath})${req.url}`);
653
+ if (req.path === '/' || req.path === subsitePath) req.url = subsitePath;else req.url = `${subsitePath}${req.url}`;
654
+ res.setHeader('x-crb-subsite-content-path', req.url);
655
+
656
+ // Important to set the subsite_path header as this drives the subsite-scoped routing logic
657
+ req.headers['subsite_path'] = subsitePath;
658
+ }
659
+ next();
660
+ };
661
+
630
662
  const servers$1 = SERVERS; /* global SERVERS */
631
663
  const project = PROJECT; /* global PROJECT */
632
664
  const alias$1 = ALIAS; /* global ALIAS */
633
- const deliveryApiHostname = VersionInfo.url(alias$1, project).api;
665
+ const deliveryApiHostname = urls.urls(alias$1, project).api;
666
+ const proxyTimeoutMs = 45_000;
634
667
  const assetProxy = httpProxy__default.default.createProxyServer();
635
668
  const deliveryProxy = httpProxy__default.default.createProxyServer();
636
669
  const reverseProxies = (app, reverseProxyPaths = []) => {
637
670
  deliveryApiProxy(deliveryProxy, app);
638
- app.all(reverseProxyPaths, (req, res) => {
639
- const target = req.hostname.indexOf('preview-') || req.hostname.indexOf('preview.') || req.hostname === 'localhost' ? servers$1.previewIis || servers$1.iis : servers$1.iis;
671
+ app.all(reverseProxyPaths.map(proxyPath =>
672
+ // Patch to update paths for express v5
673
+ proxyPath.endsWith('/*') ? `${proxyPath.slice(0, -2)}/{*splat}` : proxyPath.endsWith('/**') ? `${proxyPath.slice(0, -3)}/{*splat}` : proxyPath), (req, res) => {
674
+ const target = req.hostname.includes('preview-') || req.hostname.includes('preview.') || req.hostname === 'localhost' ? servers$1.previewIis || servers$1.iis : servers$1.iis;
640
675
  assetProxy.web(req, res, {
641
676
  target,
642
- changeOrigin: true
643
- });
644
- assetProxy.on('error', e => {
645
- /* eslint-disable no-console */
646
- console.log(`Proxy Request for ${req.path} HostName:${req.hostname} failed with ${e}`);
647
- /* eslint-enable no-console */
677
+ changeOrigin: true,
678
+ proxyTimeout: proxyTimeoutMs,
679
+ timeout: proxyTimeoutMs
648
680
  });
649
681
  });
682
+ assetProxy.on('error', (e, req) => {
683
+ console.log(`[assetProxy] "${req.method} ${req.url}" host: ${req.headers.host} failed with ${e}`);
684
+ });
650
685
  };
651
686
  const deliveryApiProxy = (apiProxy, app) => {
652
687
  // This is just here to stop cors requests on localhost. In Production this is mapped using varnish.
653
- app.all(['/api/delivery/*', '/api/forms/*', '/api/image/*', '/authenticate/*'], (req, res) => {
654
- /* eslint-disable no-console */
655
- console.log(`Proxying api request to ${servers$1.alias}`);
688
+ app.all(['/api/delivery/{*splat}', '/api/forms/{*splat}', '/api/image/{*splat}', '/authenticate/{*splat}'], (req, res) => {
689
+ console.log(`[apiProxy] "${req.method} ${App.shorten(req.url)}" target: ${servers$1.alias}`);
656
690
  apiProxy.web(req, res, {
657
691
  target: deliveryApiHostname,
658
- changeOrigin: true
659
- });
660
- apiProxy.on('error', e => {
661
- /* eslint-disable no-console */
662
- console.log(`Proxy request for ${req.path} HostName:${req.hostname} failed with ${e}`);
663
- /* eslint-enable no-console */
692
+ changeOrigin: true,
693
+ proxyTimeout: proxyTimeoutMs,
694
+ timeout: proxyTimeoutMs
664
695
  });
665
696
  });
697
+ apiProxy.on('error', (e, req) => {
698
+ console.log(`[apiProxy] "${req.method} ${req.url}" host: ${req.headers.host} failed with ${e}`);
699
+ });
666
700
  };
667
701
 
668
702
  const CacheDuration = {
@@ -737,9 +771,8 @@ const resolveStartupMiddleware = ({
737
771
  if (maxage) res.set('Cache-Control', `public, max-age=${maxage}`);
738
772
  res.sendFile(startupFileLocation);
739
773
  } catch (sendFileError) {
740
- // eslint-disable-next-line no-console
741
774
  console.log(`Unable to send file startup.js at '${startupFileLocation}'`, sendFileError);
742
- next();
775
+ res.status(404).send();
743
776
  }
744
777
  } else {
745
778
  next();
@@ -747,8 +780,11 @@ const resolveStartupMiddleware = ({
747
780
  };
748
781
 
749
782
  // Serving static assets
783
+ const {
784
+ path: appPath
785
+ } = appRootPath__default.default;
750
786
  const staticAssets = (app, {
751
- appRootPath: appRootPath$1 = appRootPath.path,
787
+ appRootPath = appPath,
752
788
  scripts = {},
753
789
  startupScriptFilename = 'startup.js',
754
790
  staticFolderPath = 'static',
@@ -756,20 +792,18 @@ const staticAssets = (app, {
756
792
  staticRoutePaths = []
757
793
  }) => {
758
794
  app.use([`/${staticRoutePath}`, ...staticRoutePaths.map(p => `/${p}`), `/${staticFolderPath}`], bundleManipulationMiddleware({
759
- appRootPath: appRootPath$1,
795
+ appRootPath,
760
796
  // these maxage values are different in config but the same in runtime,
761
797
  // this one is the true value in seconds
762
798
  maxage: CacheDuration.static,
763
799
  staticFolderPath,
764
800
  staticRoutePath
765
801
  }), resolveStartupMiddleware({
766
- appRootPath: appRootPath$1,
802
+ appRootPath,
767
803
  maxage: CacheDuration.static,
768
804
  startupScriptFilename: scripts.startup || startupScriptFilename,
769
805
  staticFolderPath
770
- }),
771
- // eslint-disable-next-line import/no-named-as-default-member
772
- express__default.default.static(`dist/${staticFolderPath}`, {
806
+ }), express__default.default.static(`dist/${staticFolderPath}`, {
773
807
  // these maxage values are different in config but the same in runtime,
774
808
  // this one is somehow converted and should end up being the same as CacheDuration.static
775
809
  maxAge: CacheDuration.expressStatic
@@ -819,32 +853,59 @@ const handleResponse = (request, response, content, send = 'send') => {
819
853
  * @param response the express Response object
820
854
  * @param stream all chunks are piped to this stream to add additional style elements to each streamed chunk
821
855
  */
822
- const renderStream = (getContextHtml, jsx, response, stream) => {
823
- let header = '';
824
- let footer = '';
856
+ const renderStream = (getContextHtml, jsx, request, response, stream) => {
857
+ // Store timeout reference for cleanup on normal or abnormal termination
858
+ let timeoutId = null;
859
+ const disposeTimeout = () => {
860
+ if (timeoutId) {
861
+ clearTimeout(timeoutId);
862
+ timeoutId = null;
863
+ }
864
+ };
865
+
866
+ // Only used for abnormal termination
867
+ const abortCleanup = err => {
868
+ disposeTimeout();
869
+ stream.destroy(err instanceof Error ? err : undefined);
870
+ abort();
871
+ };
872
+
873
+ // Guard against client disconnect
874
+ request.on('close', () => abortCleanup());
875
+
876
+ // Guard against transform errors
877
+ stream.on('error', err => {
878
+ abortCleanup(err);
879
+ if (!response.headersSent) response.destroy(err);
880
+ });
825
881
  const {
826
882
  abort,
827
883
  pipe
828
884
  } = server$1.renderToPipeableStream(jsx, {
829
885
  onShellReady() {
830
- const html = getContextHtml();
886
+ const html = getContextHtml(false);
831
887
  if (!html) {
832
888
  // this means we have finished with the response already
833
- abort();
889
+ abortCleanup();
834
890
  } else {
835
- [header, footer] = html.split('{{APP}}');
891
+ const header = html.split('{{APP}}')[0];
836
892
  response.setHeader('content-type', 'text/html; charset=utf-8');
837
893
  stream.write(header);
838
894
  pipe(stream);
839
895
  }
840
896
  },
841
897
  onAllReady() {
898
+ const footer = getContextHtml(true).split('{{APP}}')[1];
842
899
  stream.write(footer);
900
+ disposeTimeout(); // Clear the timeout, let stream end naturally
843
901
  },
844
902
  onShellError(error) {
845
- response.statusCode = 500;
846
- response.setHeader('content-type', 'text/html; charset=utf-8');
847
- response.send('<h1>Something went wrong</h1>');
903
+ abortCleanup(error); // Abnormal - destroy everything
904
+ if (!response.headersSent) {
905
+ response.statusCode = 500;
906
+ response.setHeader('content-type', 'text/html; charset=utf-8');
907
+ response.send('<h1>Something went wrong</h1>');
908
+ }
848
909
  console.error(`[renderToPipeableStream:onShellError]`, error);
849
910
  },
850
911
  onError(error) {
@@ -852,10 +913,13 @@ const renderStream = (getContextHtml, jsx, response, stream) => {
852
913
  }
853
914
  });
854
915
 
855
- // Abandon and switch to client rendering if enough time passes.
916
+ // Abandon and switch to client rendering after 30s.
856
917
  // Try lowering this to see the client recover.
857
- setTimeout(() => abort(), 30 * 1000);
858
- stream === null || stream === void 0 || stream.pipe(response);
918
+ timeoutId = setTimeout(() => {
919
+ timeoutId = null;
920
+ abortCleanup();
921
+ }, 30_000);
922
+ stream.pipe(response);
859
923
  };
860
924
 
861
925
  /**
@@ -895,6 +959,23 @@ const styledComponentsStream = sheet => {
895
959
  this.push(styledCSS + renderedHtml);
896
960
  }
897
961
  callback();
962
+ },
963
+ destroy(err, callback) {
964
+ // Called on both stream.destroy() and natural end
965
+ // Stops the sheet intercepting styles & releases its references
966
+
967
+ // try/catch is required if sheet.seal() throws for any reason,
968
+ // callback(err) must still be called, as Node.js stream internals depend
969
+ // on it to complete teardown. Omitting it causes the stream to hang.
970
+ try {
971
+ sheet.seal();
972
+ } catch (sealErr) {
973
+ // Catch any errors from sealing the sheet, we MUST always call the
974
+ // callback to prevent hanging the stream
975
+
976
+ console.error('[styledComponentsStream] sheet.seal() failed - styles may leak:', sealErr);
977
+ }
978
+ callback(err);
898
979
  }
899
980
  });
900
981
  return readerWriter;
@@ -947,7 +1028,8 @@ const loadableChunkExtractors = () => {
947
1028
  statsFile: path__default.default.resolve('dist/legacy/loadable-stats.json')
948
1029
  });
949
1030
  } catch (e) {
950
- console.info('@loadable/server legacy ChunkExtractor not available');
1031
+ // legacy bundling deprecated in v4
1032
+ // console.info('@loadable/server legacy ChunkExtractor not available');
951
1033
  }
952
1034
  commonLoadableExtractor.addChunk = chunk => {
953
1035
  var _modern, _legacy, _legacy2;
@@ -1055,6 +1137,7 @@ const unhandledExceptionHandler = (handleExceptions = handleDefaultEvents) => {
1055
1137
  }
1056
1138
  };
1057
1139
 
1140
+ const logPrefix = '[addHeaders]';
1058
1141
  const addStandardHeaders = (state, response, packagejson, groups) => {
1059
1142
  if (state) {
1060
1143
  try {
@@ -1068,14 +1151,14 @@ const addStandardHeaders = (state, response, packagejson, groups) => {
1068
1151
  // - add `any-update` header that will indiscriminately
1069
1152
  // invalidate the SSR page cache when any content is updated
1070
1153
  const addAnyUpdateHeader = routingSurrogateKeys.length >= 2000 || response.statusCode >= 400 || anyApiError;
1071
- console.info(`[addStandardHeaders] ${addAnyUpdateHeader ? anyUpdateHeader : routingSurrogateKeys.length} surrogate keys for ${response.req.url}`);
1154
+ console.info(`${logPrefix} ${addAnyUpdateHeader ? anyUpdateHeader : routingSurrogateKeys.length} surrogate keys for ${response.req.url}`);
1072
1155
  const surrogateKeys = addAnyUpdateHeader ? anyUpdateHeader : routingSurrogateKeys.join(' ');
1073
1156
  const surrogateKeyHeader = `${packagejson.name}-app ${surrogateKeys}`;
1074
1157
  response.setHeader('surrogate-key', surrogateKeyHeader);
1075
1158
  addVarnishAuthenticationHeaders(state, response, groups);
1076
1159
  response.setHeader('surrogate-control', `max-age=${getCacheDuration(response.statusCode)}`);
1077
1160
  } catch (e) {
1078
- console.info('[addStandardHeaders] Error adding headers', e.message);
1161
+ console.info(`${logPrefix} Error adding headers`, e.message);
1079
1162
  }
1080
1163
  }
1081
1164
  };
@@ -1088,14 +1171,13 @@ const addVarnishAuthenticationHeaders = (state, response, groups = {}) => {
1088
1171
  globalGroups,
1089
1172
  allowedGroups
1090
1173
  } = groups;
1091
- // console.info(globalGroups, allowedGroups);
1092
1174
  let allGroups = Array.from(globalGroups && globalGroups[project] || {});
1093
1175
  if (stateEntry && selectors.getImmutableOrJS(stateEntry, ['authentication', 'isLoginRequired']) && allowedGroups && allowedGroups[project]) {
1094
1176
  allGroups = [...allGroups, ...allowedGroups[project]];
1095
1177
  }
1096
1178
  response.header('x-contensis-viewer-groups', allGroups.join('|'));
1097
1179
  } catch (e) {
1098
- console.info('Error adding authentication header', e);
1180
+ console.info(`${logPrefix} Error adding authentication header`, e);
1099
1181
  }
1100
1182
  }
1101
1183
  };
@@ -1121,14 +1203,17 @@ const replaceHtml = ({
1121
1203
  let responseHTML = '';
1122
1204
  // Serve a blank HTML page with client scripts to load the app in the browser
1123
1205
  if (accessMethod.DYNAMIC) {
1124
- responseHTML = templateHTML.replace('{{TITLE}}', '').replace('{{SEO_CRITICAL_METADATA}}', '').replace('{{CRITICAL_CSS}}', '').replace('{{APP}}', '').replace('{{LOADABLE_CHUNKS}}', bundleTags).replace('{{REDUX_DATA}}', state);
1206
+ responseHTML = templateHTML.replace('{{TITLE}}', '').replace('{{SEO_CRITICAL_METADATA}}', '').replace('{{CRITICAL_CSS}}', '').replace('{{APP}}', '')
1207
+ // .replace('{{LOADABLE_CHUNKS}}', bundleTags)
1208
+ .replace('{{REDUX_DATA}}', state);
1125
1209
  }
1126
1210
 
1127
1211
  // Page fragment served with client scripts and redux data that hydrate the app client side
1128
1212
  else if (accessMethod.FRAGMENT && !accessMethod.STATIC) {
1129
1213
  responseHTML = templateHTMLFragment.replace('{{TITLE}}', title).replace('{{SEO_CRITICAL_METADATA}}', metadata).replace('{{CRITICAL_CSS}}', minifyCssString__default.default(styleTags))
1130
1214
  //.replace('{{APP}}', html)
1131
- .replace('{{LOADABLE_CHUNKS}}', bundleTags).replace('{{REDUX_DATA}}', state);
1215
+ // .replace('{{LOADABLE_CHUNKS}}', bundleTags)
1216
+ .replace('{{REDUX_DATA}}', state);
1132
1217
  }
1133
1218
 
1134
1219
  // Full HTML page served statically
@@ -1142,7 +1227,8 @@ const replaceHtml = ({
1142
1227
  else if (!accessMethod.FRAGMENT && !accessMethod.STATIC) {
1143
1228
  responseHTML = templateHTML.replace('{{TITLE}}', title).replace('{{SEO_CRITICAL_METADATA}}', metadata).replace('{{CRITICAL_CSS}}', styleTags)
1144
1229
  //.replace('{{APP}}', html)
1145
- .replace('{{LOADABLE_CHUNKS}}', bundleTags).replace('{{REDUX_DATA}}', state);
1230
+ // .replace('{{LOADABLE_CHUNKS}}', bundleTags)
1231
+ .replace('{{REDUX_DATA}}', state);
1146
1232
  }
1147
1233
 
1148
1234
  // If react-helmet htmlAttributes are being used,
@@ -1151,7 +1237,12 @@ const replaceHtml = ({
1151
1237
  if (htmlAttributes) {
1152
1238
  responseHTML = responseHTML.replace(/<html?.+?>/, `<html ${htmlAttributes}>`);
1153
1239
  }
1154
- return html ? responseHTML.replace('{{APP}}', html) : responseHTML;
1240
+ responseHTML = html ? responseHTML.replace('{{APP}}', html) : responseHTML;
1241
+
1242
+ // Only replace bundle tags at the very end when we have rendered and are
1243
+ // streaming out the HTML "footer"
1244
+ if (bundleTags) responseHTML = responseHTML.replace('{{LOADABLE_CHUNKS}}', bundleTags);
1245
+ return responseHTML;
1155
1246
  };
1156
1247
 
1157
1248
  /**
@@ -1163,13 +1254,15 @@ const replaceHtml = ({
1163
1254
  */
1164
1255
  const ssrJsxProducer = (ReactApp, {
1165
1256
  providers,
1166
- props,
1167
- ssrAssets
1257
+ props
1258
+ // ssrAssets,
1168
1259
  }) => {
1169
1260
  var _providers$styledComp;
1170
1261
  // Recast ChunkExtractorManager to avoid TS error `Property 'children' does not exist on type...`
1171
1262
  const ChunkExtractor = server$2.ChunkExtractorManager;
1172
- const jsx = /*#__PURE__*/React__default.default.createElement(ChunkExtractor, {
1263
+ const jsx = /*#__PURE__*/React__default.default.createElement(reactHelmetAsync.HelmetProvider, {
1264
+ context: providers.helmet
1265
+ }, /*#__PURE__*/React__default.default.createElement(ChunkExtractor, {
1173
1266
  extractor: providers.loadable.extractor
1174
1267
  }, /*#__PURE__*/React__default.default.createElement(reactCookie.CookiesProvider, {
1175
1268
  cookies: providers.cookies
@@ -1178,16 +1271,20 @@ const ssrJsxProducer = (ReactApp, {
1178
1271
  }, /*#__PURE__*/React__default.default.createElement(RouteLoader.HttpContext.Provider, {
1179
1272
  value: providers.httpContext
1180
1273
  }, /*#__PURE__*/React__default.default.createElement(server$3.StaticRouter, {
1181
- location: providers.router.url
1274
+ location: providers.router.url,
1275
+ future: {
1276
+ v7_startTransition: true,
1277
+ v7_relativeSplatPath: true
1278
+ }
1182
1279
  }, /*#__PURE__*/React__default.default.createElement(SSRContext.SSRContextProvider, {
1183
1280
  accessMethod: providers.ssrContext.accessMethod,
1184
1281
  request: providers.ssrContext.request,
1185
- response: providers.ssrContext.response,
1186
- ssrAssets: ssrAssets
1282
+ response: providers.ssrContext.response
1283
+ // ssrAssets={ssrAssets}
1187
1284
  }, /*#__PURE__*/React__default.default.createElement(ReactApp, {
1188
1285
  routes: props.routes,
1189
1286
  withEvents: props.withEvents
1190
- })))))));
1287
+ }))))))));
1191
1288
 
1192
1289
  // Wrap the JSX in a StyleSheetManager if a ServerStyleSheet is provided
1193
1290
  return !((_providers$styledComp = providers.styledComponents) !== null && _providers$styledComp !== void 0 && _providers$styledComp.sheet) ? jsx : providers.styledComponents.sheet.collectStyles(jsx);
@@ -1195,7 +1292,7 @@ const ssrJsxProducer = (ReactApp, {
1195
1292
 
1196
1293
  const webApp = (app, ReactApp, config) => {
1197
1294
  const {
1198
- stateType = 'immutable',
1295
+ stateType = 'js',
1199
1296
  routes,
1200
1297
  withReducers,
1201
1298
  withSagas,
@@ -1209,8 +1306,12 @@ const webApp = (app, ReactApp, config) => {
1209
1306
  disableSsrRedux,
1210
1307
  enableSsrCookies,
1211
1308
  handleResponses,
1212
- handleExceptions = true
1309
+ handleExceptions = true,
1310
+ i18n
1213
1311
  } = config;
1312
+
1313
+ // process locales in static routes for i18n
1314
+ const localeRoutes = App.createLocaleRoutes(routes);
1214
1315
  const staticRoutePath = config.staticRoutePath || staticFolderPath;
1215
1316
  let isRenderingJsxToString = config.renderToString || false;
1216
1317
  const bundleData = getBundleData(config, staticRoutePath);
@@ -1224,8 +1325,16 @@ const webApp = (app, ReactApp, config) => {
1224
1325
  if (handleExceptions !== false) unhandledExceptionHandler(handleExceptions); // Create `process.on` event handlers for unhandled exceptions (Node v15+)
1225
1326
 
1226
1327
  const versionInfo = getVersionInfo(staticFolderPath);
1227
- app.get('/*', cookiesMiddleware__default.default(), async (request, response) => {
1228
- const url = encodeURI(request.url);
1328
+ app.get('/{*splat}', cookiesMiddleware__default.default(), async (request, response) => {
1329
+ /*
1330
+ * Do not inject url directly into HTML as it can lead to XSS attacks
1331
+ * CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
1332
+ * CWE-96: Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS)
1333
+ * Removed URL encoding as it causes inconsistencies when routes contain encoded characters in SSR
1334
+ * e.g. /search?category=sport%20and%20wellbeing becomes /search?category=sport%2520and%2520wellbeing
1335
+ * // const url = encodeURI(request.url);
1336
+ */
1337
+ const url = request.url;
1229
1338
  const matchedStaticRoute = reactRouterDom.matchRoutes(routes.StaticRoutes, request.path);
1230
1339
  const isStaticRoute = matchedStaticRoute && matchedStaticRoute.length > 0;
1231
1340
  if (isStaticRoute) {
@@ -1260,30 +1369,27 @@ const webApp = (app, ReactApp, config) => {
1260
1369
  }), stateType);
1261
1370
 
1262
1371
  // dispatch any global and non-saga related actions before calling our JSX
1263
- const versionStatus = SSRContext.deliveryApi.getServerSideVersionStatus(request);
1372
+ const versionStatus = ContensisDeliveryApi.deliveryApi.getServerSideVersionStatus(request);
1264
1373
 
1265
1374
  // In server-side blocks world, the hostname requested by the client resides in the x-orig-host header
1266
1375
  // Because of this, we prioritize x-orig-host when setting our hostname
1267
1376
  const hostname = request.headers['x-orig-host'] || request.hostname;
1268
- console.info(`Request for ${request.path} hostname: ${hostname} versionStatus: ${versionStatus}`);
1377
+ const subsitePath = SSRContext.getSubsitePath(request);
1378
+ const subsitePathScript = subsitePath ? `window.subsitePath = ${serialize__default.default(subsitePath)};` : '';
1379
+ console.info(`[webApp] "${request.method} ${request.path}" hostname: ${hostname} versionStatus: ${versionStatus}`);
1269
1380
  store$1.dispatch(version.setVersionStatus(versionStatus));
1270
1381
  store$1.dispatch(version.setVersion(versionInfo.commitRef, versionInfo.buildNo));
1271
1382
  const project = App.pickProject(hostname, request.query);
1272
1383
  const groups = allowedGroups && allowedGroups[project];
1273
1384
  store$1.dispatch(selectors.setCurrentProject(project, groups, hostname));
1385
+ if (i18n) {
1386
+ store$1.dispatch(slice.actions.INIT_LOCALES({
1387
+ locales: {},
1388
+ routes: localeRoutes,
1389
+ ...i18n
1390
+ }));
1391
+ }
1274
1392
  const loadableExtractor = loadableChunkExtractors();
1275
-
1276
- // type ChunkExtractorManagerPropsForReact18 = ChunkExtractorManagerProps & {
1277
- // children?: React.ReactNode;
1278
- // };
1279
-
1280
- // // Recast ChunkExtractorManager to avoid TS error `Property 'children' does not exist on type...`
1281
- // const ChunkExtractor = ChunkExtractorManager as ClassType<
1282
- // ChunkExtractorManagerPropsForReact18,
1283
- // Component<ChunkExtractorManagerPropsForReact18>,
1284
- // ComponentClass<ChunkExtractorManagerPropsForReact18>
1285
- // >;
1286
-
1287
1393
  const ssrCookies = enableSsrCookies ?
1288
1394
  // these cookies are managed by the cookiesMiddleware and contain listeners
1289
1395
  // when cookies are read or written in ssr can be added to the `set-cookie` response header
@@ -1298,12 +1404,17 @@ const webApp = (app, ReactApp, config) => {
1298
1404
  // and read back any context props set by the ReactApp
1299
1405
  const context = {};
1300
1406
 
1407
+ // Per-request helmet context object — populated by HelmetProvider during renderToString
1408
+ // Using a fresh object per request ensures thread safety under concurrent SSR requests
1409
+ const helmetContext = {};
1410
+
1301
1411
  // Amalgamate all props for the various Providers we wrap the ReactApp with
1302
1412
  const jsxProviderProps = {
1303
1413
  loadable: {
1304
1414
  extractor: loadableExtractor.commonLoadableExtractor
1305
1415
  },
1306
1416
  cookies: ssrCookies,
1417
+ helmet: helmetContext,
1307
1418
  redux: store$1,
1308
1419
  httpContext: context,
1309
1420
  router: {
@@ -1333,13 +1444,10 @@ const webApp = (app, ReactApp, config) => {
1333
1444
  // Dynamic doesn't need sagas
1334
1445
  // or styles, or any split component bundles
1335
1446
  // nor are we streaming responses
1336
- const isDynamicHints = `<script ${attributes}>window.versionStatus = "${versionStatus}"; window.isDynamic = true;</script>`;
1447
+ const isDynamicHints = `<script ${attributes}>window.isDynamic = true; ${subsitePathScript}</script>`;
1337
1448
  const jsx = ssrJsxProducer(ReactApp, {
1338
1449
  providers: jsxProviderProps,
1339
- props: jsxReactAppProps,
1340
- ssrAssets: {
1341
- serializedState: isDynamicHints
1342
- }
1450
+ props: jsxReactAppProps
1343
1451
  });
1344
1452
  server$1.renderToString(jsx);
1345
1453
 
@@ -1398,40 +1506,16 @@ const webApp = (app, ReactApp, config) => {
1398
1506
  return true;
1399
1507
  }
1400
1508
  if (!disableSsrRedux) {
1401
- // window.versionStatus is not strictly required here and is added to support cases
1402
- // where a consumer may not be using the contensisVersionStatus in redux and calling
1403
- // the `getClientSideVersionStatus()` method directly
1404
- serialisedReduxData = `<script ${attributes}>window.versionStatus = "${versionStatus}"; window.REDUX_DATA = ${serialisedReduxData}</script>`;
1509
+ serialisedReduxData = `<script ${attributes}>${subsitePathScript} window.__USE_HYDRATE__ = true; window.REDUX_DATA = ${serialisedReduxData}</script>`;
1405
1510
  }
1406
1511
  }
1407
1512
 
1408
1513
  // Responses
1409
-
1410
- const helmet = reactHelmet.Helmet.renderStatic();
1411
- reactHelmet.Helmet.rewind();
1412
- const htmlAttributes = helmet.htmlAttributes.toString();
1413
- let title = helmet.title.toString();
1414
- const metadata = helmet.meta.toString().concat(helmet.base.toString()).concat(helmet.link.toString()).concat(helmet.script.toString()).concat(helmet.noscript.toString());
1415
1514
  addStandardHeaders(reduxState, response, packagejson, {
1416
1515
  allowedGroups,
1417
1516
  globalGroups
1418
1517
  });
1419
-
1420
- // After running rootSaga there should be an additional react-loadable
1421
- // code-split bundles for any page components as well as core app bundles
1422
- const bundleTags = getBundleTags(loadableExtractor, scripts, staticRoutePath);
1423
1518
  const sheet = new styled.ServerStyleSheet();
1424
- // Produce the ssr jsx one time so we can get any style tags to pass back in
1425
- ssrJsxProducer(ReactApp, {
1426
- providers: {
1427
- ...jsxProviderProps,
1428
- styledComponents: {
1429
- sheet
1430
- }
1431
- },
1432
- props: jsxReactAppProps
1433
- });
1434
- let styleTags = sheet.getStyleTags();
1435
1519
  const styledJsx = ssrJsxProducer(ReactApp, {
1436
1520
  providers: {
1437
1521
  ...jsxProviderProps,
@@ -1439,14 +1523,31 @@ const webApp = (app, ReactApp, config) => {
1439
1523
  sheet
1440
1524
  }
1441
1525
  },
1442
- props: jsxReactAppProps,
1443
- ssrAssets: {
1444
- bundleTags,
1445
- htmlAttributes,
1446
- metadata,
1447
- title
1448
- }
1526
+ props: jsxReactAppProps
1449
1527
  });
1528
+
1529
+ // We have to call renderToString() in order for all components to have
1530
+ // had chance to set the helmet metadata
1531
+ const html = server$1.renderToString(styledJsx);
1532
+ // Helmet.renderStatic() has to be called synchronously immediately after calling renderToString()
1533
+ // as it is not thread-safe (or specifically scoped to only this request)
1534
+ // TODO: deprecate `react-helmet`
1535
+ const helmet = reactHelmet.Helmet.renderStatic();
1536
+
1537
+ // helmetContext is populated synchronously by HelmetProvider during renderToString()
1538
+ // It is scoped per-request via the helmetContext object, making this thread-safe
1539
+ // under concurrent SSR requests (unlike the previous Helmet.renderStatic() global singleton)
1540
+ const {
1541
+ helmet: helmetAsync
1542
+ } = helmetContext;
1543
+
1544
+ // Because we have had to call renderToString() here to reliably gather all helmet metadata
1545
+ // We could potentially call sheet.getStyleTags() here too and avoid piping a react-rendered
1546
+ // stream to a second stream to inject styled-components CSS
1547
+
1548
+ const htmlAttributes = helmetAsync.htmlAttributes.toString() || helmet.htmlAttributes.toString();
1549
+ let title = helmet.title.toString().includes('><') ? helmetAsync.title.toString() : helmet.title.toString();
1550
+ const metadata = helmetAsync.meta.toString().concat(helmetAsync.base.toString()).concat(helmetAsync.priority.toString()).concat(helmetAsync.link.toString()).concat(helmetAsync.script.toString()).concat(helmetAsync.noscript.toString()).concat(helmet.meta.toString()).concat(helmet.base.toString()).concat(helmet.link.toString()).concat(helmet.script.toString()).concat(helmet.noscript.toString());
1450
1551
  try {
1451
1552
  /**
1452
1553
  * Loads all page assets into the provided templateHTML
@@ -1457,7 +1558,8 @@ const webApp = (app, ReactApp, config) => {
1457
1558
  * we render the page as STATIC or render nothing
1458
1559
  * if the context has requested a redirect
1459
1560
  * */
1460
- const getContextHtml = renderedJsx => {
1561
+ const getContextHtml = (isFinal = false, styleTags, renderedJsxMarkup) => {
1562
+ var _loadableExtractor$mo;
1461
1563
  if (context.url) {
1462
1564
  response.redirect(context.statusCode || 302, context.url);
1463
1565
  return '';
@@ -1471,13 +1573,19 @@ const webApp = (app, ReactApp, config) => {
1471
1573
 
1472
1574
  // Set response.status from React StaticRouter
1473
1575
  if (typeof context.statusCode === 'number') response.status(context.statusCode);
1576
+ const bundleTags = isFinal ? getBundleTags(loadableExtractor, scripts, staticRoutePath) : '';
1577
+
1578
+ // Getting style tags generated by "CSS Modules" because they will be
1579
+ // available to loadable stats if we have built parts of the app with CSS
1580
+ // plugins that are not within styled-components
1581
+ const styles = loadableExtractor === null || loadableExtractor === void 0 || (_loadableExtractor$mo = loadableExtractor.modern) === null || _loadableExtractor$mo === void 0 ? void 0 : _loadableExtractor$mo.getStyleTags();
1474
1582
  const html = replaceHtml({
1475
1583
  bundleTags,
1476
- html: renderedJsx,
1584
+ html: renderedJsxMarkup,
1477
1585
  htmlAttributes,
1478
1586
  metadata,
1479
1587
  state: serialisedReduxData,
1480
- styleTags,
1588
+ styleTags: `${styleTags || ''}${styles || ''}`,
1481
1589
  title,
1482
1590
  templateHTML,
1483
1591
  templateHTMLFragment,
@@ -1486,12 +1594,14 @@ const webApp = (app, ReactApp, config) => {
1486
1594
  return html;
1487
1595
  };
1488
1596
  if (isRenderingJsxToString) {
1489
- const html = server$1.renderToString(styledJsx);
1490
- styleTags = sheet.getStyleTags();
1491
- const responseHTML = getContextHtml(html);
1597
+ // We have already (begrudgingly) rendered the JSX to a string above
1598
+ // so we can get all of the Helmet metadata out from any rendered component
1599
+ // const html = renderToString(styledJsx);
1600
+ const styleTags = sheet.getStyleTags();
1601
+ const responseHTML = getContextHtml(true, styleTags, html);
1492
1602
  responseHandler(request, response, responseHTML);
1493
1603
  } else {
1494
- renderStream(getContextHtml, styledJsx, response, styledComponentsStream(sheet));
1604
+ renderStream(getContextHtml, styledJsx, request, response, styledComponentsStream(sheet));
1495
1605
  }
1496
1606
  } catch (err) {
1497
1607
  console.info(err.message);
@@ -1554,6 +1664,7 @@ var internalServer = {
1554
1664
  };
1555
1665
 
1556
1666
  exports.ReactApp = App.AppRoot;
1667
+ exports.DO_NOT_COMMIT_subsiteDebugMiddleware = subsiteDebugMiddleware;
1557
1668
  exports.default = internalServer;
1558
1669
  exports.linkDepthApi = makeLinkDepthApi;
1559
1670
  //# sourceMappingURL=contensis-react-base.js.map