Skip to content

Commit

Permalink
Make autoreload survive laptop suspend
Browse files Browse the repository at this point in the history
Also simplify the implementation for softening the grey curtain when
autoreload is in progress (only applies to Shiny for Python)
  • Loading branch information
jcheng5 committed Oct 17, 2023
1 parent b22aedb commit 26dad15
Show file tree
Hide file tree
Showing 11 changed files with 96 additions and 65 deletions.
3 changes: 2 additions & 1 deletion inst/www/shared/shiny-autoreload.js

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions inst/www/shared/shiny-autoreload.js.map

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions inst/www/shared/shiny.js
Original file line number Diff line number Diff line change
Expand Up @@ -17905,15 +17905,16 @@
}, _callee2);
})));
};
socket.onclose = function() {
socket.onclose = function(e) {
var restarting = e.code === 1012;
if (hasOpened) {
(0, import_jquery38.default)(document).trigger({
type: "shiny:disconnected",
socket: socket
});
_this.$notifyDisconnected();
}
_this.onDisconnected();
_this.onDisconnected(restarting);
_this.$removeSocket();
};
return socket;
Expand Down Expand Up @@ -17993,10 +17994,11 @@
}, {
key: "onDisconnected",
value: function onDisconnected() {
var $overlay = (0, import_jquery38.default)("#shiny-disconnected-overlay");
if ($overlay.length === 0) {
var reloading = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : false;
if ((0, import_jquery38.default)("#shiny-disconnected-overlay").length === 0) {
(0, import_jquery38.default)(document.body).append('<div id="shiny-disconnected-overlay"></div>');
}
(0, import_jquery38.default)("#shiny-disconnected-overlay").toggleClass("reloading", reloading);
if (this.$allowReconnect === true && this.$socket.allowReconnect === true || this.$allowReconnect === "force") {
var delay = this.reconnectDelay.next();
showReconnectDialog(delay);
Expand Down
4 changes: 2 additions & 2 deletions inst/www/shared/shiny.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion inst/www/shared/shiny.min.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion inst/www/shared/shiny.min.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions inst/www/shared/shiny.min.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion inst/www/shared/shiny_scss/shiny.scss
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ pre.shiny-text-output {
pointer-events: none;
}

.autoreload:not(.autoreload-closed) #shiny-disconnected-overlay {
#shiny-disconnected-overlay.reloading {
opacity: 0;
animation: fadeIn 250ms forwards;
animation-delay: 1s;
Expand Down
110 changes: 69 additions & 41 deletions srcts/extras/shiny-autoreload.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,84 @@
/* eslint-disable unicorn/filename-case */
const protocol = (window.location.protocol === "https:") ? "wss:" : "ws:";

const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
// Add trailing slash to path, if necessary, before appending "autoreload"
const defaultPath = window.location.pathname.replace(/\/?$/, "/") + "autoreload/";
const defaultPath =
window.location.pathname.replace(/\/?$/, "/") + "autoreload/";
const defaultUrl = `${protocol}//${window.location.host}${defaultPath}`;

// By default, use the defaultUrl. But if there's a data-ws-url attribute on our
// <script> tag, use that instead.
const wsUrl = document.currentScript.dataset.wsUrl || defaultUrl;
const ws = new WebSocket(wsUrl);

enum Status {
// Trying to reach the autoreload server
Connecting = "connecting",
// Connected to the autoreload server, just waiting for it to tell us to
// reload
Ready = "ready",
// The server has asked us to reload and we're in the process of doing so
Reloading = "reloading",
// The connection to the server was broken. TODO: should we try reconnecting?
Closed = "closed",
}
const wsUrl = document.currentScript?.dataset?.wsUrl || defaultUrl;

// Put a class on <html> indicating the current state of the autoreload channel
function setAutoreloadStatus(status: Status) {
for (const s of Object.values(Status)) {
if (status === s) {
document.documentElement.classList.add(`autoreload-${s}`);
} else {
document.documentElement.classList.remove(`autoreload-${s}`);
}
}
}
/**
* Connects to an autoreload URL and waits for the server to tell us what to do.
*
* @param url The ws:// or wss:// URL to connect to.
* @returns true if the server requests a reload, or false if the connection was
* successfully established but then closed without the server requesting a
* reload
* @throws A nondescript error if the connection fails to be established.
*/
async function autoreload(url: string): Promise<boolean> {
const ws = new WebSocket(url);

let success = false;

// Also unconditionally add the "autoreload" class to <html>, so it's easy to
// tell whether autoreload is even enabled
document.documentElement.classList.add("autoreload");
return new Promise((resolve, reject) => {
ws.onopen = () => {
success = true;
};

setAutoreloadStatus(Status.Connecting);
ws.onerror = (err) => {
reject(err);
};

ws.onopen = function(event) {
setAutoreloadStatus(Status.Ready);
};
ws.onclose = () => {
if (!success) {
reject(new Error("WebSocket connection failed"));
} else {
resolve(false);
}
};

ws.onclose = function(event) {
setAutoreloadStatus(Status.Closed);
};
ws.onmessage = function (event) {
if (event.data === "autoreload") {
resolve(true);
}
};
});
}

async function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

ws.onmessage = function (event) {
if (event.data === "autoreload") {
window.location.reload();
setAutoreloadStatus(Status.Reloading);
async function initialize() {
while (true) {
try {
if (await autoreload(wsUrl)) {
window.location.reload();
return;
}
} catch (err) {
// It's possible for the autoreload() call to throw. If it does, that
// means we tried but failed to connect to the autoreload socket. This
// probably means that the entire `shiny run --reload` process was
// restarted. As of today, the autoreload websocket port number is
// randomly chosen for each `shiny run --reload` process, so it's
// impossible for us to recover.
console.debug("Giving up on autoreload");
return;
}
// If we get here, the connection to the autoreload server was
// successful but then got broken. Wait for a second, and then
// try to re-establish the connection.
await sleep(1000);
}
};
}

initialize().catch((err) => {
console.error(err);
});

export {};
12 changes: 6 additions & 6 deletions srcts/src/shiny/shinyapp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,8 @@ class ShinyApp {
};
// Called when a successfully-opened websocket is closed, or when an
// attempt to open a connection fails.
socket.onclose = () => {
socket.onclose = (e) => {
const restarting = e.code === 1012; // Uvicorn sets this code when autoreloading
// These things are needed only if we've successfully opened the
// websocket.
if (hasOpened) {
Expand All @@ -257,7 +258,7 @@ class ShinyApp {
this.$notifyDisconnected();
}

this.onDisconnected(); // Must be run before this.$removeSocket()
this.onDisconnected(restarting); // Must be run before this.$removeSocket()
this.$removeSocket();
};
return socket;
Expand Down Expand Up @@ -333,13 +334,12 @@ class ShinyApp {
};
})();

onDisconnected(): void {
onDisconnected(reloading = false): void {
// Add gray-out overlay, if not already present
const $overlay = $("#shiny-disconnected-overlay");

if ($overlay.length === 0) {
if ($("#shiny-disconnected-overlay").length === 0) {
$(document.body).append('<div id="shiny-disconnected-overlay"></div>');
}
$("#shiny-disconnected-overlay").toggleClass("reloading", reloading);

// To try a reconnect, both the app (this.$allowReconnect) and the
// server (this.$socket.allowReconnect) must allow reconnections, or
Expand Down
2 changes: 1 addition & 1 deletion srcts/types/src/shiny/shinyapp.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ declare class ShinyApp {
next: () => number;
reset: () => void;
};
onDisconnected(): void;
onDisconnected(reloading?: boolean): void;
onConnected(): void;
makeRequest(method: string, args: unknown[], onSuccess: OnSuccessRequest, onError: OnErrorRequest, blobs: Array<ArrayBuffer | Blob | string> | undefined): void;
$sendMsg(msg: MessageValue): void;
Expand Down

0 comments on commit 26dad15

Please sign in to comment.