gatsby
Version:
Blazing fast modern site generator for React
208 lines (205 loc) • 7.02 kB
JavaScript
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.websocketManager = exports.WebsocketManager = void 0;
var _redux = require("../redux");
var _internal = require("../redux/actions/internal");
var _gatsbyTelemetry = _interopRequireDefault(require("gatsby-telemetry"));
var _url = _interopRequireDefault(require("url"));
var _crypto = require("crypto");
var _findPageByPath = require("./find-page-by-path");
var _socket = require("socket.io");
var _pageMode = require("./page-mode");
/* eslint-disable no-invalid-this */
function hashPaths(paths) {
return paths.map(path => (0, _crypto.createHash)(`sha256`).update(path).digest(`hex`));
}
class WebsocketManager {
activePaths = new Set();
clients = new Set();
errors = new Map();
staticQueryResults = new Map();
init = ({
server
}) => {
// make typescript happy, else it complained about this.websocket being undefined
const websocket = new _socket.Server(server, {
// we see ping-pong timeouts on gatsby-cloud when socket.io is running for a while
// increasing it should help
// @see https://github.com/socketio/socket.io/issues/3259#issuecomment-448058937
pingTimeout: 30000,
// whitelist all (https://github.com/expressjs/cors#configuration-options)
cors: {
origin: true
},
cookie: true
});
this.websocket = websocket;
const updateServerActivePaths = () => {
const serverActivePaths = new Set();
for (const client of this.clients) {
if (client.activePath) {
serverActivePaths.add(client.activePath);
}
}
this.activePaths = serverActivePaths;
};
websocket.on(`connection`, socket => {
var _socket$handshake, _socket$handshake$hea;
const clientInfo = {
activePath: null,
socket
};
this.clients.add(clientInfo);
const setActivePath = (newActivePath, fallbackTo404 = false) => {
let activePagePath = null;
if (newActivePath) {
const page = (0, _findPageByPath.findPageByPath)(_redux.store.getState(), newActivePath, fallbackTo404);
if (page) {
// when it's SSR we don't want to return the page path but the actualy url used,
// this is necessary when matchPaths are used.
if ((0, _pageMode.getPageMode)(page) === `SSR`) {
activePagePath = newActivePath;
} else {
activePagePath = page.path;
}
}
}
clientInfo.activePath = activePagePath;
updateServerActivePaths();
};
if (socket !== null && socket !== void 0 && (_socket$handshake = socket.handshake) !== null && _socket$handshake !== void 0 && (_socket$handshake$hea = _socket$handshake.headers) !== null && _socket$handshake$hea !== void 0 && _socket$handshake$hea.referer) {
const path = _url.default.parse(socket.handshake.headers.referer).path;
setActivePath(path, true);
}
this.errors.forEach((message, errorID) => {
socket.send({
type: `overlayError`,
payload: {
id: errorID,
message
}
});
});
socket.on(`registerPath`, path => {
setActivePath(path, true);
});
socket.on(`disconnect`, () => {
setActivePath(null);
this.clients.delete(clientInfo);
});
socket.on(`unregisterPath`, _path => {
setActivePath(null);
});
});
if (process.env.GATSBY_QUERY_ON_DEMAND) {
// page-data marked stale due to dirty query tracking
const boundEmitStalePageDataPathsFromDirtyQueryTracking = this.emitStalePageDataPathsFromDirtyQueryTracking.bind(this);
_redux.emitter.on(`CREATE_PAGE`, boundEmitStalePageDataPathsFromDirtyQueryTracking);
_redux.emitter.on(`CREATE_NODE`, boundEmitStalePageDataPathsFromDirtyQueryTracking);
_redux.emitter.on(`DELETE_NODE`, boundEmitStalePageDataPathsFromDirtyQueryTracking);
_redux.emitter.on(`QUERY_EXTRACTED`, boundEmitStalePageDataPathsFromDirtyQueryTracking);
}
// page-data marked stale due to static query hashes change
_redux.emitter.on(`ADD_PENDING_TEMPLATE_DATA_WRITE`, this.emitStalePageDataPathsFromStaticQueriesAssignment.bind(this));
return websocket;
};
getSocket = () => this.websocket;
emitStaticQueryData = data => {
this.staticQueryResults.set(data.id, data);
if (this.websocket) {
this.websocket.send({
type: `staticQueryResult`,
payload: data
});
if (this.clients.size > 0) {
_gatsbyTelemetry.default.trackCli(`WEBSOCKET_EMIT_STATIC_PAGE_DATA_UPDATE`, {
siteMeasurements: {
clientsCount: this.clients.size,
paths: hashPaths(Array.from(this.activePaths))
}
}, {
debounce: true
});
}
}
};
emitPageData = data => {
if (this.websocket) {
this.websocket.send({
type: `pageQueryResult`,
payload: data
});
if (this.clients.size > 0) {
_gatsbyTelemetry.default.trackCli(`WEBSOCKET_EMIT_PAGE_DATA_UPDATE`, {
siteMeasurements: {
clientsCount: this.clients.size,
paths: hashPaths(Array.from(this.activePaths))
}
}, {
debounce: true
});
}
}
};
emitSliceData = data => {
if (this.websocket) {
this.websocket.send({
type: `sliceQueryResult`,
payload: data
});
}
};
emitError = (id, message) => {
if (message) {
this.errors.set(id, message);
} else {
this.errors.delete(id);
}
if (this.websocket) {
this.websocket.send({
type: `overlayError`,
payload: {
id,
message
}
});
}
};
emitStalePageDataPathsFromDirtyQueryTracking() {
const dirtyQueries = _redux.store.getState().queries.dirtyQueriesListToEmitViaWebsocket;
if (this.emitStalePageDataPaths(dirtyQueries)) {
_redux.store.dispatch((0, _internal.clearDirtyQueriesListToEmitViaWebsocket)());
}
}
emitStalePageDataPathsFromStaticQueriesAssignment(pendingTemplateDataWrite) {
this.emitStalePageDataPaths(Array.from(pendingTemplateDataWrite.payload.pages));
}
emitStalePageDataPaths(stalePageDataPaths) {
if (stalePageDataPaths.length > 0) {
if (this.websocket) {
this.websocket.send({
type: `stalePageData`,
payload: {
stalePageDataPaths
}
});
return true;
}
}
return false;
}
emitStaleServerData() {
if (this.websocket) {
this.websocket.send({
type: `staleServerData`
});
return true;
}
return false;
}
}
exports.WebsocketManager = WebsocketManager;
const websocketManager = new WebsocketManager();
exports.websocketManager = websocketManager;
//# sourceMappingURL=websocket-manager.js.map
;