1 | "use strict";
|
2 |
|
3 | var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
4 |
|
5 | var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
6 |
|
7 | const path = require(`path`);
|
8 |
|
9 | const {
|
10 | store
|
11 | } = require(`../redux`);
|
12 |
|
13 | const fs = require(`fs`);
|
14 |
|
15 | const pageDataUtil = require(`../utils/page-data`);
|
16 |
|
17 | const normalizePagePath = require(`../utils/normalize-page-path`);
|
18 |
|
19 | const telemetry = require(`gatsby-telemetry`);
|
20 |
|
21 | const url = require(`url`);
|
22 |
|
23 | const {
|
24 | createHash
|
25 | } = require(`crypto`);
|
26 |
|
27 | const denormalize = path => {
|
28 | if (path === undefined) {
|
29 | return path;
|
30 | }
|
31 |
|
32 | if (path === `/`) {
|
33 | return `/`;
|
34 | }
|
35 |
|
36 | if (path.charAt(path.length - 1) !== `/`) {
|
37 | return path + `/`;
|
38 | }
|
39 |
|
40 | return path;
|
41 | };
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | const getCachedPageData = async (pagePath, directory) => {
|
50 | const {
|
51 | program,
|
52 | pages
|
53 | } = store.getState();
|
54 | const publicDir = path.join(program.directory, `public`);
|
55 |
|
56 | if (pages.has(denormalize(pagePath)) || pages.has(pagePath)) {
|
57 | try {
|
58 | const pageData = await pageDataUtil.read({
|
59 | publicDir
|
60 | }, pagePath);
|
61 | return {
|
62 | result: pageData.result,
|
63 | id: pagePath
|
64 | };
|
65 | } catch (err) {
|
66 | throw new Error(`Error loading a result for the page query in "${pagePath}". Query was not run and no cached result was found.`);
|
67 | }
|
68 | }
|
69 |
|
70 | return undefined;
|
71 | };
|
72 |
|
73 | const hashPaths = paths => {
|
74 | if (!paths) {
|
75 | return undefined;
|
76 | }
|
77 |
|
78 | return paths.map(path => {
|
79 | if (!path) {
|
80 | return undefined;
|
81 | }
|
82 |
|
83 | return createHash(`sha256`).update(path).digest(`hex`);
|
84 | });
|
85 | };
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 | const getCachedStaticQueryResults = (resultsMap, directory) => {
|
94 | const cachedStaticQueryResults = new Map();
|
95 | const {
|
96 | staticQueryComponents
|
97 | } = store.getState();
|
98 | staticQueryComponents.forEach(staticQueryComponent => {
|
99 |
|
100 | if (resultsMap.has(staticQueryComponent.hash)) return;
|
101 | const filePath = path.join(directory, `public`, `static`, `d`, `${staticQueryComponent.hash}.json`);
|
102 | const fileResult = fs.readFileSync(filePath, `utf-8`);
|
103 |
|
104 | if (fileResult === `undefined`) {
|
105 | console.log(`Error loading a result for the StaticQuery in "${staticQueryComponent.componentPath}". Query was not run and no cached result was found.`);
|
106 | return;
|
107 | }
|
108 |
|
109 | cachedStaticQueryResults.set(staticQueryComponent.hash, {
|
110 | result: JSON.parse(fileResult),
|
111 | id: staticQueryComponent.hash
|
112 | });
|
113 | });
|
114 | return cachedStaticQueryResults;
|
115 | };
|
116 |
|
117 | const getRoomNameFromPath = path => `path-${path}`;
|
118 |
|
119 | class WebsocketManager {
|
120 | constructor() {
|
121 | (0, _defineProperty2.default)(this, "pageResults", void 0);
|
122 | (0, _defineProperty2.default)(this, "staticQueryResults", void 0);
|
123 | (0, _defineProperty2.default)(this, "errors", void 0);
|
124 | (0, _defineProperty2.default)(this, "isInitialised", void 0);
|
125 | (0, _defineProperty2.default)(this, "activePaths", void 0);
|
126 | (0, _defineProperty2.default)(this, "programDir", void 0);
|
127 | this.isInitialised = false;
|
128 | this.activePaths = new Set();
|
129 | this.pageResults = new Map();
|
130 | this.staticQueryResults = new Map();
|
131 | this.errors = new Map();
|
132 |
|
133 |
|
134 | this.init = this.init.bind(this);
|
135 | this.getSocket = this.getSocket.bind(this);
|
136 | this.emitPageData = this.emitPageData.bind(this);
|
137 | this.emitStaticQueryData = this.emitStaticQueryData.bind(this);
|
138 | this.emitError = this.emitError.bind(this);
|
139 | this.connectedClients = 0;
|
140 | }
|
141 |
|
142 | init({
|
143 | server,
|
144 | directory
|
145 | }) {
|
146 | this.programDir = directory;
|
147 | const cachedStaticQueryResults = getCachedStaticQueryResults(this.staticQueryResults, this.programDir);
|
148 | this.staticQueryResults = new Map([...this.staticQueryResults, ...cachedStaticQueryResults]);
|
149 | this.websocket = require(`socket.io`)(server);
|
150 | this.websocket.on(`connection`, s => {
|
151 | let activePath = null;
|
152 |
|
153 | if (s && s.handshake && s.handshake.headers && s.handshake.headers.referer) {
|
154 | const path = url.parse(s.handshake.headers.referer).path;
|
155 |
|
156 | if (path) {
|
157 | activePath = path;
|
158 | this.activePaths.add(path);
|
159 | }
|
160 | }
|
161 |
|
162 | this.connectedClients += 1;
|
163 |
|
164 | this.staticQueryResults.forEach(result => {
|
165 | this.websocket.send({
|
166 | type: `staticQueryResult`,
|
167 | payload: result
|
168 | });
|
169 | });
|
170 | this.errors.forEach((message, errorID) => {
|
171 | this.websocket.send({
|
172 | type: `overlayError`,
|
173 | payload: {
|
174 | id: errorID,
|
175 | message
|
176 | }
|
177 | });
|
178 | });
|
179 |
|
180 | const leaveRoom = path => {
|
181 | s.leave(getRoomNameFromPath(path));
|
182 | const leftRoom = this.websocket.sockets.adapter.rooms[getRoomNameFromPath(path)];
|
183 |
|
184 | if (!leftRoom || leftRoom.length === 0) {
|
185 | this.activePaths.delete(path);
|
186 | }
|
187 | };
|
188 |
|
189 | const getDataForPath = async path => {
|
190 | if (!this.pageResults.has(path)) {
|
191 | try {
|
192 | const result = await getCachedPageData(path, this.programDir);
|
193 | this.pageResults.set(path, result || {
|
194 | id: path
|
195 | });
|
196 | } catch (err) {
|
197 | console.log(err.message);
|
198 | return;
|
199 | }
|
200 | }
|
201 |
|
202 | this.websocket.send({
|
203 | type: `pageQueryResult`,
|
204 | why: `getDataForPath`,
|
205 | payload: this.pageResults.get(path)
|
206 | });
|
207 | const clientsCount = this.connectedClients;
|
208 |
|
209 | if (clientsCount && clientsCount > 0) {
|
210 | telemetry.trackCli(`WEBSOCKET_PAGE_DATA_UPDATE`, {
|
211 | siteMeasurements: {
|
212 | clientsCount,
|
213 | paths: hashPaths(Array.from(this.activePaths))
|
214 | }
|
215 | }, {
|
216 | debounce: true
|
217 | });
|
218 | }
|
219 | };
|
220 |
|
221 | s.on(`getDataForPath`, getDataForPath);
|
222 | s.on(`registerPath`, path => {
|
223 | s.join(getRoomNameFromPath(path));
|
224 | activePath = path;
|
225 | this.activePaths.add(path);
|
226 | });
|
227 | s.on(`disconnect`, s => {
|
228 | leaveRoom(activePath);
|
229 | this.connectedClients -= 1;
|
230 | });
|
231 | s.on(`unregisterPath`, path => {
|
232 | leaveRoom(path);
|
233 | });
|
234 | });
|
235 | this.isInitialised = true;
|
236 | }
|
237 |
|
238 | getSocket() {
|
239 | return this.isInitialised && this.websocket;
|
240 | }
|
241 |
|
242 | emitStaticQueryData(data) {
|
243 | this.staticQueryResults.set(data.id, data);
|
244 |
|
245 | if (this.isInitialised) {
|
246 | this.websocket.send({
|
247 | type: `staticQueryResult`,
|
248 | payload: data
|
249 | });
|
250 | const clientsCount = this.connectedClients;
|
251 |
|
252 | if (clientsCount && clientsCount > 0) {
|
253 | telemetry.trackCli(`WEBSOCKET_EMIT_STATIC_PAGE_DATA_UPDATE`, {
|
254 | siteMeasurements: {
|
255 | clientsCount,
|
256 | paths: hashPaths(Array.from(this.activePaths))
|
257 | }
|
258 | }, {
|
259 | debounce: true
|
260 | });
|
261 | }
|
262 | }
|
263 | }
|
264 |
|
265 | emitPageData(data) {
|
266 | data.id = normalizePagePath(data.id);
|
267 | this.pageResults.set(data.id, data);
|
268 |
|
269 | if (this.isInitialised) {
|
270 | this.websocket.send({
|
271 | type: `pageQueryResult`,
|
272 | payload: data
|
273 | });
|
274 | const clientsCount = this.connectedClients;
|
275 |
|
276 | if (clientsCount && clientsCount > 0) {
|
277 | telemetry.trackCli(`WEBSOCKET_EMIT_PAGE_DATA_UPDATE`, {
|
278 | siteMeasurements: {
|
279 | clientsCount,
|
280 | paths: hashPaths(Array.from(this.activePaths))
|
281 | }
|
282 | }, {
|
283 | debounce: true
|
284 | });
|
285 | }
|
286 | }
|
287 | }
|
288 |
|
289 | emitError(id, message) {
|
290 | if (message) {
|
291 | this.errors.set(id, message);
|
292 | } else {
|
293 | this.errors.delete(id);
|
294 | }
|
295 |
|
296 | if (this.isInitialised) {
|
297 | this.websocket.send({
|
298 | type: `overlayError`,
|
299 | payload: {
|
300 | id,
|
301 | message
|
302 | }
|
303 | });
|
304 | }
|
305 | }
|
306 |
|
307 | }
|
308 |
|
309 | const manager = new WebsocketManager();
|
310 | module.exports = manager;
|
311 |
|
\ | No newline at end of file |