1 |
|
2 |
|
3 | import React from "react";
|
4 | import Helmet from "react-helmet";
|
5 | import {renderToString} from "react-dom/server";
|
6 | import {createMemoryHistory, match, RouterContext} from "react-router";
|
7 | import {I18nextProvider} from "react-i18next";
|
8 | import {Provider} from "react-redux";
|
9 | import createRoutes from "routes";
|
10 | import configureStore from "./storeConfig";
|
11 | import preRenderMiddleware from "./middlewares/preRenderMiddleware";
|
12 | import pretty from "pretty";
|
13 |
|
14 | import CanonProvider from "./CanonProvider";
|
15 |
|
16 | import serialize from "serialize-javascript";
|
17 |
|
18 | const tagManagerHead = process.env.CANON_GOOGLE_TAG_MANAGER === undefined ? ""
|
19 | : `
|
20 | <!-- Google Tag Manager -->
|
21 | <script>
|
22 | (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
23 | new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
24 | j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
25 | 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
26 | })(window,document,'script','dataLayer','${process.env.CANON_GOOGLE_TAG_MANAGER}');
|
27 | </script>
|
28 | <!-- End Google Tag Manager -->
|
29 | `;
|
30 |
|
31 | const tagManagerBody = process.env.CANON_GOOGLE_TAG_MANAGER === undefined ? ""
|
32 | : `
|
33 | <!-- Google Tag Manager (noscript) -->
|
34 | <noscript>
|
35 | <iframe src="https://www.googletagmanager.com/ns.html?id=${process.env.CANON_GOOGLE_TAG_MANAGER}" height="0" width="0" style="display:none;visibility:hidden"></iframe>
|
36 | </noscript>
|
37 | <!-- End Google Tag Manager (noscript) -->
|
38 | `;
|
39 |
|
40 | const analtyicsScript = process.env.CANON_GOOGLE_ANALYTICS === undefined ? ""
|
41 | : `
|
42 | <!-- Google Analytics -->
|
43 | <script>
|
44 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
45 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
46 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
47 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
48 | ga('create', '${process.env.CANON_GOOGLE_ANALYTICS}', 'auto');
|
49 | ga('send', 'pageview');
|
50 | </script>
|
51 | <!-- End Google Analytics -->
|
52 | `;
|
53 |
|
54 | const pixelScript = process.env.CANON_FACEBOOK_PIXEL === undefined ? ""
|
55 | : `
|
56 | <!-- Facebook Pixel -->
|
57 | <script> !function(f,b,e,v,n,t,s) {if(f.fbq)return;n=f.fbq=function(){
|
58 | n.callMethod? n.callMethod.apply(n,arguments):n.queue.push(arguments)};
|
59 | if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
|
60 | n.queue=[];t=b.createElement(e);t.async=!0; t.src=v;s=b.getElementsByTagName(e)[0];
|
61 | s.parentNode.insertBefore(t,s)}(window,document,'script', 'https://connect.facebook.net/en_US/fbevents.js');
|
62 | fbq('init', '${process.env.CANON_FACEBOOK_PIXEL}'); fbq('track', 'PageView');
|
63 | </script>
|
64 | <!-- End Facebook Pixel -->
|
65 | `;
|
66 |
|
67 | const hotjarScript = process.env.CANON_HOTJAR === undefined ? ""
|
68 | : `
|
69 | <!-- Hotjar -->
|
70 | <script>
|
71 | (function(h,o,t,j,a,r){
|
72 | h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
|
73 | h._hjSettings={hjid:${process.env.CANON_HOTJAR},hjsv:6};
|
74 | a=o.getElementsByTagName('head')[0];
|
75 | r=o.createElement('script');r.async=1;
|
76 | r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
|
77 | a.appendChild(r);
|
78 | })(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
|
79 | </script>
|
80 | <!-- End Hotjar -->
|
81 | `;
|
82 |
|
83 | const BASE_URL = process.env.CANON_BASE_URL || "/";
|
84 | const basename = BASE_URL.replace(/^[A-z]{4,5}\:\/{2}[A-z0-9\.\-]{1,}\:{0,}[0-9]{0,4}/g, "");
|
85 | const baseTag = process.env.CANON_BASE_URL === undefined ? ""
|
86 | : `
|
87 | <base href='${ BASE_URL }'>`;
|
88 |
|
89 |
|
90 |
|
91 |
|
92 | export default function(defaultStore = {}, headerConfig, reduxMiddleware = false) {
|
93 |
|
94 | return function(req, res) {
|
95 |
|
96 | const locale = req.i18n.language,
|
97 | resources = req.i18n.getResourceBundle(req.i18n.language);
|
98 |
|
99 | const windowLocation = {
|
100 | basename,
|
101 | host: req.headers.host,
|
102 | hostname: req.headers.host.split(":")[0],
|
103 | href: `${ req.protocol }://${ req.headers.host }${ req.url }`,
|
104 | origin: `${ req.protocol }://${ req.headers.host }`,
|
105 | pathname: req.url.split("?")[0],
|
106 | port: req.headers.host.includes(":") ? req.headers.host.split(":")[1] : "80",
|
107 | protocol: `${ req.protocol }:`,
|
108 | query: req.query,
|
109 | search: req.url.includes("?") ? `?${req.url.split("?")[1]}` : ""
|
110 | };
|
111 |
|
112 | const location = req.url.replace(BASE_URL, "");
|
113 | const history = createMemoryHistory({basename, entries: [location]});
|
114 | const store = configureStore({i18n: {locale, resources}, location: windowLocation, ...defaultStore}, history, reduxMiddleware);
|
115 | const routes = createRoutes(store);
|
116 | const rtl = ["ar", "he"].includes(locale);
|
117 |
|
118 | match({history, routes}, (err, redirect, props) => {
|
119 |
|
120 | if (err) res.status(500).json(err);
|
121 | else if (redirect) res.redirect(302, `${redirect.basename}${redirect.pathname}${redirect.hash}${redirect.search}`);
|
122 | else if (props) {
|
123 |
|
124 |
|
125 | preRenderMiddleware(store, props)
|
126 | .then(() => {
|
127 | const initialState = store.getState();
|
128 | const componentHTML = renderToString(
|
129 | <I18nextProvider i18n={req.i18n}>
|
130 | <Provider store={store}>
|
131 | <CanonProvider helmet={headerConfig} locale={locale}>
|
132 | <RouterContext {...props} />
|
133 | </CanonProvider>
|
134 | </Provider>
|
135 | </I18nextProvider>
|
136 | );
|
137 |
|
138 | const header = Helmet.rewind();
|
139 | const htmlAttrs = header.htmlAttributes.toString().replace(" amp", "");
|
140 |
|
141 | const defaultAttrs = headerConfig.htmlAttributes ? Object.keys(headerConfig.htmlAttributes)
|
142 | .map(key => {
|
143 | const val = headerConfig.htmlAttributes[key];
|
144 | return ` ${key}${val ? `="${val}"` : ""}`;
|
145 | })
|
146 | .join("") : "";
|
147 |
|
148 | let status = 200;
|
149 | for (const key in initialState.data) {
|
150 | if ({}.hasOwnProperty.call(initialState.data, key)) {
|
151 | const error = initialState.data[key] ? initialState.data[key].error : null;
|
152 | if (error && typeof error === "number" && error > status) status = error;
|
153 | }
|
154 | }
|
155 |
|
156 | res.status(status).send(`<!doctype html>
|
157 | <html dir="${ rtl ? "rtl" : "ltr" }" ${htmlAttrs}${defaultAttrs}>
|
158 | <head>
|
159 | ${tagManagerHead}${pixelScript}${baseTag}
|
160 | ${ pretty(header.title.toString()).replace(/\n/g, "\n ") }
|
161 |
|
162 | ${ pretty(header.meta.toString()).replace(/\n/g, "\n ") }
|
163 |
|
164 | ${ pretty(header.link.toString()).replace(/\n/g, "\n ") }
|
165 |
|
166 | <link rel='stylesheet' type='text/css' href='${ process.env.CANON_BASE_URL ? "" : "/" }assets/normalize.css'>
|
167 | <link rel='stylesheet' type='text/css' href='${ process.env.CANON_BASE_URL ? "" : "/" }assets/styles.css?v${__TIMESTAMP__}'>
|
168 | ${hotjarScript}
|
169 | </head>
|
170 | <body>
|
171 | ${tagManagerBody}
|
172 | <div id="React-Container">${ componentHTML }</div>
|
173 |
|
174 | <script>
|
175 | window.__SSR__ = true;
|
176 | window.__APP_NAME__ = "${ req.i18n.options.defaultNS }";
|
177 | window.__HELMET_DEFAULT__ = ${ serialize(headerConfig, {isJSON: true, space: 2}).replace(/\n/g, "\n ") };
|
178 | window.__INITIAL_STATE__ = ${ serialize(initialState, {isJSON: true, space: 2}).replace(/\n/g, "\n ") };
|
179 | </script>
|
180 | ${analtyicsScript}
|
181 | <script type="text/javascript" charset="utf-8" src="${ process.env.CANON_BASE_URL ? "" : "/" }assets/app.js?v${__TIMESTAMP__}"></script>
|
182 |
|
183 | </body>
|
184 | </html>`);
|
185 | })
|
186 | .catch(err => {
|
187 | res.status(500).send({error: err.toString(), stackTrace: err.stack.toString()});
|
188 | });
|
189 | }
|
190 | else res.sendStatus(404);
|
191 |
|
192 | });
|
193 | };
|
194 |
|
195 | }
|