@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 +7 -0
- package/bin-dev-scripts/standard-version-release.sh +1 -1
- package/package.json +2 -1
- package/server/routes.js +254 -237
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
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quintype/framework",
|
|
3
|
-
"version": "7.
|
|
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(
|
|
10
|
-
const { generateServiceWorker } = require(
|
|
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(
|
|
18
|
-
|
|
19
|
-
const { oneSignalImport } = require(
|
|
20
|
-
const { customRouteHandler } = require(
|
|
21
|
-
const { handleManifest, handleAssetLink } = require(
|
|
22
|
-
const { redirectStory } = require(
|
|
23
|
-
const { simpleJsonHandler } = require(
|
|
24
|
-
const { makePickComponentSync } = require(
|
|
25
|
-
const { registerFCMTopic } = require(
|
|
26
|
-
const { triggerWebengageNotifications } = require(
|
|
27
|
-
const rp = require(
|
|
28
|
-
const bodyParser = require(
|
|
29
|
-
const get = require(
|
|
30
|
-
const { URL } = require(
|
|
31
|
-
const prerender = require(
|
|
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(
|
|
55
|
-
getClient = require(
|
|
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(
|
|
61
|
-
const apiProxy = require(
|
|
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(
|
|
64
|
-
})
|
|
63
|
+
ssl: host.startsWith('https') ? { servername: host.replace(/^https:\/\//, '') } : undefined
|
|
64
|
+
})
|
|
65
65
|
|
|
66
|
-
apiProxy.on(
|
|
67
|
-
proxyReq.setHeader(
|
|
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, [
|
|
71
|
-
const _maxAge = get(config, [
|
|
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(
|
|
75
|
-
const pathName = get(req, [
|
|
76
|
-
const checkForExcludeRoutes = excludeRoutes.some(
|
|
77
|
-
const matchFn = match(path, { decode: decodeURIComponent })
|
|
78
|
-
return matchFn(pathName)
|
|
79
|
-
})
|
|
80
|
-
const getCacheControl = get(proxyRes, [
|
|
81
|
-
if (!checkForExcludeRoutes && getCacheControl.includes(
|
|
82
|
-
proxyRes.headers[
|
|
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(
|
|
87
|
-
const pathName = get(req, [
|
|
88
|
-
const checkForExcludeRoutes = excludeRoutes.some(
|
|
89
|
-
const matchFn = match(path, { decode: decodeURIComponent })
|
|
90
|
-
return matchFn(pathName)
|
|
91
|
-
})
|
|
92
|
-
const getCacheControl = get(proxyRes, [
|
|
93
|
-
if (!checkForExcludeRoutes && getCacheControl.includes(
|
|
94
|
-
proxyRes.headers[
|
|
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(
|
|
100
|
+
app.get('/ping', (req, res) => {
|
|
101
101
|
getClient(req.hostname)
|
|
102
102
|
.getConfig()
|
|
103
|
-
.then(() => res.send(
|
|
104
|
-
.catch(() => res.status(503).send({ error: { message:
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
]
|
|
116
|
-
|
|
117
|
-
app.all(
|
|
118
|
-
app.all(
|
|
119
|
-
app.all(
|
|
120
|
-
app.all(
|
|
121
|
-
app.all(
|
|
122
|
-
app.all(
|
|
123
|
-
app.all(
|
|
124
|
-
app.all(
|
|
125
|
-
app.all(
|
|
126
|
-
app.all(
|
|
127
|
-
app.all(
|
|
128
|
-
app.all(
|
|
129
|
-
app.all(
|
|
130
|
-
app.all(
|
|
131
|
-
app.all(
|
|
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(
|
|
134
|
-
app.all(
|
|
133
|
+
app.all('/news_sitemap/today.xml', sketchesProxy)
|
|
134
|
+
app.all('/news_sitemap/yesterday.xml', sketchesProxy)
|
|
135
135
|
} else {
|
|
136
|
-
app.all(
|
|
136
|
+
app.all('/news_sitemap.xml', sketchesProxy)
|
|
137
137
|
}
|
|
138
138
|
if (forwardAmp) {
|
|
139
|
-
app.get(
|
|
139
|
+
app.get('/amp/*', sketchesProxy)
|
|
140
140
|
}
|
|
141
141
|
if (forwardFavicon) {
|
|
142
|
-
app.get(
|
|
142
|
+
app.get('/favicon.ico', sketchesProxy)
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
extraRoutes.forEach(
|
|
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 ===
|
|
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(
|
|
176
|
-
configWrapper =
|
|
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(
|
|
185
|
-
.then(
|
|
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[
|
|
226
|
-
const domain = (config.domains || []).find(
|
|
227
|
-
|
|
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[
|
|
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(
|
|
264
|
-
publisherConfig = require(
|
|
265
|
-
logError = require(
|
|
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 =
|
|
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 =
|
|
338
|
-
serviceWorkerPaths = [
|
|
339
|
-
maxConfigVersion =
|
|
340
|
-
configWrapper =
|
|
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(
|
|
344
|
-
assetHelper = require(
|
|
345
|
-
getClient = require(
|
|
343
|
+
logError = require('./logger').error,
|
|
344
|
+
assetHelper = require('./asset-helper'),
|
|
345
|
+
getClient = require('./api-client').getClient,
|
|
346
346
|
renderServiceWorker = renderServiceWorkerFn,
|
|
347
|
-
publisherConfig = require(
|
|
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, [
|
|
364
|
+
const _sMaxAge = parseInt(get(publisherConfig, ['publisher', 'isomorphicRoutesSmaxage'], sMaxAge))
|
|
365
365
|
|
|
366
|
-
const _maxAge = parseInt(get(publisherConfig, [
|
|
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(
|
|
378
|
-
prerender.set(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
472
|
+
app.post('/register-fcm-topic', bodyParser.json(), withConfig(registerFCMTopic, { publisherConfig, fcmServerKey }))
|
|
456
473
|
|
|
457
474
|
if (webengageConfig.enableWebengage) {
|
|
458
475
|
app.post(
|
|
459
|
-
|
|
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(
|
|
483
|
+
app.get('/manifest.json', withConfig(handleManifest, { manifestFn, logError }))
|
|
467
484
|
}
|
|
468
485
|
|
|
469
486
|
if (mobileApiEnabled) {
|
|
470
487
|
app.get(
|
|
471
|
-
|
|
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(
|
|
508
|
+
app.get('/.well-known/assetlinks.json', withConfig(handleAssetLink, { assetLinkFn, logError }))
|
|
492
509
|
}
|
|
493
510
|
|
|
494
511
|
if (templateOptions) {
|
|
495
512
|
app.get(
|
|
496
|
-
|
|
513
|
+
'/template-options.json',
|
|
497
514
|
withConfig(simpleJsonHandler, {
|
|
498
|
-
jsonData: toFunction(templateOptions,
|
|
515
|
+
jsonData: toFunction(templateOptions, './impl/template-options')
|
|
499
516
|
})
|
|
500
|
-
)
|
|
517
|
+
)
|
|
501
518
|
}
|
|
502
519
|
|
|
503
|
-
staticRoutes.forEach(
|
|
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(
|
|
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(
|
|
611
|
-
const { cacheControl =
|
|
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 ===
|
|
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(
|
|
631
|
-
res.setHeader(
|
|
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 ===
|
|
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 !==
|
|
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(
|
|
695
|
+
const { ampStoryPageHandler, storyPageInfiniteScrollHandler } = require('./amp/handlers')
|
|
679
696
|
|
|
680
|
-
getWithConfig(app,
|
|
681
|
-
getWithConfig(app,
|
|
682
|
-
getWithConfig(app,
|
|
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
|
+
}
|