@quintype/framework 7.4.0-upgrade-react.1 → 7.4.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 +11 -0
- package/README.md +51 -27
- package/client/impl/fcm.js +15 -28
- package/client/start.js +8 -4
- package/package.json +12 -8
- package/server/amp/handlers/story-page.js +15 -0
- package/server/routes.js +4 -1
- package/test/babel.js +5 -1
- package/test/integration/amp-handler-test.js +38 -10
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
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.4.0](https://github.com/quintype/quintype-node-framework/compare/v7.2.0...v7.4.0) (2022-05-23)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **request:** Implement request fallback for external calls ([#296](https://github.com/quintype/quintype-node-framework/issues/296)) ([44e1f7e](https://github.com/quintype/quintype-node-framework/commit/44e1f7eea9825225c0c300fb57a8178bd22c0630))
|
|
11
|
+
|
|
12
|
+
### [7.3.4](https://github.com/quintype/quintype-node-framework/compare/v7.3.3...v7.3.4) (2022-05-20)
|
|
13
|
+
|
|
14
|
+
### [7.3.3](https://github.com/quintype/quintype-node-framework/compare/v7.3.2...v7.3.3) (2022-05-20)
|
|
15
|
+
|
|
5
16
|
### [7.3.2](https://github.com/quintype/quintype-node-framework/compare/v7.3.1...v7.3.2) (2022-04-18)
|
|
6
17
|
|
|
7
18
|
### [7.3.1](https://github.com/quintype/quintype-node-framework/compare/v7.3.0...v7.3.1) (2022-03-03)
|
package/README.md
CHANGED
|
@@ -145,23 +145,30 @@ startApp(renderApplication, CUSTOM_REDUCERS, {
|
|
|
145
145
|
|
|
146
146
|
Steps to Integrate FCM in your project
|
|
147
147
|
|
|
148
|
-
1. While executing startApp in your project
|
|
148
|
+
1. app/client/app.js While executing startApp in your project send the firebaseConfig via opts. `firebaseConfig` can either be an object or a function that returns the firebase config object
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
Example:
|
|
151
151
|
|
|
152
|
-
```
|
|
152
|
+
```js
|
|
153
153
|
startApp(renderApplication,
|
|
154
154
|
CUSTOM_REDUCERS,
|
|
155
155
|
{
|
|
156
156
|
enableServiceWorker: process.env.NODE_ENV === "production",
|
|
157
|
-
|
|
157
|
+
firebaseConfig: {
|
|
158
|
+
messagingSenderId: --YOUR KEY-- ,
|
|
159
|
+
projectId: --YOUR KEY--,
|
|
160
|
+
apiKey: --YOUR KEY--,
|
|
161
|
+
storageBucket: --YOUR KEY--,
|
|
162
|
+
authDomain: --YOUR KEY--,
|
|
163
|
+
appId: --YOUR KEY--,
|
|
164
|
+
}
|
|
158
165
|
...
|
|
159
166
|
})
|
|
160
167
|
```
|
|
161
168
|
|
|
162
169
|
2. app/server/app.js should have the fcm configuration as below:
|
|
163
170
|
|
|
164
|
-
```
|
|
171
|
+
```js
|
|
165
172
|
isomorphicRoutes(app, {
|
|
166
173
|
...
|
|
167
174
|
fcmServerKey: (config) => <ServerKey> || fcmServerKey: <ServerKey> {(function|string)}
|
|
@@ -174,28 +181,45 @@ isomorphicRoutes(app, {
|
|
|
174
181
|
|
|
175
182
|
Example of the script:
|
|
176
183
|
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
184
|
+
```js
|
|
185
|
+
importScripts("https://www.gstatic.com/firebasejs/9.6.10/firebase-app-compat.js");
|
|
186
|
+
importScripts("https://www.gstatic.com/firebasejs/9.6.10/firebase-messaging-compat.js");
|
|
187
|
+
|
|
188
|
+
firebase.initializeApp({
|
|
189
|
+
messagingSenderId: --YOUR KEY-- ,
|
|
190
|
+
projectId: --YOUR KEY--,
|
|
191
|
+
apiKey: --YOUR KEY--,
|
|
192
|
+
storageBucket: --YOUR KEY--,
|
|
193
|
+
authDomain: --YOUR KEY--,
|
|
194
|
+
appId: --YOUR KEY--,
|
|
195
|
+
});
|
|
196
|
+
self.addEventListener('notificationclick', (event) => {
|
|
197
|
+
|
|
198
|
+
const url = event.notification.data.url;
|
|
199
|
+
if(url) {
|
|
200
|
+
clients.openWindow(url);
|
|
201
|
+
}
|
|
202
|
+
clients.openWindow(-- YOUR Sketches Host --);
|
|
203
|
+
}, false);
|
|
204
|
+
|
|
205
|
+
const messaging = firebase.messaging();
|
|
206
|
+
|
|
207
|
+
messaging.onBackgroundMessage(function(payload) {
|
|
208
|
+
const data = payload["data"];
|
|
209
|
+
const notificationTitle = data.title || ""
|
|
210
|
+
const notificationOptions = {
|
|
211
|
+
body: data.body,
|
|
212
|
+
icon: data["hero_image_s3_url"],
|
|
213
|
+
image: data["hero_image_s3_url"],
|
|
214
|
+
data: {
|
|
215
|
+
url: data["click_action"],
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
self.registration.showNotification(notificationTitle,
|
|
220
|
+
notificationOptions);
|
|
221
|
+
});
|
|
222
|
+
|
|
199
223
|
```
|
|
200
224
|
|
|
201
225
|
4. Make sure that the page data should have config with key fcmMessageSenderId refer doStartApp function in app/client/start.js.
|
package/client/impl/fcm.js
CHANGED
|
@@ -1,39 +1,26 @@
|
|
|
1
|
-
export function initializeFCM(
|
|
2
|
-
if (!messageSenderId) {
|
|
3
|
-
console.log("messageSenderId is required");
|
|
4
|
-
return false;
|
|
5
|
-
}
|
|
1
|
+
export function initializeFCM(firebaseConfig) {
|
|
6
2
|
Promise.all([
|
|
7
3
|
import(/* webpackChunkName: "firebase-app" */ "firebase/app"),
|
|
8
4
|
import(/* webpackChunkName: "firebase-messaging" */ "firebase/messaging"),
|
|
9
|
-
])
|
|
10
|
-
|
|
11
|
-
firebase.initializeApp({
|
|
12
|
-
messagingSenderId:
|
|
5
|
+
])
|
|
6
|
+
.then(([firebase, m]) => {
|
|
7
|
+
const app = firebase.initializeApp({
|
|
8
|
+
messagingSenderId: firebaseConfig.messagingSenderId.toString(),
|
|
9
|
+
projectId: firebaseConfig.projectId,
|
|
10
|
+
apiKey: firebaseConfig.apiKey,
|
|
11
|
+
storageBucket: firebaseConfig.storageBucket,
|
|
12
|
+
authDomain: firebaseConfig.authDomain,
|
|
13
|
+
appId: firebaseConfig.appId,
|
|
13
14
|
});
|
|
14
|
-
const messaging =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
messaging.onTokenRefresh(() => updateToken(firebase));
|
|
19
|
-
})
|
|
20
|
-
.catch(console.error);
|
|
21
|
-
});
|
|
22
|
-
} catch (error) {
|
|
23
|
-
console.error(error);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function updateToken(firebaseInstance) {
|
|
29
|
-
return firebaseInstance
|
|
30
|
-
.messaging()
|
|
31
|
-
.getToken()
|
|
15
|
+
const messaging = m.getMessaging(app);
|
|
16
|
+
return m.getToken(messaging);
|
|
17
|
+
// No need to refresh token https://github.com/firebase/firebase-js-sdk/issues/4132
|
|
18
|
+
})
|
|
32
19
|
.then((token) => {
|
|
33
20
|
return registerFCMTopic(token);
|
|
34
21
|
})
|
|
35
22
|
.catch((err) => {
|
|
36
|
-
|
|
23
|
+
console.error(err);
|
|
37
24
|
});
|
|
38
25
|
}
|
|
39
26
|
|
package/client/start.js
CHANGED
|
@@ -282,10 +282,14 @@ export function startApp(renderApplication, reducers, opts) {
|
|
|
282
282
|
|
|
283
283
|
store.dispatch({ type: NAVIGATE_TO_PAGE, page, currentPath: path });
|
|
284
284
|
|
|
285
|
-
if (opts.
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
285
|
+
if (opts.firebaseConfig) {
|
|
286
|
+
const fcm = typeof opts.firebaseConfig === "function" ? opts.firebaseConfig(page) : opts.firebaseConfig;
|
|
287
|
+
if (fcm) {
|
|
288
|
+
const mssgSenderId = fcm.messagingSenderId;
|
|
289
|
+
const projectId = fcm.projectId;
|
|
290
|
+
const apiKey = fcm.apiKey;
|
|
291
|
+
if (mssgSenderId && projectId && apiKey) initializeFCM(fcm);
|
|
292
|
+
}
|
|
289
293
|
}
|
|
290
294
|
|
|
291
295
|
const { config: { "theme-attributes": pageThemeAttributes = {} } = {} } = page;
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quintype/framework",
|
|
3
|
-
"version": "7.4.0
|
|
3
|
+
"version": "7.4.0",
|
|
4
4
|
"description": "Libraries to help build Quintype Node.js apps",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": "^16.0.0",
|
|
8
|
+
"npm": "^8.5.0"
|
|
9
|
+
},
|
|
6
10
|
"scripts": {
|
|
7
11
|
"prepublishOnly": "npm test && ./bin-dev-scripts/standard-version-release.sh",
|
|
8
12
|
"test": "NODE_ENV=test npx mocha --recursive --require ./test/babel",
|
|
@@ -27,18 +31,19 @@
|
|
|
27
31
|
"homepage": "https://github.com/quintype/quintype-node-framework#readme",
|
|
28
32
|
"dependencies": {
|
|
29
33
|
"@ampproject/toolbox-optimizer": "2.8.3",
|
|
30
|
-
"@quintype/amp": "^2.4.
|
|
34
|
+
"@quintype/amp": "^2.4.21",
|
|
31
35
|
"@quintype/backend": "^2.3.1",
|
|
32
36
|
"@quintype/components": "^3.0.0",
|
|
33
37
|
"@quintype/prerender-node": "^3.2.24",
|
|
34
|
-
"@quintype/seo": "^1.
|
|
38
|
+
"@quintype/seo": "^1.39.0",
|
|
35
39
|
"atob": "^2.1.2",
|
|
40
|
+
"babel-plugin-react-css-modules": "^5.2.6",
|
|
36
41
|
"chalk": "^4.1.2",
|
|
37
42
|
"cluster": "^0.7.7",
|
|
38
43
|
"compression": "^1.7.4",
|
|
39
44
|
"ejs": "^3.1.6",
|
|
40
45
|
"express": "^4.17.1",
|
|
41
|
-
"firebase": "^6.
|
|
46
|
+
"firebase": "^9.6.10",
|
|
42
47
|
"get-youtube-id": "^1.0.1",
|
|
43
48
|
"grpc": "^1.21.1",
|
|
44
49
|
"http-proxy": "^1.18.1",
|
|
@@ -47,8 +52,8 @@
|
|
|
47
52
|
"mocha-snapshots": "^4.2.0",
|
|
48
53
|
"morgan": "^1.10.0",
|
|
49
54
|
"path-to-regexp": "^6.2.0",
|
|
50
|
-
"react": "^
|
|
51
|
-
"react-dom": "^
|
|
55
|
+
"react": "^16.14.0",
|
|
56
|
+
"react-dom": "^16.14.0",
|
|
52
57
|
"react-redux": "^7.2.5",
|
|
53
58
|
"react-router": "^5.2.1",
|
|
54
59
|
"redux": "^4.1.1",
|
|
@@ -60,9 +65,8 @@
|
|
|
60
65
|
"@babel/eslint-parser": "^7.15.7",
|
|
61
66
|
"@loadable/component": "^5.15.0",
|
|
62
67
|
"@loadable/server": "^5.15.1",
|
|
63
|
-
"@quintype/build": "^
|
|
68
|
+
"@quintype/build": "^4.0.0",
|
|
64
69
|
"babel-plugin-quintype-assets": "^1.1.1",
|
|
65
|
-
"babel-plugin-react-css-modules": "^5.2.6",
|
|
66
70
|
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
|
|
67
71
|
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
|
68
72
|
"babel-preset-es2015": "^6.24.1",
|
|
@@ -7,6 +7,7 @@ const { Story, AmpConfig } = require("../../impl/api-client-impl");
|
|
|
7
7
|
const { optimize, getDomainSpecificOpts } = require("../helpers");
|
|
8
8
|
const { storyToCacheKey } = require("../../caching");
|
|
9
9
|
const { addCacheHeadersToResult } = require("../../handlers/cdn-caching");
|
|
10
|
+
const { getRedirectUrl } = require("../../../server/redirect-url-helper");
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* ampStoryPageHandler gets all the things needed and calls "ampifyStory" function (which comes from ampLib)
|
|
@@ -37,6 +38,20 @@ async function ampStoryPageHandler(
|
|
|
37
38
|
) {
|
|
38
39
|
try {
|
|
39
40
|
const opts = cloneDeep(rest);
|
|
41
|
+
|
|
42
|
+
const redirectUrls = opts && opts.redirectUrls;
|
|
43
|
+
const getEnableAmp = get(opts, ["enableAmp"], true);
|
|
44
|
+
|
|
45
|
+
const enableAmp = typeof getEnableAmp === "function" ? opts.enableAmp(config) : getEnableAmp;
|
|
46
|
+
|
|
47
|
+
if (typeof redirectUrls === "function" || (redirectUrls && Object.keys(redirectUrls).length > 0)) {
|
|
48
|
+
await getRedirectUrl(req, res, next, { redirectUrls, config });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!enableAmp) {
|
|
52
|
+
return res.redirect(301, `/${req.params[0]}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
40
55
|
const domainSpecificOpts = getDomainSpecificOpts(opts, domainSlug);
|
|
41
56
|
const url = urlLib.parse(req.url, true);
|
|
42
57
|
const { ampifyStory, unsupportedStoryElementsPresent } = ampLibrary;
|
package/server/routes.js
CHANGED
|
@@ -232,12 +232,13 @@ function wrapLoadDataWithMultiDomain(publisherConfig, f, configPos) {
|
|
|
232
232
|
* @param {Object} opts Options that will be passed to the handler. These options will be merged with a *config* and *client*
|
|
233
233
|
*/
|
|
234
234
|
function getWithConfig(app, route, handler, opts = {}) {
|
|
235
|
+
const configWrapper = opts.configWrapper;
|
|
235
236
|
const {
|
|
236
237
|
getClient = require("./api-client").getClient,
|
|
237
238
|
publisherConfig = require("./publisher-config"),
|
|
238
239
|
logError = require("./logger").error,
|
|
239
240
|
} = opts;
|
|
240
|
-
const withConfig = withConfigPartial(getClient, logError, publisherConfig);
|
|
241
|
+
const withConfig = withConfigPartial(getClient, logError, publisherConfig, configWrapper);
|
|
241
242
|
app.get(route, withConfig(handler, opts));
|
|
242
243
|
}
|
|
243
244
|
|
|
@@ -608,6 +609,8 @@ exports.mountQuintypeAt = function (app, mountAt) {
|
|
|
608
609
|
* @param {Object} opts.templates An object that's used to pass custom templates. Each key corresponds to the template name and corresponding value is the template
|
|
609
610
|
* @param {Object} opts.slots An object used to pass slot data.
|
|
610
611
|
* @param {SEO} opts.seo An SEO object that will generate html tags for each page. See [@quintype/seo](https://developers.quintype.com/malibu/isomorphic-rendering/server-side-architecture#quintypeseo)
|
|
612
|
+
* @param {boolean|function} opts.enableAmp 'amp/story/:slug' should redirect to non-amp page if enableAmp is false
|
|
613
|
+
* @param {object|function} opts.redirectUrls list of urls which is used to redirect URL(sourceUrl) to a different URL(destinationUrl). Eg: redirectUrls: { "/amp/story/sports/ipl-2021": {destinationUrl: "/amp/story/sports/cricket-2022", statusCode: 302,},}
|
|
611
614
|
* @param {function} opts.headerCardRender Render prop for story headerCard. If passed, the headerCard in default stories will be replaced with this
|
|
612
615
|
* @param {function} opts.relatedStoriesRender Render prop for relatedStories in a story page. If passed, this will replace the related stories
|
|
613
616
|
*
|
package/test/babel.js
CHANGED
|
@@ -2,7 +2,11 @@ const ROOT_PATH = require("path").resolve(__dirname, "..");
|
|
|
2
2
|
|
|
3
3
|
require("babel-register")({
|
|
4
4
|
presets: ["react"],
|
|
5
|
-
plugins: [
|
|
5
|
+
plugins: [
|
|
6
|
+
"transform-es2015-modules-commonjs",
|
|
7
|
+
"transform-object-rest-spread",
|
|
8
|
+
"quintype-assets",
|
|
9
|
+
],
|
|
6
10
|
ignore(file) {
|
|
7
11
|
return file.startsWith(ROOT_PATH + "/node_modules");
|
|
8
12
|
},
|
|
@@ -70,16 +70,14 @@ function getClientStub({
|
|
|
70
70
|
"story-elements": [
|
|
71
71
|
{
|
|
72
72
|
description: "",
|
|
73
|
-
"page-url":
|
|
74
|
-
"/story/7f3d5bdb-ec52-4047-ac0d-df4036ec974b/element/9eb8f5cc-6ebe-4fb0-88b8-eca79efde210",
|
|
73
|
+
"page-url": "/story/7f3d5bdb-ec52-4047-ac0d-df4036ec974b/element/9eb8f5cc-6ebe-4fb0-88b8-eca79efde210",
|
|
75
74
|
type: "text",
|
|
76
75
|
"family-id": "e9e12f9f-8b9f-4b93-a8c8-83c7b278000f",
|
|
77
76
|
title: "",
|
|
78
77
|
id: "9eb8f5cc-6ebe-4fb0-88b8-eca79efde210",
|
|
79
78
|
metadata: {},
|
|
80
79
|
subtype: null,
|
|
81
|
-
text:
|
|
82
|
-
"<p>In India today, the legal profession is growing in lockstep with one of the world’s most dynamic economies. It’s no surprise then— that in terms of absolute numbers— India’s legal profession is the world’s second largest, with over 1.4 million enrolled lawyers in legal practices nationwide.</p>",
|
|
80
|
+
text: "<p>In India today, the legal profession is growing in lockstep with one of the world’s most dynamic economies. It’s no surprise then— that in terms of absolute numbers— India’s legal profession is the world’s second largest, with over 1.4 million enrolled lawyers in legal practices nationwide.</p>",
|
|
83
81
|
},
|
|
84
82
|
],
|
|
85
83
|
"card-updated-at": 1581327522163,
|
|
@@ -195,20 +193,18 @@ const dummyAmpLib = {
|
|
|
195
193
|
|
|
196
194
|
const getClientStubWithRelatedStories = (relatedStories) => () =>
|
|
197
195
|
Object.assign({}, getClientStub(), {
|
|
198
|
-
getRelatedStories: () =>
|
|
199
|
-
Promise.resolve({ "related-stories": relatedStories }),
|
|
196
|
+
getRelatedStories: () => Promise.resolve({ "related-stories": relatedStories }),
|
|
200
197
|
});
|
|
201
198
|
|
|
202
|
-
function createApp({
|
|
203
|
-
clientStub = getClientStub,
|
|
204
|
-
ampLibrary = dummyAmpLib,
|
|
205
|
-
} = {}) {
|
|
199
|
+
function createApp({ clientStub = getClientStub, ampLibrary = dummyAmpLib, enableAmp = true, redirectUrls = {} } = {}) {
|
|
206
200
|
const app = express();
|
|
207
201
|
ampRoutes(app, {
|
|
208
202
|
getClient: clientStub,
|
|
209
203
|
publisherConfig: {},
|
|
210
204
|
ampLibrary,
|
|
211
205
|
additionalConfig: {},
|
|
206
|
+
enableAmp,
|
|
207
|
+
redirectUrls,
|
|
212
208
|
});
|
|
213
209
|
return app;
|
|
214
210
|
}
|
|
@@ -240,6 +236,38 @@ describe("ampStoryPageHandler integration tests", () => {
|
|
|
240
236
|
return done();
|
|
241
237
|
});
|
|
242
238
|
});
|
|
239
|
+
it("should redirect to non-amp page if enableAmp is false", (done) => {
|
|
240
|
+
const app = createApp({
|
|
241
|
+
enableAmp: false,
|
|
242
|
+
});
|
|
243
|
+
supertest(app)
|
|
244
|
+
.get("/amp/story/cricket/ipl-2021")
|
|
245
|
+
.expect(301)
|
|
246
|
+
.expect("Location", "/cricket/ipl-2021")
|
|
247
|
+
.end((err) => {
|
|
248
|
+
if (err) return done(err);
|
|
249
|
+
return done();
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
it("Redirects the urls with status code to 302 if redirectUrls is present ", (done) => {
|
|
253
|
+
const app = createApp({
|
|
254
|
+
enableAmp: true,
|
|
255
|
+
redirectUrls: {
|
|
256
|
+
"/amp/story/sports/ipl-2021": {
|
|
257
|
+
destinationUrl: "/amp/story/sports/cricket-2022",
|
|
258
|
+
statusCode: 302,
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
supertest(app)
|
|
263
|
+
.get("/amp/story/sports/ipl-2021")
|
|
264
|
+
.expect(302)
|
|
265
|
+
.expect("Location", "/amp/story/sports/cricket-2022")
|
|
266
|
+
.end((err) => {
|
|
267
|
+
if (err) return done(err);
|
|
268
|
+
return done();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
243
271
|
it("should redirect to non-amp page if story contains unsuported elements and invalid-elements-strategy in /api/v1/amp/config is set to redirect-to-web-version", (done) => {
|
|
244
272
|
const app = createApp({
|
|
245
273
|
ampLibrary: {
|