@quintype/framework 7.19.21 → 7.19.22-custom-maxage.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quintype/framework",
3
- "version": "7.19.21",
3
+ "version": "7.19.22-custom-maxage.0",
4
4
  "description": "Libraries to help build Quintype Node.js apps",
5
5
  "main": "index.js",
6
6
  "engines": {
@@ -6,6 +6,7 @@ exports.addCacheHeadersToResult = function addCacheHeadersToResult({
6
6
  cacheKeys,
7
7
  cdnProvider = "cloudflare",
8
8
  config,
9
+ maxAge = "15",
9
10
  sMaxAge = "900",
10
11
  networkOnly = false,
11
12
  }) {
@@ -42,7 +43,7 @@ exports.addCacheHeadersToResult = function addCacheHeadersToResult({
42
43
  } else {
43
44
  res.setHeader(
44
45
  "Cache-Control",
45
- `public,max-age=15,s-maxage=${sMaxAge},stale-while-revalidate=1000,stale-if-error=14400`
46
+ `public,max-age=${maxAge},s-maxage=${sMaxAge},stale-while-revalidate=1000,stale-if-error=14400`
46
47
  );
47
48
  cdnProviderVal === "akamai" &&
48
49
  res.setHeader("Edge-Control", `public,maxage=${sMaxAge},stale-while-revalidate=1000,stale-if-error=14400`);
@@ -19,6 +19,8 @@ function renderStaticPageContent(store, content) {
19
19
  }
20
20
 
21
21
  function writeStaticPageResponse(res, url, page, result, { config, renderLayout, seo }) {
22
+ const hideHeader = !page.metadata.header;
23
+ const hideFooter = !page.metadata.footer;
22
24
  const qt = {
23
25
  pageType: page.type,
24
26
  // remove content from data to avoid the script tag inside json breaking the page
@@ -37,6 +39,8 @@ function writeStaticPageResponse(res, url, page, result, { config, renderLayout,
37
39
  res.status(page["status-code"] || 200);
38
40
 
39
41
  return renderLayout(res, {
42
+ hideHeader,
43
+ hideFooter,
40
44
  title: page.title,
41
45
  metadata: page.metadata,
42
46
  content: renderStaticPageContent(store, page.content),
@@ -50,7 +54,7 @@ exports.customRouteHandler = function customRouteHandler(
50
54
  req,
51
55
  res,
52
56
  next,
53
- { config, client, loadData, loadErrorData, renderLayout, logError, seo, domainSlug, cdnProvider = null, sMaxAge }
57
+ { config, client, loadData, loadErrorData, renderLayout, logError, seo, domainSlug, cdnProvider = null, sMaxAge, maxAge }
54
58
  ) {
55
59
  const url = urlLib.parse(req.url, true);
56
60
  const path = req.params[0].endsWith("/") ? req.params[0].slice(0, -1) : req.params[0];
@@ -70,6 +74,7 @@ exports.customRouteHandler = function customRouteHandler(
70
74
  cdnProvider: cdnProvider,
71
75
  config: config,
72
76
  sMaxAge,
77
+ maxAge,
73
78
  });
74
79
 
75
80
  let destination = page["destination-path"] || "/";
@@ -89,6 +94,7 @@ exports.customRouteHandler = function customRouteHandler(
89
94
  cdnProvider: cdnProvider,
90
95
  config: config,
91
96
  sMaxAge,
97
+ maxAge,
92
98
  });
93
99
  addStaticPageMimeType({ res, page });
94
100
 
@@ -230,6 +230,7 @@ exports.handleIsomorphicDataLoad = function handleIsomorphicDataLoad(
230
230
  redirectToLowercaseSlugs,
231
231
  sMaxAge,
232
232
  networkOnly,
233
+ maxAge,
233
234
  }
234
235
  ) {
235
236
  const url = urlLib.parse(req.query.path || "/", true);
@@ -302,6 +303,7 @@ exports.handleIsomorphicDataLoad = function handleIsomorphicDataLoad(
302
303
  config: config,
303
304
  sMaxAge,
304
305
  networkOnly,
306
+ maxAge,
305
307
  });
306
308
  const seoInstance = getSeoInstance(seo, config, result.pageType);
307
309
  res.json(
@@ -423,6 +425,7 @@ exports.handleIsomorphicRoute = function handleIsomorphicRoute(
423
425
  shouldEncodeAmpUri,
424
426
  publisherConfig,
425
427
  sMaxAge,
428
+ maxAge,
426
429
  }
427
430
  ) {
428
431
  const url = urlLib.parse(req.url, true);
@@ -437,6 +440,7 @@ exports.handleIsomorphicRoute = function handleIsomorphicRoute(
437
440
  cdnProvider: cdnProvider,
438
441
  config: config,
439
442
  sMaxAge: result.pageType === "tag-page" ? 600 : sMaxAge,
443
+ maxAge: maxAge,
440
444
  });
441
445
  return res.redirect(301, result.data.location);
442
446
  }
@@ -463,6 +467,7 @@ exports.handleIsomorphicRoute = function handleIsomorphicRoute(
463
467
  cdnProvider: cdnProvider,
464
468
  config: config,
465
469
  sMaxAge: result.pageType === "tag-page" ? 600 : sMaxAge,
470
+ maxAge: maxAge,
466
471
  });
467
472
 
468
473
  if (preloadJs) {
@@ -537,6 +542,7 @@ exports.handleStaticRoute = function handleStaticRoute(
537
542
  oneSignalServiceWorkers,
538
543
  publisherConfig,
539
544
  sMaxAge,
545
+ maxAge,
540
546
  }
541
547
  ) {
542
548
  const url = urlLib.parse(path);
@@ -577,6 +583,7 @@ exports.handleStaticRoute = function handleStaticRoute(
577
583
  cdnProvider: cdnProvider,
578
584
  config: config,
579
585
  sMaxAge,
586
+ maxAge,
580
587
  });
581
588
 
582
589
  const oneSignalScript = oneSignalServiceWorkers ? getOneSignalScript({ config, publisherConfig }) : null;
@@ -6,7 +6,7 @@ exports.redirectStory = function redirectStory(
6
6
  req,
7
7
  res,
8
8
  next,
9
- { logError, config, client, cdnProvider = null, sMaxAge }
9
+ { logError, config, client, cdnProvider = null, sMaxAge, maxAge }
10
10
  ) {
11
11
  const storySlug = req.params.storySlug.toLowerCase() || "";
12
12
  return Story.getStoryBySlug(client, storySlug)
@@ -18,6 +18,7 @@ exports.redirectStory = function redirectStory(
18
18
  cdnProvider: cdnProvider,
19
19
  config: config,
20
20
  sMaxAge,
21
+ maxAge,
21
22
  });
22
23
  return res.redirect(301, `/${story.slug}`);
23
24
  } else {
package/server/routes.js CHANGED
@@ -39,6 +39,7 @@ const prerender = require("@quintype/prerender-node");
39
39
  * @param {Array<string>} opts.extraRoutes Additionally forward some routes upstream. This takes an array of express compatible routes, such as ["/foo/*"]
40
40
  * @param {boolean} opts.forwardAmp Forward amp story routes upstream (default false)
41
41
  * @param {number} opts.sMaxAge Support overriding of proxied response cache header `s-maxage` from Sketches. For Breaking News and if the cacheability is Private, it is not overwritten instead the cache control will be the same as how it's set in sketches. We can set `upstreamRoutesSmaxage: 900` under `publisher` in publisher.yml config file that comes from BlackKnight or pass sMaxAge as a param.
42
+ * @param {number} opts.maxAge Support overriding of proxied response cache header `maxage` from Sketches. For Breaking News and if the cacheability is Private, it is not overwritten instead the cache control will be the same as how it's set in sketches. We can set `upstreamRoutesMaxage: 15` under `publisher` in publisher.yml config file that comes from BlackKnight or pass maxAge as a param.
42
43
  * @param {boolean} opts.forwardFavicon Forward favicon requests to the CMS (default false)
43
44
  * @param {boolean} opts.isSitemapUrlEnabled To enable /news_sitemap/today and /news_sitemap/yesterday sitemap news url (default /news_sitemap.xml)
44
45
  */
@@ -49,7 +50,7 @@ exports.upstreamQuintypeRoutes = function upstreamQuintypeRoutes(
49
50
  forwardFavicon = false,
50
51
  extraRoutes = [],
51
52
  sMaxAge,
52
-
53
+ maxAge,
53
54
  config = require("./publisher-config"),
54
55
  getClient = require("./api-client").getClient,
55
56
  isSitemapUrlEnabled = false,
@@ -67,8 +68,22 @@ exports.upstreamQuintypeRoutes = function upstreamQuintypeRoutes(
67
68
  });
68
69
 
69
70
  const _sMaxAge = get(config, ["publisher", "upstreamRoutesSmaxage"], sMaxAge);
71
+ const _maxAge = get(config, ["publisher", "upstreamRoutesMaxage"], maxAge);
70
72
 
71
- parseInt(_sMaxAge) > 0 &&
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
83
+ .replace(/s-maxage=\d*/g, `s-maxage=${_sMaxAge}`);
84
+ }
85
+ });
86
+ parseInt(_maxAge) > 0 &&
72
87
  apiProxy.on("proxyRes", function (proxyRes, req) {
73
88
  const pathName = get(req, ["originalUrl"], "").split("?")[0];
74
89
  const checkForExcludeRoutes = excludeRoutes.some((path) => {
@@ -77,7 +92,8 @@ exports.upstreamQuintypeRoutes = function upstreamQuintypeRoutes(
77
92
  });
78
93
  const getCacheControl = get(proxyRes, ["headers", "cache-control"], "");
79
94
  if (!checkForExcludeRoutes && getCacheControl.includes("public")) {
80
- proxyRes.headers["cache-control"] = getCacheControl.replace(/s-maxage=\d*/g, `s-maxage=${_sMaxAge}`);
95
+ proxyRes.headers["cache-control"] = getCacheControl
96
+ .replace(/max-age=\d*/g, `max-age=${_maxAge}`);
81
97
  }
82
98
  });
83
99
 
@@ -90,7 +106,7 @@ exports.upstreamQuintypeRoutes = function upstreamQuintypeRoutes(
90
106
  .catch(() => res.status(503).send({ error: { message: "Config not loaded" } }));
91
107
  });
92
108
 
93
- // Mention the routes which don't want to override the s-maxage value
109
+ // Mention the routes which don't want to override the s-maxage value and max-age value
94
110
  const excludeRoutes = [
95
111
  "/qlitics.js",
96
112
  "/api/v1/breaking-news",
@@ -288,6 +304,7 @@ function getWithConfig(app, route, handler, opts = {}) {
288
304
  * @param {boolean|function} redirectToLowercaseSlugs If set or evaluates to true, then for every story-page request having capital latin letters in the slug, it responds with a 301 redirect to the lowercase slug URL. (default: true)
289
305
  * @param {boolean|function} shouldEncodeAmpUri If set to true, then for every story-page request the slug will be encoded, in case of a vernacular slug this should be set to false. Receives path as param (default: true)
290
306
  * @param {number} sMaxAge Overrides the s-maxage value, the default value is set to 900 seconds. We can set `isomorphicRoutesSmaxage: 900` under `publisher` in publisher.yml config file that comes from BlackKnight or pass sMaxAge as a param.
307
+ * @param {number} maxAge Overrides the max-age value, the default value is set to 15 seconds. We can set `isomorphicRoutesMaxage: 15` under `publisher` in publisher.yml config file that comes from BlackKnight or pass maxAge as a param.
291
308
  * @param {(string|function)} fcmServerKey FCM serverKey is used for registering FCM Topic.
292
309
  * @param {string} appLoadingPlaceholder This string gets injected into the app container when the page is loaded via service worker. Can be used to show skeleton layouts, animations or other progress indicators before it is replaced by the page content.
293
310
  */
@@ -331,6 +348,7 @@ exports.isomorphicRoutes = function isomorphicRoutes(
331
348
  redirectToLowercaseSlugs = false,
332
349
  shouldEncodeAmpUri,
333
350
  sMaxAge = 900,
351
+ maxAge = 15,
334
352
  appLoadingPlaceholder = "",
335
353
  fcmServerKey = "",
336
354
  webengageConfig = {},
@@ -340,6 +358,8 @@ exports.isomorphicRoutes = function isomorphicRoutes(
340
358
 
341
359
  const _sMaxAge = parseInt(get(publisherConfig, ["publisher", "isomorphicRoutesSmaxage"], sMaxAge));
342
360
 
361
+ const _maxAge = parseInt(get(publisherConfig, ["publisher", "isomorphicRoutesMaxage"], maxAge));
362
+
343
363
  pickComponent = makePickComponentSync(pickComponent);
344
364
  loadData = wrapLoadDataWithMultiDomain(publisherConfig, loadData, 2);
345
365
  loadErrorData = wrapLoadDataWithMultiDomain(publisherConfig, loadErrorData, 1);
@@ -422,6 +442,7 @@ exports.isomorphicRoutes = function isomorphicRoutes(
422
442
  cdnProvider,
423
443
  redirectToLowercaseSlugs,
424
444
  sMaxAge: _sMaxAge,
445
+ maxAge: _maxAge,
425
446
  networkOnly: true,
426
447
  })
427
448
  );
@@ -456,6 +477,7 @@ exports.isomorphicRoutes = function isomorphicRoutes(
456
477
  cdnProvider,
457
478
  redirectToLowercaseSlugs,
458
479
  sMaxAge: _sMaxAge,
480
+ maxAge: _maxAge,
459
481
  })
460
482
  );
461
483
  }
@@ -489,6 +511,7 @@ exports.isomorphicRoutes = function isomorphicRoutes(
489
511
  oneSignalServiceWorkers,
490
512
  publisherConfig,
491
513
  sMaxAge: _sMaxAge,
514
+ maxAge: _maxAge,
492
515
  },
493
516
  route
494
517
  )
@@ -517,11 +540,12 @@ exports.isomorphicRoutes = function isomorphicRoutes(
517
540
  oneSignalServiceWorkers,
518
541
  publisherConfig,
519
542
  sMaxAge: _sMaxAge,
543
+ maxAge: _maxAge,
520
544
  })
521
545
  );
522
546
 
523
547
  if (redirectRootLevelStories) {
524
- app.get("/:storySlug", withConfig(redirectStory, { logError, cdnProvider, sMaxAge: _sMaxAge }));
548
+ app.get("/:storySlug", withConfig(redirectStory, { logError, cdnProvider, sMaxAge: _sMaxAge, maxAge: _maxAge }));
525
549
  }
526
550
 
527
551
  if (handleCustomRoute) {
@@ -534,6 +558,7 @@ exports.isomorphicRoutes = function isomorphicRoutes(
534
558
  seo,
535
559
  cdnProvider,
536
560
  sMaxAge: _sMaxAge,
561
+ maxAge: _maxAge,
537
562
  })
538
563
  );
539
564
  }
@@ -189,6 +189,79 @@ describe("Sketches Proxy", function () {
189
189
  });
190
190
  });
191
191
 
192
+ describe("Override the max-age cache header", function () {
193
+ function getClientStub(hostname) {
194
+ return {
195
+ getHostname: () => "demo.quintype.io",
196
+ getConfig: () =>
197
+ Promise.resolve({
198
+ foo: "bar",
199
+ "sketches-host": "https://www.foo.com",
200
+ }),
201
+ baseUrl: "https://www.foo.com",
202
+ };
203
+ }
204
+ function buildApp(maxAge, { app = express() } = {}) {
205
+ upstreamQuintypeRoutes(app, {
206
+ config: {
207
+ sketches_host: `https://demo.quintype.io`,
208
+ },
209
+ getClient: getClientStub,
210
+ extraRoutes: ["/custom-route"],
211
+ forwardAmp: true,
212
+ forwardFavicon: true,
213
+ publisherConfig: {},
214
+ maxAge: maxAge,
215
+ });
216
+ return app;
217
+ }
218
+
219
+ it("Override the max-age cache header when maxAge value is present", function (done) {
220
+ const maxAge = 3600;
221
+ supertest(buildApp(maxAge))
222
+ .get("/api/v1/config")
223
+ .expect(200)
224
+ .then((res) => {
225
+ const cacheControl = res.headers["cache-control"];
226
+ assert.equal(cacheControl, "public,max-age=3600,s-maxage=240,stale-while-revalidate=300,stale-if-error=7200");
227
+ })
228
+ .then(done);
229
+ });
230
+
231
+ it("Does not override the max-age cache header if cacheability is Private", function (done) {
232
+ const maxAge = 15;
233
+ supertest(buildApp(maxAge))
234
+ .get("/api/auth/v1/users/me")
235
+ .then((res) => {
236
+ const cacheControl = res.headers["cache-control"];
237
+ assert.equal(cacheControl, "private,no-cache,no-store");
238
+ })
239
+ .then(done);
240
+ });
241
+
242
+ it("Does not override the max-age cache header for Breaking News", function (done) {
243
+ const maxAge = 3600;
244
+ supertest(buildApp(maxAge))
245
+ .get("/api/v1/breaking-news")
246
+ .then((res) => {
247
+ const cacheControl = res.headers["cache-control"];
248
+ assert.equal(cacheControl, "public,max-age=15,s-maxage=240,stale-while-revalidate=300,stale-if-error=7200");
249
+ })
250
+ .then(done);
251
+ });
252
+
253
+ it("if maxAge value is not present, do not override cache headers", function (done) {
254
+ supertest(buildApp())
255
+ .get("/api/v1/config")
256
+ .expect(200)
257
+ .then((res) => {
258
+ const cacheControl = res.headers["cache-control"];
259
+ assert.equal(cacheControl, "public,max-age=15,s-maxage=240,stale-while-revalidate=300,stale-if-error=7200");
260
+ })
261
+ .then(done);
262
+ });
263
+ });
264
+
192
265
  describe("sitemap requests", function () {
193
266
  function buildApp({ isSitemapUrlEnabled }) {
194
267
  const app = express();