EVOLUTION-MANAGER
Edit File: main.js
"use strict"; const assert = require("assert"); const fixupUrl = require("./fixup-url"); const log = require("./log"); const token = require("./decorators/token"); const subapp = require("./subapp"); const extendSession = require("./decorators/extend-session"); const reconnect = require("./decorators/reconnect"); const disconnect = require("./decorators/disconnect"); const multiplex = require("./decorators/multiplex"); const workerId = require("./decorators/worker-id"); const sockjs = require("./sockjs"); const PromisedConnection = require("./promised-connection"); const ConnectionContext = require("./decorators/connection-context"); const ReconnectUI = require("./reconnect-ui"); const ui = require("./ui"); const ProtocolChooser = require("./protocol-chooser"); /* Connection factories: - SockJS (reconnect-aware) - Subapp Connection factory decorators: - WorkerId maintainer (reconnect-aware) - Token adder - Reconnector (requires underlying connections to be reconnect-aware) - MultiplexClient SSOS config: Primary app: SockJS + Reconnector + MultiplexClient Subapp: Subapp SSP/RSC config: Primary app: SockJS + WorkerId + Token + Reconnector + MultiplexClient Subapp: Subapp */ let reconnectUI = new ReconnectUI(); /** * options = { * debugging: false, * extendSession: false, * fixupInternalLinks: false, * reconnect: false, * subappTag: false, * token: false, * workerId: false, * * reconnectTimeout: 15000, * connectErrorDelay: 500, * disableProtocols: [], * transportDebugging: false * } * */ function initSession(shiny, options, shinyServer) { if (subapp.isSubApp()) { shiny.createSocket = () => { return subapp.createSocket(); }; } else { // Not a subapp ProtocolChooser.init(shinyServer, options.disableProtocols); let factory = sockjs.createFactory(ProtocolChooser, options); if (options.workerId) { factory = workerId.decorate(factory, options); } if (options.token) { factory = token.decorate(factory, options); } if (options.reconnect) { factory = reconnect.decorate(factory, options); } else { factory = disconnect.decorate(factory, options); } if (options.extendSession) { factory = extendSession.decorate(factory, options); } factory = multiplex.decorate(factory, options); // Register the connection with Shiny.createSocket, etc. shiny.createSocket = () => { let url = location.protocol + "//" + location.host + location.pathname.replace(/\/[^/]*$/, ""); url += "/__sockjs__/"; reconnectUI.hide(); let ctx = new ConnectionContext(); let doReconnectHandler = () => { ctx.emit("do-reconnect"); }; reconnectUI.on("do-reconnect", doReconnectHandler); if (reconnectUI.listenerCount("do-reconnect") > 1) { log("do-reconnect handlers are leaking!"); } ctx.on("reconnect-schedule", delay => { reconnectUI.showCountdown(delay); }); ctx.on("reconnect-attempt", () => { reconnectUI.showAttempting(); }); ctx.on("reconnect-success", () => { reconnectUI.hide(); }); let onDisconnected = () => { reconnectUI.removeListener("do-reconnect", doReconnectHandler); reconnectUI.showDisconnected(); }; ctx.on("reconnect-failure", onDisconnected); ctx.on("disconnect", onDisconnected); let pc = new PromisedConnection(); factory(url, ctx, (err, conn) => { pc.resolve(err, conn); }); assert(ctx.multiplexClient); shinyServer.multiplexer = ctx.multiplexClient; // Signal to Shiny 0.14 and above that a Shiny-level reconnection (i.e. // automatically starting a new session) is permitted. pc.allowReconnect = true; ctx.on("disconnect", e => { // e here is the websocket/SockJS close event. // Don't allow a Shiny-level reconnection (new session) if we close // cleanly; this is an indication that the server wanted us to close // and stay closed (e.g. session idle timeout). // // But in some cases, even a clean close should allow reconnect; these // are cases where the server couldn't service our existing session // but wouldn't mind us starting a new one. E.g.: robust id not found // or expired. The server indicates this by sending a close code in // the 47xx range. if (e.code && e.code >= 4700 && e.code < 4800) { pc.allowReconnect = true; } else { pc.allowReconnect = false; } }); return pc; }; } } global.preShinyInit = function(options) { if (options.fixupInternalLinks && !subapp.isSubApp()) { global.jQuery(() => { fixupInternalLinks(); }); } if (!global.Shiny) { // Don't do anything if this isn't even a Shiny URL return; } global.ShinyServer = global.ShinyServer || {}; initSession(global.Shiny, options, global.ShinyServer); if (!subapp.isSubApp()) { /*eslint-disable no-console*/ global.Shiny.oncustommessage = function(message) { if (message.license) ui.onLicense(global.Shiny, message.license); if (message.credentials) ui.onLoggedIn(message.credentials); if (typeof message === "string" && console.log) console.log(message); // Legacy format if (message.alert && console.log) console.log(message.alert); if (message.console && console.log) console.log(message.console); }; /*eslint-enable no-console*/ } }; global.fixupInternalLinks = fixupInternalLinks; function fixupInternalLinks() { global.jQuery("body").on("click", "a", function(ev) { // We don't scrub links from subapps because a.) We need to make sure that // everything (even relative links) stick to the same worker, as this app // doesn't exist on another worker, and b.) because we don't care about the // side-effect of creating a big mess in the URL bar, since it's just an // iframe and won't be visible anyway. assert(!subapp.isSubApp()); // Bootstrap collapse/tab links should not have their href touched if (ev.currentTarget.hasAttribute("data-toggle")) { return; } // setting /any/ value to ev.target.href (even assigning it to itself) would // have the side-effect of creating a real value in that property, even if // one shouldn't exist if (ev.currentTarget.href === null || !ev.currentTarget.href){ return; } let href = fixupUrl(ev.currentTarget.href, global.location); if (href === ev.currentTarget.href) { // Must not have been a relative URL, or base href isn't in effect. return; } ev.currentTarget.href = href; }); } global.Shiny.createSocket = () => { throw new Error("shiny-server-client was loaded but preShinyInit() was not called"); };