@quintype/framework 7.32.0 → 7.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [7.33.0](https://github.com/quintype/quintype-node-framework/compare/v7.32.0...v7.33.0) (2024-12-10)
6
+
7
+
8
+ ### Features
9
+
10
+ * **CORS:** whitelist quintype.com ([#451](https://github.com/quintype/quintype-node-framework/issues/451)) ([94eafa5](https://github.com/quintype/quintype-node-framework/commit/94eafa5e3180dfe56dc0b74ac7c92b12615937af))
11
+
5
12
  ## [7.32.0](https://github.com/quintype/quintype-node-framework/compare/v7.31.0...v7.32.0) (2024-12-10)
6
13
 
7
14
 
@@ -1,6 +1,6 @@
1
1
  #!/bin/bash -e
2
2
 
3
- # npm install --legacy-peer-deps
3
+ npm install --legacy-peer-deps
4
4
  git diff --quiet
5
5
  npm run docs
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quintype/framework",
3
- "version": "7.32.0",
3
+ "version": "7.33.0",
4
4
  "description": "Libraries to help build Quintype Node.js apps",
5
5
  "main": "index.js",
6
6
  "engines": {
@@ -8,6 +8,7 @@
8
8
  "npm": "^8.5.0"
9
9
  },
10
10
  "scripts": {
11
+ "prepublishOnly": "npm test && ./bin-dev-scripts/standard-version-release.sh",
11
12
  "test": "NODE_ENV=test npx mocha --recursive --require ./test/babel",
12
13
  "watch-test": "NODE_ENV=test npx mocha --recursive --watch --require ./test/babel",
13
14
  "coverage": "nyc --all npm test",
package/server/routes.js CHANGED
@@ -6,29 +6,29 @@
6
6
  * @category Server
7
7
  * @module routes
8
8
  */
9
- const { match } = require("path-to-regexp");
10
- const { generateServiceWorker } = require("./handlers/generate-service-worker");
9
+ const { match } = require('path-to-regexp')
10
+ const { generateServiceWorker } = require('./handlers/generate-service-worker')
11
11
  const {
12
12
  handleIsomorphicShell,
13
13
  handleIsomorphicDataLoad,
14
14
  handleIsomorphicRoute,
15
15
  handleStaticRoute,
16
- notFoundHandler,
17
- } = require("./handlers/isomorphic-handler");
18
-
19
- const { oneSignalImport } = require("./handlers/one-signal");
20
- const { customRouteHandler } = require("./handlers/custom-route-handler");
21
- const { handleManifest, handleAssetLink } = require("./handlers/json-manifest-handlers");
22
- const { redirectStory } = require("./handlers/story-redirect");
23
- const { simpleJsonHandler } = require("./handlers/simple-json-handler");
24
- const { makePickComponentSync } = require("../isomorphic/impl/make-pick-component-sync");
25
- const { registerFCMTopic } = require("./handlers/fcm-registration-handler");
26
- const { triggerWebengageNotifications } = require("./handlers/webengage-notifications");
27
- const rp = require("request-promise");
28
- const bodyParser = require("body-parser");
29
- const get = require("lodash/get");
30
- const { URL } = require("url");
31
- const prerender = require("@quintype/prerender-node");
16
+ notFoundHandler
17
+ } = require('./handlers/isomorphic-handler')
18
+
19
+ const { oneSignalImport } = require('./handlers/one-signal')
20
+ const { customRouteHandler } = require('./handlers/custom-route-handler')
21
+ const { handleManifest, handleAssetLink } = require('./handlers/json-manifest-handlers')
22
+ const { redirectStory } = require('./handlers/story-redirect')
23
+ const { simpleJsonHandler } = require('./handlers/simple-json-handler')
24
+ const { makePickComponentSync } = require('../isomorphic/impl/make-pick-component-sync')
25
+ const { registerFCMTopic } = require('./handlers/fcm-registration-handler')
26
+ const { triggerWebengageNotifications } = require('./handlers/webengage-notifications')
27
+ const rp = require('request-promise')
28
+ const bodyParser = require('body-parser')
29
+ const get = require('lodash/get')
30
+ const { URL } = require('url')
31
+ const prerender = require('@quintype/prerender-node')
32
32
 
33
33
  /**
34
34
  * *upstreamQuintypeRoutes* connects various routes directly to the upstream API server.
@@ -43,7 +43,7 @@ const prerender = require("@quintype/prerender-node");
43
43
  * @param {boolean} opts.forwardFavicon Forward favicon requests to the CMS (default false)
44
44
  * @param {boolean} opts.isSitemapUrlEnabled To enable /news_sitemap/today and /news_sitemap/yesterday sitemap news url (default /news_sitemap.xml)
45
45
  */
46
- exports.upstreamQuintypeRoutes = function upstreamQuintypeRoutes(
46
+ exports.upstreamQuintypeRoutes = function upstreamQuintypeRoutes (
47
47
  app,
48
48
  {
49
49
  forwardAmp = false,
@@ -51,138 +51,138 @@ exports.upstreamQuintypeRoutes = function upstreamQuintypeRoutes(
51
51
  extraRoutes = [],
52
52
  sMaxAge,
53
53
  maxAge,
54
- config = require("./publisher-config"),
55
- getClient = require("./api-client").getClient,
56
- isSitemapUrlEnabled = false,
54
+ config = require('./publisher-config'),
55
+ getClient = require('./api-client').getClient,
56
+ isSitemapUrlEnabled = false
57
57
  } = {}
58
58
  ) {
59
- const host = config.sketches_host;
60
- const get = require("lodash/get");
61
- const apiProxy = require("http-proxy").createProxyServer({
59
+ const host = config.sketches_host
60
+ const get = require('lodash/get')
61
+ const apiProxy = require('http-proxy').createProxyServer({
62
62
  target: host,
63
- ssl: host.startsWith("https") ? { servername: host.replace(/^https:\/\//, "") } : undefined,
64
- });
63
+ ssl: host.startsWith('https') ? { servername: host.replace(/^https:\/\//, '') } : undefined
64
+ })
65
65
 
66
- apiProxy.on("proxyReq", (proxyReq, req, res, options) => {
67
- proxyReq.setHeader("Host", getClient(req.hostname).getHostname());
68
- });
66
+ apiProxy.on('proxyReq', (proxyReq, req, res, options) => {
67
+ proxyReq.setHeader('Host', getClient(req.hostname).getHostname())
68
+ })
69
69
 
70
- const _sMaxAge = get(config, ["publisher", "upstreamRoutesSmaxage"], sMaxAge);
71
- const _maxAge = get(config, ["publisher", "upstreamRoutesMaxage"], maxAge);
70
+ const _sMaxAge = get(config, ['publisher', 'upstreamRoutesSmaxage'], sMaxAge)
71
+ const _maxAge = get(config, ['publisher', 'upstreamRoutesMaxage'], maxAge)
72
72
 
73
73
  parseInt(_sMaxAge) > 0 &&
74
- apiProxy.on("proxyRes", function (proxyRes, req) {
75
- const pathName = get(req, ["originalUrl"], "").split("?")[0];
76
- const checkForExcludeRoutes = excludeRoutes.some((path) => {
77
- const matchFn = match(path, { decode: decodeURIComponent });
78
- return matchFn(pathName);
79
- });
80
- const getCacheControl = get(proxyRes, ["headers", "cache-control"], "");
81
- if (!checkForExcludeRoutes && getCacheControl.includes("public")) {
82
- proxyRes.headers["cache-control"] = getCacheControl.replace(/s-maxage=\d*/g, `s-maxage=${_sMaxAge}`);
74
+ apiProxy.on('proxyRes', function (proxyRes, req) {
75
+ const pathName = get(req, ['originalUrl'], '').split('?')[0]
76
+ const checkForExcludeRoutes = excludeRoutes.some(path => {
77
+ const matchFn = match(path, { decode: decodeURIComponent })
78
+ return matchFn(pathName)
79
+ })
80
+ const getCacheControl = get(proxyRes, ['headers', 'cache-control'], '')
81
+ if (!checkForExcludeRoutes && getCacheControl.includes('public')) {
82
+ proxyRes.headers['cache-control'] = getCacheControl.replace(/s-maxage=\d*/g, `s-maxage=${_sMaxAge}`)
83
83
  }
84
- });
84
+ })
85
85
  parseInt(_maxAge) > 0 &&
86
- apiProxy.on("proxyRes", function (proxyRes, req) {
87
- const pathName = get(req, ["originalUrl"], "").split("?")[0];
88
- const checkForExcludeRoutes = excludeRoutes.some((path) => {
89
- const matchFn = match(path, { decode: decodeURIComponent });
90
- return matchFn(pathName);
91
- });
92
- const getCacheControl = get(proxyRes, ["headers", "cache-control"], "");
93
- if (!checkForExcludeRoutes && getCacheControl.includes("public")) {
94
- proxyRes.headers["cache-control"] = getCacheControl.replace(/max-age=\d*/g, `max-age=${_maxAge}`);
86
+ apiProxy.on('proxyRes', function (proxyRes, req) {
87
+ const pathName = get(req, ['originalUrl'], '').split('?')[0]
88
+ const checkForExcludeRoutes = excludeRoutes.some(path => {
89
+ const matchFn = match(path, { decode: decodeURIComponent })
90
+ return matchFn(pathName)
91
+ })
92
+ const getCacheControl = get(proxyRes, ['headers', 'cache-control'], '')
93
+ if (!checkForExcludeRoutes && getCacheControl.includes('public')) {
94
+ proxyRes.headers['cache-control'] = getCacheControl.replace(/max-age=\d*/g, `max-age=${_maxAge}`)
95
95
  }
96
- });
96
+ })
97
97
 
98
- const sketchesProxy = (req, res) => apiProxy.web(req, res);
98
+ const sketchesProxy = (req, res) => apiProxy.web(req, res)
99
99
 
100
- app.get("/ping", (req, res) => {
100
+ app.get('/ping', (req, res) => {
101
101
  getClient(req.hostname)
102
102
  .getConfig()
103
- .then(() => res.send("pong"))
104
- .catch(() => res.status(503).send({ error: { message: "Config not loaded" } }));
105
- });
103
+ .then(() => res.send('pong'))
104
+ .catch(() => res.status(503).send({ error: { message: 'Config not loaded' } }))
105
+ })
106
106
 
107
107
  // Mention the routes which don't want to override the s-maxage value and max-age value
108
108
  const excludeRoutes = [
109
- "/qlitics.js",
110
- "/api/v1/breaking-news",
111
- "/stories.rss",
112
- "/api/v1/collections/:slug.rss",
113
- "/api/v1/advanced-search",
114
- "/api/instant-articles.rss",
115
- ];
116
-
117
- app.all("/api/*", sketchesProxy);
118
- app.all("*/api/*", sketchesProxy);
119
- app.all("/login", sketchesProxy);
120
- app.all("/qlitics.js", sketchesProxy);
121
- app.all("/auth.form", sketchesProxy);
122
- app.all("/auth.callback", sketchesProxy);
123
- app.all("/auth", sketchesProxy);
124
- app.all("/admin/*", sketchesProxy);
125
- app.all("/sitemap.xml", sketchesProxy);
126
- app.all("/sitemap/*", sketchesProxy);
127
- app.all("/feed", sketchesProxy);
128
- app.all("/rss-feed", sketchesProxy);
129
- app.all("/stories.rss", sketchesProxy);
130
- app.all("/sso-login", sketchesProxy);
131
- app.all("/sso-signup", sketchesProxy);
109
+ '/qlitics.js',
110
+ '/api/v1/breaking-news',
111
+ '/stories.rss',
112
+ '/api/v1/collections/:slug.rss',
113
+ '/api/v1/advanced-search',
114
+ '/api/instant-articles.rss'
115
+ ]
116
+
117
+ app.all('/api/*', sketchesProxy)
118
+ app.all('*/api/*', sketchesProxy)
119
+ app.all('/login', sketchesProxy)
120
+ app.all('/qlitics.js', sketchesProxy)
121
+ app.all('/auth.form', sketchesProxy)
122
+ app.all('/auth.callback', sketchesProxy)
123
+ app.all('/auth', sketchesProxy)
124
+ app.all('/admin/*', sketchesProxy)
125
+ app.all('/sitemap.xml', sketchesProxy)
126
+ app.all('/sitemap/*', sketchesProxy)
127
+ app.all('/feed', sketchesProxy)
128
+ app.all('/rss-feed', sketchesProxy)
129
+ app.all('/stories.rss', sketchesProxy)
130
+ app.all('/sso-login', sketchesProxy)
131
+ app.all('/sso-signup', sketchesProxy)
132
132
  if (isSitemapUrlEnabled) {
133
- app.all("/news_sitemap/today.xml", sketchesProxy);
134
- app.all("/news_sitemap/yesterday.xml", sketchesProxy);
133
+ app.all('/news_sitemap/today.xml', sketchesProxy)
134
+ app.all('/news_sitemap/yesterday.xml', sketchesProxy)
135
135
  } else {
136
- app.all("/news_sitemap.xml", sketchesProxy);
136
+ app.all('/news_sitemap.xml', sketchesProxy)
137
137
  }
138
138
  if (forwardAmp) {
139
- app.get("/amp/*", sketchesProxy);
139
+ app.get('/amp/*', sketchesProxy)
140
140
  }
141
141
  if (forwardFavicon) {
142
- app.get("/favicon.ico", sketchesProxy);
142
+ app.get('/favicon.ico', sketchesProxy)
143
143
  }
144
144
 
145
- extraRoutes.forEach((route) => app.all(route, sketchesProxy));
146
- };
145
+ extraRoutes.forEach(route => app.all(route, sketchesProxy))
146
+ }
147
147
 
148
148
  // istanbul ignore next
149
- function renderServiceWorkerFn(res, layout, params, callback) {
150
- return res.render(layout, params, callback);
149
+ function renderServiceWorkerFn (res, layout, params, callback) {
150
+ return res.render(layout, params, callback)
151
151
  }
152
152
 
153
153
  // istanbul ignore next
154
- function toFunction(value, toRequire) {
154
+ function toFunction (value, toRequire) {
155
155
  if (value === true) {
156
- value = require(toRequire);
156
+ value = require(toRequire)
157
157
  }
158
158
 
159
- if (typeof value === "function") {
160
- return value;
159
+ if (typeof value === 'function') {
160
+ return value
161
161
  }
162
- return () => value;
162
+ return () => value
163
163
  }
164
164
 
165
- function getDomainSlug(publisherConfig, hostName) {
165
+ function getDomainSlug (publisherConfig, hostName) {
166
166
  if (!publisherConfig.domain_mapping) {
167
- return undefined;
167
+ return undefined
168
168
  }
169
- return publisherConfig.domain_mapping[hostName] || null;
169
+ return publisherConfig.domain_mapping[hostName] || null
170
170
  }
171
171
 
172
- function withConfigPartial(
172
+ function withConfigPartial (
173
173
  getClient,
174
174
  logError,
175
- publisherConfig = require("./publisher-config"),
176
- configWrapper = (config) => config
175
+ publisherConfig = require('./publisher-config'),
176
+ configWrapper = config => config
177
177
  ) {
178
- return function withConfig(f, staticParams) {
178
+ return function withConfig (f, staticParams) {
179
179
  return function (req, res, next) {
180
- const domainSlug = getDomainSlug(publisherConfig, req.hostname);
181
- const client = getClient(req.hostname);
180
+ const domainSlug = getDomainSlug(publisherConfig, req.hostname)
181
+ const client = getClient(req.hostname)
182
182
  return client
183
183
  .getConfig()
184
- .then((config) => configWrapper(config, domainSlug, { req }))
185
- .then((config) =>
184
+ .then(config => configWrapper(config, domainSlug, { req }))
185
+ .then(config =>
186
186
  f(
187
187
  req,
188
188
  res,
@@ -190,52 +190,52 @@ function withConfigPartial(
190
190
  Object.assign({}, staticParams, {
191
191
  config,
192
192
  client,
193
- domainSlug,
193
+ domainSlug
194
194
  })
195
195
  )
196
196
  )
197
- .catch(logError);
198
- };
199
- };
197
+ .catch(logError)
198
+ }
199
+ }
200
200
  }
201
201
 
202
- exports.withError = function withError(handler, logError) {
202
+ exports.withError = function withError (handler, logError) {
203
203
  return async (req, res, next, opts) => {
204
204
  try {
205
- await handler(req, res, next, opts);
205
+ await handler(req, res, next, opts)
206
206
  } catch (e) {
207
- logError(e);
208
- res.status(500);
209
- res.end();
207
+ logError(e)
208
+ res.status(500)
209
+ res.end()
210
210
  }
211
- };
212
- };
211
+ }
212
+ }
213
213
 
214
- function convertToDomain(path) {
214
+ function convertToDomain (path) {
215
215
  if (!path) {
216
- return path;
216
+ return path
217
217
  }
218
- return new URL(path).origin;
218
+ return new URL(path).origin
219
219
  }
220
220
 
221
- function wrapLoadDataWithMultiDomain(publisherConfig, f, configPos) {
222
- return async function loadDataWrapped() {
223
- const { domainSlug } = arguments[arguments.length - 1];
224
- const config = arguments[configPos];
225
- const primaryHostUrl = convertToDomain(config["sketches-host"]);
226
- const domain = (config.domains || []).find((d) => d.slug === domainSlug) || {
227
- "host-url": primaryHostUrl,
228
- };
229
- const result = await f.apply(this, arguments);
221
+ function wrapLoadDataWithMultiDomain (publisherConfig, f, configPos) {
222
+ return async function loadDataWrapped () {
223
+ const { domainSlug } = arguments[arguments.length - 1]
224
+ const config = arguments[configPos]
225
+ const primaryHostUrl = convertToDomain(config['sketches-host'])
226
+ const domain = (config.domains || []).find(d => d.slug === domainSlug) || {
227
+ 'host-url': primaryHostUrl
228
+ }
229
+ const result = await f.apply(this, arguments)
230
230
  return Object.assign(
231
231
  {
232
232
  domainSlug,
233
- currentHostUrl: convertToDomain(domain["host-url"]),
234
- primaryHostUrl,
233
+ currentHostUrl: convertToDomain(domain['host-url']),
234
+ primaryHostUrl
235
235
  },
236
236
  result
237
- );
238
- };
237
+ )
238
+ }
239
239
  }
240
240
 
241
241
  /**
@@ -257,15 +257,15 @@ function wrapLoadDataWithMultiDomain(publisherConfig, f, configPos) {
257
257
  * @param {module:routes~Handler} handler The Handler to run
258
258
  * @param {Object} opts Options that will be passed to the handler. These options will be merged with a *config* and *client*
259
259
  */
260
- function getWithConfig(app, route, handler, opts = {}) {
261
- const configWrapper = opts.configWrapper;
260
+ function getWithConfig (app, route, handler, opts = {}) {
261
+ const configWrapper = opts.configWrapper
262
262
  const {
263
- getClient = require("./api-client").getClient,
264
- publisherConfig = require("./publisher-config"),
265
- logError = require("./logger").error,
266
- } = opts;
267
- const withConfig = withConfigPartial(getClient, logError, publisherConfig, configWrapper);
268
- app.get(route, withConfig(handler, opts));
263
+ getClient = require('./api-client').getClient,
264
+ publisherConfig = require('./publisher-config'),
265
+ logError = require('./logger').error
266
+ } = opts
267
+ const withConfig = withConfigPartial(getClient, logError, publisherConfig, configWrapper)
268
+ app.get(route, withConfig(handler, opts))
269
269
  }
270
270
 
271
271
  /**
@@ -309,7 +309,7 @@ function getWithConfig(app, route, handler, opts = {}) {
309
309
  * @param {boolean|function} enableExternalStories If set to true, then for every request an external story api call is made and renders the story-page if the story is found. (default: false)
310
310
  * @param {string|function} externalIdPattern This string specifies the external id pattern the in the url. Mention `EXTERNAL_ID` to specify the position of external id in the url. Ex: "/parent-section/child-section/EXTERNAL_ID"
311
311
  */
312
- exports.isomorphicRoutes = function isomorphicRoutes(
312
+ exports.isomorphicRoutes = function isomorphicRoutes (
313
313
  app,
314
314
  {
315
315
  generateRoutes,
@@ -320,7 +320,7 @@ exports.isomorphicRoutes = function isomorphicRoutes(
320
320
  seo,
321
321
  manifestFn,
322
322
  assetLinkFn,
323
- ampPageBasePath = "/amp/story",
323
+ ampPageBasePath = '/amp/story',
324
324
 
325
325
  oneSignalServiceWorkers = false,
326
326
  staticRoutes = [],
@@ -334,57 +334,74 @@ exports.isomorphicRoutes = function isomorphicRoutes(
334
334
  mobileConfigFields = {},
335
335
  templateOptions = false,
336
336
  lightPages = false,
337
- cdnProvider = "cloudflare",
338
- serviceWorkerPaths = ["/service-worker.js"],
339
- maxConfigVersion = (config) => get(config, ["theme-attributes", "cache-burst"], 0),
340
- configWrapper = (config) => config,
337
+ cdnProvider = 'cloudflare',
338
+ serviceWorkerPaths = ['/service-worker.js'],
339
+ maxConfigVersion = config => get(config, ['theme-attributes', 'cache-burst'], 0),
340
+ configWrapper = config => config,
341
341
 
342
342
  // The below are primarily for testing
343
- logError = require("./logger").error,
344
- assetHelper = require("./asset-helper"),
345
- getClient = require("./api-client").getClient,
343
+ logError = require('./logger').error,
344
+ assetHelper = require('./asset-helper'),
345
+ getClient = require('./api-client').getClient,
346
346
  renderServiceWorker = renderServiceWorkerFn,
347
- publisherConfig = require("./publisher-config"),
347
+ publisherConfig = require('./publisher-config'),
348
348
  redirectUrls = [],
349
- prerenderServiceUrl = "",
349
+ prerenderServiceUrl = '',
350
350
  redirectToLowercaseSlugs = false,
351
351
  shouldEncodeAmpUri,
352
352
  sMaxAge = 900,
353
353
  maxAge = 15,
354
- appLoadingPlaceholder = "",
355
- fcmServerKey = "",
354
+ appLoadingPlaceholder = '',
355
+ fcmServerKey = '',
356
356
  webengageConfig = {},
357
- externalIdPattern = "",
357
+ externalIdPattern = '',
358
358
  enableExternalStories = false,
359
- lazyLoadImageMargin,
359
+ lazyLoadImageMargin
360
360
  }
361
361
  ) {
362
- const withConfig = withConfigPartial(getClient, logError, publisherConfig, configWrapper);
362
+ const withConfig = withConfigPartial(getClient, logError, publisherConfig, configWrapper)
363
363
 
364
- const _sMaxAge = parseInt(get(publisherConfig, ["publisher", "isomorphicRoutesSmaxage"], sMaxAge));
364
+ const _sMaxAge = parseInt(get(publisherConfig, ['publisher', 'isomorphicRoutesSmaxage'], sMaxAge))
365
365
 
366
- const _maxAge = parseInt(get(publisherConfig, ["publisher", "isomorphicRoutesMaxage"], maxAge));
366
+ const _maxAge = parseInt(get(publisherConfig, ['publisher', 'isomorphicRoutesMaxage'], maxAge))
367
367
 
368
- pickComponent = makePickComponentSync(pickComponent);
369
- loadData = wrapLoadDataWithMultiDomain(publisherConfig, loadData, 2);
370
- loadErrorData = wrapLoadDataWithMultiDomain(publisherConfig, loadErrorData, 1);
368
+ pickComponent = makePickComponentSync(pickComponent)
369
+ loadData = wrapLoadDataWithMultiDomain(publisherConfig, loadData, 2)
370
+ loadErrorData = wrapLoadDataWithMultiDomain(publisherConfig, loadErrorData, 1)
371
371
 
372
372
  if (prerenderServiceUrl) {
373
373
  app.use((req, res, next) => {
374
374
  if (req.query.prerender) {
375
375
  try {
376
376
  // eslint-disable-next-line global-require
377
- prerender.set("protocol", "https");
378
- prerender.set("prerenderServiceUrl", prerenderServiceUrl)(req, res, next);
377
+ prerender.set('protocol', 'https')
378
+ prerender.set('prerenderServiceUrl', prerenderServiceUrl)(req, res, next)
379
379
  } catch (e) {
380
- logError(e);
380
+ logError(e)
381
381
  }
382
382
  } else {
383
- next();
383
+ next()
384
384
  }
385
- });
385
+ })
386
386
  }
387
387
 
388
+ app.use((req, res, next) => {
389
+ const origin = req.headers.origin
390
+ const allowedOriginRegex = /^https?:\/\/([a-zA-Z0-9-]+\.)*quintype\.com$/
391
+
392
+ if (allowedOriginRegex.test(origin)) {
393
+ res.setHeader('Access-Control-Allow-Origin', origin)
394
+ res.setHeader('Access-Control-Allow-Methods', 'GET')
395
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
396
+
397
+ if (req.method === 'OPTIONS') {
398
+ res.sendStatus(204)
399
+ return
400
+ }
401
+ }
402
+ next()
403
+ })
404
+
388
405
  if (serviceWorkerPaths.length > 0) {
389
406
  app.get(
390
407
  serviceWorkerPaths,
@@ -392,36 +409,36 @@ exports.isomorphicRoutes = function isomorphicRoutes(
392
409
  generateRoutes,
393
410
  assetHelper,
394
411
  renderServiceWorker,
395
- maxConfigVersion,
412
+ maxConfigVersion
396
413
  })
397
- );
414
+ )
398
415
  }
399
416
 
400
417
  if (oneSignalServiceWorkers) {
401
418
  app.get(
402
- "/OneSignalSDKWorker.js",
419
+ '/OneSignalSDKWorker.js',
403
420
  withConfig(generateServiceWorker, {
404
421
  generateRoutes,
405
422
  renderServiceWorker,
406
423
  assetHelper,
407
424
  appendFn: oneSignalImport,
408
- maxConfigVersion,
425
+ maxConfigVersion
409
426
  })
410
- );
427
+ )
411
428
  app.get(
412
- "/OneSignalSDKUpdaterWorker.js",
429
+ '/OneSignalSDKUpdaterWorker.js',
413
430
  withConfig(generateServiceWorker, {
414
431
  generateRoutes,
415
432
  renderServiceWorker,
416
433
  assetHelper,
417
434
  appendFn: oneSignalImport,
418
- maxConfigVersion,
435
+ maxConfigVersion
419
436
  })
420
- );
437
+ )
421
438
  }
422
439
 
423
440
  app.get(
424
- "/shell.html",
441
+ '/shell.html',
425
442
  withConfig(handleIsomorphicShell, {
426
443
  seo,
427
444
  renderLayout,
@@ -431,11 +448,11 @@ exports.isomorphicRoutes = function isomorphicRoutes(
431
448
  logError,
432
449
  preloadJs,
433
450
  maxConfigVersion,
434
- appLoadingPlaceholder,
451
+ appLoadingPlaceholder
435
452
  })
436
- );
453
+ )
437
454
  app.get(
438
- "/route-data.json",
455
+ '/route-data.json',
439
456
  withConfig(handleIsomorphicDataLoad, {
440
457
  generateRoutes,
441
458
  loadData,
@@ -448,27 +465,27 @@ exports.isomorphicRoutes = function isomorphicRoutes(
448
465
  redirectToLowercaseSlugs,
449
466
  sMaxAge: _sMaxAge,
450
467
  maxAge: _maxAge,
451
- networkOnly: true,
468
+ networkOnly: true
452
469
  })
453
- );
470
+ )
454
471
 
455
- app.post("/register-fcm-topic", bodyParser.json(), withConfig(registerFCMTopic, { publisherConfig, fcmServerKey }));
472
+ app.post('/register-fcm-topic', bodyParser.json(), withConfig(registerFCMTopic, { publisherConfig, fcmServerKey }))
456
473
 
457
474
  if (webengageConfig.enableWebengage) {
458
475
  app.post(
459
- "/integrations/webengage/trigger-notification",
476
+ '/integrations/webengage/trigger-notification',
460
477
  bodyParser.json(),
461
478
  withConfig(triggerWebengageNotifications, webengageConfig)
462
- );
479
+ )
463
480
  }
464
481
 
465
482
  if (manifestFn) {
466
- app.get("/manifest.json", withConfig(handleManifest, { manifestFn, logError }));
483
+ app.get('/manifest.json', withConfig(handleManifest, { manifestFn, logError }))
467
484
  }
468
485
 
469
486
  if (mobileApiEnabled) {
470
487
  app.get(
471
- "/mobile-data.json",
488
+ '/mobile-data.json',
472
489
  withConfig(handleIsomorphicDataLoad, {
473
490
  generateRoutes,
474
491
  loadData,
@@ -482,25 +499,25 @@ exports.isomorphicRoutes = function isomorphicRoutes(
482
499
  cdnProvider,
483
500
  redirectToLowercaseSlugs,
484
501
  sMaxAge: _sMaxAge,
485
- maxAge: _maxAge,
502
+ maxAge: _maxAge
486
503
  })
487
- );
504
+ )
488
505
  }
489
506
 
490
507
  if (assetLinkFn) {
491
- app.get("/.well-known/assetlinks.json", withConfig(handleAssetLink, { assetLinkFn, logError }));
508
+ app.get('/.well-known/assetlinks.json', withConfig(handleAssetLink, { assetLinkFn, logError }))
492
509
  }
493
510
 
494
511
  if (templateOptions) {
495
512
  app.get(
496
- "/template-options.json",
513
+ '/template-options.json',
497
514
  withConfig(simpleJsonHandler, {
498
- jsonData: toFunction(templateOptions, "./impl/template-options"),
515
+ jsonData: toFunction(templateOptions, './impl/template-options')
499
516
  })
500
- );
517
+ )
501
518
  }
502
519
 
503
- staticRoutes.forEach((route) => {
520
+ staticRoutes.forEach(route => {
504
521
  app.get(
505
522
  route.path,
506
523
  withConfig(
@@ -516,16 +533,16 @@ exports.isomorphicRoutes = function isomorphicRoutes(
516
533
  oneSignalServiceWorkers,
517
534
  publisherConfig,
518
535
  sMaxAge: _sMaxAge,
519
- maxAge: _maxAge,
536
+ maxAge: _maxAge
520
537
  },
521
538
  route
522
539
  )
523
540
  )
524
- );
525
- });
541
+ )
542
+ })
526
543
 
527
544
  app.get(
528
- "/*",
545
+ '/*',
529
546
  withConfig(handleIsomorphicRoute, {
530
547
  generateRoutes,
531
548
  loadData,
@@ -549,17 +566,17 @@ exports.isomorphicRoutes = function isomorphicRoutes(
549
566
  ampPageBasePath,
550
567
  externalIdPattern,
551
568
  enableExternalStories,
552
- lazyLoadImageMargin,
569
+ lazyLoadImageMargin
553
570
  })
554
- );
571
+ )
555
572
 
556
573
  if (redirectRootLevelStories) {
557
- app.get("/:storySlug", withConfig(redirectStory, { logError, cdnProvider, sMaxAge: _sMaxAge, maxAge: _maxAge }));
574
+ app.get('/:storySlug', withConfig(redirectStory, { logError, cdnProvider, sMaxAge: _sMaxAge, maxAge: _maxAge }))
558
575
  }
559
576
 
560
577
  if (handleCustomRoute) {
561
578
  app.get(
562
- "/*",
579
+ '/*',
563
580
  withConfig(customRouteHandler, {
564
581
  loadData,
565
582
  renderLayout,
@@ -567,27 +584,27 @@ exports.isomorphicRoutes = function isomorphicRoutes(
567
584
  seo,
568
585
  cdnProvider,
569
586
  sMaxAge: _sMaxAge,
570
- maxAge: _maxAge,
587
+ maxAge: _maxAge
571
588
  })
572
- );
589
+ )
573
590
  }
574
591
 
575
592
  if (handleNotFound) {
576
593
  app.get(
577
- "/*",
594
+ '/*',
578
595
  withConfig(notFoundHandler, {
579
596
  renderLayout,
580
597
  pickComponent,
581
598
  loadErrorData,
582
599
  logError,
583
600
  seo,
584
- assetHelper,
601
+ assetHelper
585
602
  })
586
- );
603
+ )
587
604
  }
588
- };
605
+ }
589
606
 
590
- exports.getWithConfig = getWithConfig;
607
+ exports.getWithConfig = getWithConfig
591
608
 
592
609
  /**
593
610
  * *proxyGetRequest* can be used to forward requests to another host, and cache the results on our CDN. This can be done as follows in `app/server/app.js`.
@@ -607,52 +624,52 @@ exports.getWithConfig = getWithConfig;
607
624
  * @param opts.cacheControl The cache control header to set on proxied requests (default: *"public,max-age=15,s-maxage=240,stale-while-revalidate=300,stale-if-error=3600"*)
608
625
  */
609
626
  exports.proxyGetRequest = function (app, route, handler, opts = {}) {
610
- const { logError = require("./logger").error } = opts;
611
- const { cacheControl = "public,max-age=15,s-maxage=240,stale-while-revalidate=300,stale-if-error=3600" } = opts;
627
+ const { logError = require('./logger').error } = opts
628
+ const { cacheControl = 'public,max-age=15,s-maxage=240,stale-while-revalidate=300,stale-if-error=3600' } = opts
612
629
 
613
- getWithConfig(app, route, proxyHandler, opts);
630
+ getWithConfig(app, route, proxyHandler, opts)
614
631
 
615
- async function proxyHandler(req, res, next, { config, client }) {
632
+ async function proxyHandler (req, res, next, { config, client }) {
616
633
  try {
617
- const result = await handler(req.params, { config, client });
618
- if (typeof result === "string" && result.startsWith("http")) {
619
- sendResult(await rp(result, { json: true }));
634
+ const result = await handler(req.params, { config, client })
635
+ if (typeof result === 'string' && result.startsWith('http')) {
636
+ sendResult(await rp(result, { json: true }))
620
637
  } else {
621
- sendResult(result);
638
+ sendResult(result)
622
639
  }
623
640
  } catch (e) {
624
- logError(e);
625
- sendResult(null);
641
+ logError(e)
642
+ sendResult(null)
626
643
  }
627
644
 
628
- function sendResult(result) {
645
+ function sendResult (result) {
629
646
  if (result) {
630
- res.setHeader("Cache-Control", cacheControl);
631
- res.setHeader("Vary", "Accept-Encoding");
632
- res.json(result);
647
+ res.setHeader('Cache-Control', cacheControl)
648
+ res.setHeader('Vary', 'Accept-Encoding')
649
+ res.json(result)
633
650
  } else {
634
- res.status(503);
635
- res.end();
651
+ res.status(503)
652
+ res.end()
636
653
  }
637
654
  }
638
655
  }
639
- };
656
+ }
640
657
 
641
658
  // This could also be done using express's mount point, but /ping stops working
642
659
  exports.mountQuintypeAt = function (app, mountAt) {
643
660
  app.use(function (req, res, next) {
644
- const mountPoint = typeof mountAt === "function" ? mountAt(req.hostname) : mountAt;
661
+ const mountPoint = typeof mountAt === 'function' ? mountAt(req.hostname) : mountAt
645
662
 
646
663
  if (mountPoint && req.url.startsWith(mountPoint)) {
647
- req.url = req.url.slice(mountPoint.length) || "/";
648
- next();
649
- } else if (mountPoint && req.url !== "/ping") {
650
- res.status(404).send(`Not Found: Quintype has been mounted at ${mountPoint}`);
664
+ req.url = req.url.slice(mountPoint.length) || '/'
665
+ next()
666
+ } else if (mountPoint && req.url !== '/ping') {
667
+ res.status(404).send(`Not Found: Quintype has been mounted at ${mountPoint}`)
651
668
  } else {
652
- next();
669
+ next()
653
670
  }
654
- });
655
- };
671
+ })
672
+ }
656
673
 
657
674
  /**
658
675
  * *ampRoutes* handles all the amp page routes using the *[@quintype/amp](https://developers.quintype.com/quintype-node-amp)* library
@@ -675,9 +692,9 @@ exports.mountQuintypeAt = function (app, mountAt) {
675
692
  *
676
693
  */
677
694
  exports.ampRoutes = (app, opts = {}) => {
678
- const { ampStoryPageHandler, storyPageInfiniteScrollHandler } = require("./amp/handlers");
695
+ const { ampStoryPageHandler, storyPageInfiniteScrollHandler } = require('./amp/handlers')
679
696
 
680
- getWithConfig(app, "/amp/api/v1/amp-infinite-scroll", storyPageInfiniteScrollHandler, opts);
681
- getWithConfig(app, "/ampstories/*", ampStoryPageHandler, { ...opts, isVisualStory: true });
682
- getWithConfig(app, "/*", ampStoryPageHandler, opts);
683
- };
697
+ getWithConfig(app, '/amp/api/v1/amp-infinite-scroll', storyPageInfiniteScrollHandler, opts)
698
+ getWithConfig(app, '/ampstories/*', ampStoryPageHandler, { ...opts, isVisualStory: true })
699
+ getWithConfig(app, '/*', ampStoryPageHandler, opts)
700
+ }