"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); const path = require(`path`); const { store } = require(`../redux`); const fs = require(`fs`); const pageDataUtil = require(`../utils/page-data`); const normalizePagePath = require(`../utils/normalize-page-path`); const telemetry = require(`gatsby-telemetry`); const url = require(`url`); const { createHash } = require(`crypto`); const denormalize = path => { if (path === undefined) { return path; } if (path === `/`) { return `/`; } if (path.charAt(path.length - 1) !== `/`) { return path + `/`; } return path; }; /** * Get cached page query result for given page path. * @param {string} pagePath Path to a page. * @param {string} directory Root directory of current project. */ const getCachedPageData = async (pagePath, directory) => { const { program, pages } = store.getState(); const publicDir = path.join(program.directory, `public`); if (pages.has(denormalize(pagePath)) || pages.has(pagePath)) { try { const pageData = await pageDataUtil.read({ publicDir }, pagePath); return { result: pageData.result, id: pagePath }; } catch (err) { throw new Error(`Error loading a result for the page query in "${pagePath}". Query was not run and no cached result was found.`); } } return undefined; }; const hashPaths = paths => { if (!paths) { return undefined; } return paths.map(path => { if (!path) { return undefined; } return createHash(`sha256`).update(path).digest(`hex`); }); }; /** * Get cached StaticQuery results for components that Gatsby didn't run query yet. * @param {QueryResultsMap} resultsMap Already stored results for queries that don't need to be read from files. * @param {string} directory Root directory of current project. */ const getCachedStaticQueryResults = (resultsMap, directory) => { const cachedStaticQueryResults = new Map(); const { staticQueryComponents } = store.getState(); staticQueryComponents.forEach(staticQueryComponent => { // Don't read from file if results were already passed from query runner if (resultsMap.has(staticQueryComponent.hash)) return; const filePath = path.join(directory, `public`, `static`, `d`, `${staticQueryComponent.hash}.json`); const fileResult = fs.readFileSync(filePath, `utf-8`); if (fileResult === `undefined`) { console.log(`Error loading a result for the StaticQuery in "${staticQueryComponent.componentPath}". Query was not run and no cached result was found.`); return; } cachedStaticQueryResults.set(staticQueryComponent.hash, { result: JSON.parse(fileResult), id: staticQueryComponent.hash }); }); return cachedStaticQueryResults; }; const getRoomNameFromPath = path => `path-${path}`; class WebsocketManager { constructor() { (0, _defineProperty2.default)(this, "pageResults", void 0); (0, _defineProperty2.default)(this, "staticQueryResults", void 0); (0, _defineProperty2.default)(this, "errors", void 0); (0, _defineProperty2.default)(this, "isInitialised", void 0); (0, _defineProperty2.default)(this, "activePaths", void 0); (0, _defineProperty2.default)(this, "programDir", void 0); this.isInitialised = false; this.activePaths = new Set(); this.pageResults = new Map(); this.staticQueryResults = new Map(); this.errors = new Map(); // this.websocket // this.programDir this.init = this.init.bind(this); this.getSocket = this.getSocket.bind(this); this.emitPageData = this.emitPageData.bind(this); this.emitStaticQueryData = this.emitStaticQueryData.bind(this); this.emitError = this.emitError.bind(this); this.connectedClients = 0; } init({ server, directory }) { this.programDir = directory; const cachedStaticQueryResults = getCachedStaticQueryResults(this.staticQueryResults, this.programDir); this.staticQueryResults = new Map([...this.staticQueryResults, ...cachedStaticQueryResults]); this.websocket = require(`socket.io`)(server); this.websocket.on(`connection`, s => { let activePath = null; if (s && s.handshake && s.handshake.headers && s.handshake.headers.referer) { const path = url.parse(s.handshake.headers.referer).path; if (path) { activePath = path; this.activePaths.add(path); } } this.connectedClients += 1; // Send already existing static query results this.staticQueryResults.forEach(result => { this.websocket.send({ type: `staticQueryResult`, payload: result }); }); this.errors.forEach((message, errorID) => { this.websocket.send({ type: `overlayError`, payload: { id: errorID, message } }); }); const leaveRoom = path => { s.leave(getRoomNameFromPath(path)); const leftRoom = this.websocket.sockets.adapter.rooms[getRoomNameFromPath(path)]; if (!leftRoom || leftRoom.length === 0) { this.activePaths.delete(path); } }; const getDataForPath = async path => { if (!this.pageResults.has(path)) { try { const result = await getCachedPageData(path, this.programDir); if (!result) { return; } this.pageResults.set(path, result); } catch (err) { console.log(err.message); return; } } this.websocket.send({ type: `pageQueryResult`, why: `getDataForPath`, payload: this.pageResults.get(path) }); const clientsCount = this.connectedClients; if (clientsCount && clientsCount > 0) { telemetry.trackCli(`WEBSOCKET_PAGE_DATA_UPDATE`, { siteMeasurements: { clientsCount, paths: hashPaths(Array.from(this.activePaths)) } }, { debounce: true }); } }; s.on(`getDataForPath`, getDataForPath); s.on(`registerPath`, path => { s.join(getRoomNameFromPath(path)); activePath = path; this.activePaths.add(path); }); s.on(`disconnect`, s => { leaveRoom(activePath); this.connectedClients -= 1; }); s.on(`unregisterPath`, path => { leaveRoom(path); }); }); this.isInitialised = true; } getSocket() { return this.isInitialised && this.websocket; } emitStaticQueryData(data) { this.staticQueryResults.set(data.id, data); if (this.isInitialised) { this.websocket.send({ type: `staticQueryResult`, payload: data }); const clientsCount = this.connectedClients; if (clientsCount && clientsCount > 0) { telemetry.trackCli(`WEBSOCKET_EMIT_STATIC_PAGE_DATA_UPDATE`, { siteMeasurements: { clientsCount, paths: hashPaths(Array.from(this.activePaths)) } }, { debounce: true }); } } } emitPageData(data) { data.id = normalizePagePath(data.id); this.pageResults.set(data.id, data); if (this.isInitialised) { this.websocket.send({ type: `pageQueryResult`, payload: data }); const clientsCount = this.connectedClients; if (clientsCount && clientsCount > 0) { telemetry.trackCli(`WEBSOCKET_EMIT_PAGE_DATA_UPDATE`, { siteMeasurements: { clientsCount, paths: hashPaths(Array.from(this.activePaths)) } }, { debounce: true }); } } } emitError(id, message) { if (message) { this.errors.set(id, message); } else { this.errors.delete(id); } if (this.isInitialised) { this.websocket.send({ type: `overlayError`, payload: { id, message } }); } } } const manager = new WebsocketManager(); module.exports = manager; //# sourceMappingURL=websocket-manager.js.map