@jsenv/server 17.6.4 → 17.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -94,10 +94,20 @@
|
|
|
94
94
|
${css}
|
|
95
95
|
</style>
|
|
96
96
|
<dialog>
|
|
97
|
-
<p>Connection with server is lost
|
|
98
|
-
<button autofocus name="
|
|
97
|
+
<p>Connection with server is lost, waiting for it to come back...</p>
|
|
98
|
+
<button autofocus name="retry">Retry now</button>
|
|
99
99
|
</dialog>`;
|
|
100
100
|
|
|
101
|
+
// The peer is a dev server being restarted, not a flaky network: it can be down
|
|
102
|
+
// for the length of an "npm install" and there is no attempt count past which
|
|
103
|
+
// giving up would be correct. Attempts space out to RETRY_AFTER_MAX_MS and never
|
|
104
|
+
// stop; the dialog is a status the page can leave and come back to, so closing
|
|
105
|
+
// it keeps the loop running and the page still reloads once the server answers.
|
|
106
|
+
const RETRY_AFTER_MS = 500;
|
|
107
|
+
const RETRY_AFTER_MAX_MS = 5000;
|
|
108
|
+
// Most restarts complete before this, so a quick one goes unnoticed.
|
|
109
|
+
const DIALOG_AFTER_MS = 3000;
|
|
110
|
+
|
|
101
111
|
class JsenvAutoreloadOnServerRestart extends HTMLElement {
|
|
102
112
|
constructor({ url } = {}) {
|
|
103
113
|
super();
|
|
@@ -105,59 +115,83 @@
|
|
|
105
115
|
root.innerHTML = html;
|
|
106
116
|
|
|
107
117
|
const dialog = root.querySelector("dialog");
|
|
108
|
-
const
|
|
118
|
+
const retryButton = dialog.querySelector('button[name="retry"]');
|
|
109
119
|
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
websocket
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
const connect = () => {
|
|
121
|
+
const websocket = new WebSocket(url);
|
|
122
|
+
return new Promise((resolve) => {
|
|
123
|
+
websocket.onerror = () => {
|
|
124
|
+
resolve(null);
|
|
125
|
+
};
|
|
126
|
+
websocket.onopen = () => {
|
|
127
|
+
resolve(websocket);
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
let retryAfter = RETRY_AFTER_MS;
|
|
133
|
+
let retryTimeout = null;
|
|
134
|
+
let dialogTimeout = null;
|
|
135
|
+
|
|
136
|
+
const tryToReconnect = async () => {
|
|
137
|
+
retryTimeout = null;
|
|
138
|
+
retryButton.disabled = true;
|
|
139
|
+
const websocket = await connect();
|
|
140
|
+
if (websocket) {
|
|
141
|
+
clearTimeout(dialogTimeout);
|
|
142
|
+
console.info("reconnected to server, reloading the page...");
|
|
143
|
+
window.location.reload(true);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
retryButton.disabled = false;
|
|
147
|
+
retryTimeout = setTimeout(tryToReconnect, retryAfter);
|
|
148
|
+
const nextRetryAfter = retryAfter * 2;
|
|
149
|
+
retryAfter =
|
|
150
|
+
nextRetryAfter > RETRY_AFTER_MAX_MS
|
|
151
|
+
? RETRY_AFTER_MAX_MS
|
|
152
|
+
: nextRetryAfter;
|
|
153
|
+
};
|
|
123
154
|
|
|
155
|
+
const retryNow = () => {
|
|
156
|
+
if (retryTimeout === null) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
clearTimeout(retryTimeout);
|
|
160
|
+
retryAfter = RETRY_AFTER_MS;
|
|
161
|
+
tryToReconnect();
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const waitForServerToComeBack = () => {
|
|
165
|
+
console.info("connection to server lost, trying to reconnect");
|
|
166
|
+
dialogTimeout = setTimeout(() => {
|
|
167
|
+
dialogTimeout = null;
|
|
168
|
+
dialog.showModal();
|
|
169
|
+
}, DIALOG_AFTER_MS);
|
|
170
|
+
tryToReconnect();
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
retryButton.onclick = () => {
|
|
174
|
+
retryNow();
|
|
175
|
+
};
|
|
176
|
+
// A tab coming back to the foreground, or the network coming back, are the
|
|
177
|
+
// moments the server is most likely to be up already; waiting out the
|
|
178
|
+
// backoff then shows a detached page for no reason.
|
|
179
|
+
window.addEventListener("online", retryNow);
|
|
180
|
+
window.addEventListener("focus", retryNow);
|
|
181
|
+
document.addEventListener("visibilitychange", () => {
|
|
182
|
+
if (document.visibilityState === "visible") {
|
|
183
|
+
retryNow();
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
(async () => {
|
|
124
188
|
const initialWebSocket = await connect();
|
|
125
189
|
if (!initialWebSocket) {
|
|
190
|
+
waitForServerToComeBack();
|
|
126
191
|
return;
|
|
127
192
|
}
|
|
128
|
-
initialWebSocket.onclose =
|
|
129
|
-
|
|
130
|
-
const retry = async () => {
|
|
131
|
-
let attemptCount = 0;
|
|
132
|
-
let timeToWait = 100;
|
|
133
|
-
const tryToReconnect = async () => {
|
|
134
|
-
const newWebsocket = await connect();
|
|
135
|
-
if (newWebsocket) {
|
|
136
|
-
dialog.close();
|
|
137
|
-
console.info("reconnected to server, reloading the page...");
|
|
138
|
-
window.location.reload(true);
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
attemptCount++;
|
|
142
|
-
if (attemptCount === 10) {
|
|
143
|
-
dialog.showModal();
|
|
144
|
-
reconnectButton.onclick = async () => {
|
|
145
|
-
reconnectButton.disabled = true;
|
|
146
|
-
const previousContext = reconnectButton.textContent;
|
|
147
|
-
reconnectButton.textContent = "Retrying...";
|
|
148
|
-
await retry();
|
|
149
|
-
reconnectButton.textContent = previousContext;
|
|
150
|
-
reconnectButton.disabled = false;
|
|
151
|
-
};
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
timeToWait += 100;
|
|
155
|
-
await new Promise((resolve) => setTimeout(resolve, timeToWait));
|
|
156
|
-
await tryToReconnect();
|
|
157
|
-
};
|
|
158
|
-
await tryToReconnect();
|
|
159
|
-
};
|
|
160
|
-
retry();
|
|
193
|
+
initialWebSocket.onclose = () => {
|
|
194
|
+
waitForServerToComeBack();
|
|
161
195
|
};
|
|
162
196
|
})();
|
|
163
197
|
}
|
|
@@ -275,10 +275,20 @@
|
|
|
275
275
|
${css}
|
|
276
276
|
</style>
|
|
277
277
|
<dialog>
|
|
278
|
-
<p>Connection with server is lost
|
|
279
|
-
<button autofocus name="
|
|
278
|
+
<p>Connection with server is lost, waiting for it to come back...</p>
|
|
279
|
+
<button autofocus name="retry">Retry now</button>
|
|
280
280
|
</dialog>`;
|
|
281
281
|
|
|
282
|
+
// The peer is a dev server being restarted, not a flaky network: it can be down
|
|
283
|
+
// for the length of an "npm install" and there is no attempt count past which
|
|
284
|
+
// giving up would be correct. Attempts space out to RETRY_AFTER_MAX_MS and never
|
|
285
|
+
// stop; the dialog is a status the page can leave and come back to, so closing
|
|
286
|
+
// it keeps the loop running and the page still reloads once the server answers.
|
|
287
|
+
const RETRY_AFTER_MS = 500;
|
|
288
|
+
const RETRY_AFTER_MAX_MS = 5000;
|
|
289
|
+
// Most restarts complete before this, so a quick one goes unnoticed.
|
|
290
|
+
const DIALOG_AFTER_MS = 3000;
|
|
291
|
+
|
|
282
292
|
class JsenvAutoreloadOnServerRestart extends HTMLElement {
|
|
283
293
|
constructor({ url } = {}) {
|
|
284
294
|
super();
|
|
@@ -286,59 +296,83 @@
|
|
|
286
296
|
root.innerHTML = html;
|
|
287
297
|
|
|
288
298
|
const dialog = root.querySelector("dialog");
|
|
289
|
-
const
|
|
299
|
+
const retryButton = dialog.querySelector('button[name="retry"]');
|
|
290
300
|
|
|
291
|
-
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
websocket
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
301
|
+
const connect = () => {
|
|
302
|
+
const websocket = new WebSocket(url);
|
|
303
|
+
return new Promise((resolve) => {
|
|
304
|
+
websocket.onerror = () => {
|
|
305
|
+
resolve(null);
|
|
306
|
+
};
|
|
307
|
+
websocket.onopen = () => {
|
|
308
|
+
resolve(websocket);
|
|
309
|
+
};
|
|
310
|
+
});
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
let retryAfter = RETRY_AFTER_MS;
|
|
314
|
+
let retryTimeout = null;
|
|
315
|
+
let dialogTimeout = null;
|
|
316
|
+
|
|
317
|
+
const tryToReconnect = async () => {
|
|
318
|
+
retryTimeout = null;
|
|
319
|
+
retryButton.disabled = true;
|
|
320
|
+
const websocket = await connect();
|
|
321
|
+
if (websocket) {
|
|
322
|
+
clearTimeout(dialogTimeout);
|
|
323
|
+
console.info("reconnected to server, reloading the page...");
|
|
324
|
+
window.location.reload(true);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
retryButton.disabled = false;
|
|
328
|
+
retryTimeout = setTimeout(tryToReconnect, retryAfter);
|
|
329
|
+
const nextRetryAfter = retryAfter * 2;
|
|
330
|
+
retryAfter =
|
|
331
|
+
nextRetryAfter > RETRY_AFTER_MAX_MS
|
|
332
|
+
? RETRY_AFTER_MAX_MS
|
|
333
|
+
: nextRetryAfter;
|
|
334
|
+
};
|
|
304
335
|
|
|
336
|
+
const retryNow = () => {
|
|
337
|
+
if (retryTimeout === null) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
clearTimeout(retryTimeout);
|
|
341
|
+
retryAfter = RETRY_AFTER_MS;
|
|
342
|
+
tryToReconnect();
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const waitForServerToComeBack = () => {
|
|
346
|
+
console.info("connection to server lost, trying to reconnect");
|
|
347
|
+
dialogTimeout = setTimeout(() => {
|
|
348
|
+
dialogTimeout = null;
|
|
349
|
+
dialog.showModal();
|
|
350
|
+
}, DIALOG_AFTER_MS);
|
|
351
|
+
tryToReconnect();
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
retryButton.onclick = () => {
|
|
355
|
+
retryNow();
|
|
356
|
+
};
|
|
357
|
+
// A tab coming back to the foreground, or the network coming back, are the
|
|
358
|
+
// moments the server is most likely to be up already; waiting out the
|
|
359
|
+
// backoff then shows a detached page for no reason.
|
|
360
|
+
window.addEventListener("online", retryNow);
|
|
361
|
+
window.addEventListener("focus", retryNow);
|
|
362
|
+
document.addEventListener("visibilitychange", () => {
|
|
363
|
+
if (document.visibilityState === "visible") {
|
|
364
|
+
retryNow();
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
(async () => {
|
|
305
369
|
const initialWebSocket = await connect();
|
|
306
370
|
if (!initialWebSocket) {
|
|
371
|
+
waitForServerToComeBack();
|
|
307
372
|
return;
|
|
308
373
|
}
|
|
309
|
-
initialWebSocket.onclose =
|
|
310
|
-
|
|
311
|
-
const retry = async () => {
|
|
312
|
-
let attemptCount = 0;
|
|
313
|
-
let timeToWait = 100;
|
|
314
|
-
const tryToReconnect = async () => {
|
|
315
|
-
const newWebsocket = await connect();
|
|
316
|
-
if (newWebsocket) {
|
|
317
|
-
dialog.close();
|
|
318
|
-
console.info("reconnected to server, reloading the page...");
|
|
319
|
-
window.location.reload(true);
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
attemptCount++;
|
|
323
|
-
if (attemptCount === 10) {
|
|
324
|
-
dialog.showModal();
|
|
325
|
-
reconnectButton.onclick = async () => {
|
|
326
|
-
reconnectButton.disabled = true;
|
|
327
|
-
const previousContext = reconnectButton.textContent;
|
|
328
|
-
reconnectButton.textContent = "Retrying...";
|
|
329
|
-
await retry();
|
|
330
|
-
reconnectButton.textContent = previousContext;
|
|
331
|
-
reconnectButton.disabled = false;
|
|
332
|
-
};
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
timeToWait += 100;
|
|
336
|
-
await new Promise((resolve) => setTimeout(resolve, timeToWait));
|
|
337
|
-
await tryToReconnect();
|
|
338
|
-
};
|
|
339
|
-
await tryToReconnect();
|
|
340
|
-
};
|
|
341
|
-
retry();
|
|
374
|
+
initialWebSocket.onclose = () => {
|
|
375
|
+
waitForServerToComeBack();
|
|
342
376
|
};
|
|
343
377
|
})();
|
|
344
378
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/server",
|
|
3
|
-
"version": "17.6.
|
|
3
|
+
"version": "17.6.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A modern Node.js HTTP server with declarative routing, content negotiation, and WebSocket support",
|
|
6
6
|
"repository": {
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
"@jsenv/utils": "../../tooling/utils",
|
|
57
57
|
"eventsource": "5.1.1",
|
|
58
58
|
"formidable": "3.5.4",
|
|
59
|
-
"playwright": "1.
|
|
60
|
-
"undici": "8.10.
|
|
59
|
+
"playwright": "1.63.0",
|
|
60
|
+
"undici": "8.10.2",
|
|
61
61
|
"ws": "8.21.3"
|
|
62
62
|
},
|
|
63
63
|
"publishConfig": {
|
|
@@ -26,10 +26,20 @@ const html = /* html */ `<style>
|
|
|
26
26
|
${css}
|
|
27
27
|
</style>
|
|
28
28
|
<dialog>
|
|
29
|
-
<p>Connection with server is lost
|
|
30
|
-
<button autofocus name="
|
|
29
|
+
<p>Connection with server is lost, waiting for it to come back...</p>
|
|
30
|
+
<button autofocus name="retry">Retry now</button>
|
|
31
31
|
</dialog>`;
|
|
32
32
|
|
|
33
|
+
// The peer is a dev server being restarted, not a flaky network: it can be down
|
|
34
|
+
// for the length of an "npm install" and there is no attempt count past which
|
|
35
|
+
// giving up would be correct. Attempts space out to RETRY_AFTER_MAX_MS and never
|
|
36
|
+
// stop; the dialog is a status the page can leave and come back to, so closing
|
|
37
|
+
// it keeps the loop running and the page still reloads once the server answers.
|
|
38
|
+
const RETRY_AFTER_MS = 500;
|
|
39
|
+
const RETRY_AFTER_MAX_MS = 5000;
|
|
40
|
+
// Most restarts complete before this, so a quick one goes unnoticed.
|
|
41
|
+
const DIALOG_AFTER_MS = 3000;
|
|
42
|
+
|
|
33
43
|
class JsenvAutoreloadOnServerRestart extends HTMLElement {
|
|
34
44
|
constructor({ url } = {}) {
|
|
35
45
|
super();
|
|
@@ -37,59 +47,83 @@ class JsenvAutoreloadOnServerRestart extends HTMLElement {
|
|
|
37
47
|
root.innerHTML = html;
|
|
38
48
|
|
|
39
49
|
const dialog = root.querySelector("dialog");
|
|
40
|
-
const
|
|
50
|
+
const retryButton = dialog.querySelector('button[name="retry"]');
|
|
41
51
|
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
websocket
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
const connect = () => {
|
|
53
|
+
const websocket = new WebSocket(url);
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
websocket.onerror = () => {
|
|
56
|
+
resolve(null);
|
|
57
|
+
};
|
|
58
|
+
websocket.onopen = () => {
|
|
59
|
+
resolve(websocket);
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
let retryAfter = RETRY_AFTER_MS;
|
|
65
|
+
let retryTimeout = null;
|
|
66
|
+
let dialogTimeout = null;
|
|
67
|
+
|
|
68
|
+
const tryToReconnect = async () => {
|
|
69
|
+
retryTimeout = null;
|
|
70
|
+
retryButton.disabled = true;
|
|
71
|
+
const websocket = await connect();
|
|
72
|
+
if (websocket) {
|
|
73
|
+
clearTimeout(dialogTimeout);
|
|
74
|
+
console.info("reconnected to server, reloading the page...");
|
|
75
|
+
window.location.reload(true);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
retryButton.disabled = false;
|
|
79
|
+
retryTimeout = setTimeout(tryToReconnect, retryAfter);
|
|
80
|
+
const nextRetryAfter = retryAfter * 2;
|
|
81
|
+
retryAfter =
|
|
82
|
+
nextRetryAfter > RETRY_AFTER_MAX_MS
|
|
83
|
+
? RETRY_AFTER_MAX_MS
|
|
84
|
+
: nextRetryAfter;
|
|
85
|
+
};
|
|
55
86
|
|
|
87
|
+
const retryNow = () => {
|
|
88
|
+
if (retryTimeout === null) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
clearTimeout(retryTimeout);
|
|
92
|
+
retryAfter = RETRY_AFTER_MS;
|
|
93
|
+
tryToReconnect();
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const waitForServerToComeBack = () => {
|
|
97
|
+
console.info("connection to server lost, trying to reconnect");
|
|
98
|
+
dialogTimeout = setTimeout(() => {
|
|
99
|
+
dialogTimeout = null;
|
|
100
|
+
dialog.showModal();
|
|
101
|
+
}, DIALOG_AFTER_MS);
|
|
102
|
+
tryToReconnect();
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
retryButton.onclick = () => {
|
|
106
|
+
retryNow();
|
|
107
|
+
};
|
|
108
|
+
// A tab coming back to the foreground, or the network coming back, are the
|
|
109
|
+
// moments the server is most likely to be up already; waiting out the
|
|
110
|
+
// backoff then shows a detached page for no reason.
|
|
111
|
+
window.addEventListener("online", retryNow);
|
|
112
|
+
window.addEventListener("focus", retryNow);
|
|
113
|
+
document.addEventListener("visibilitychange", () => {
|
|
114
|
+
if (document.visibilityState === "visible") {
|
|
115
|
+
retryNow();
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
(async () => {
|
|
56
120
|
const initialWebSocket = await connect();
|
|
57
121
|
if (!initialWebSocket) {
|
|
122
|
+
waitForServerToComeBack();
|
|
58
123
|
return;
|
|
59
124
|
}
|
|
60
|
-
initialWebSocket.onclose =
|
|
61
|
-
|
|
62
|
-
const retry = async () => {
|
|
63
|
-
let attemptCount = 0;
|
|
64
|
-
let timeToWait = 100;
|
|
65
|
-
const tryToReconnect = async () => {
|
|
66
|
-
const newWebsocket = await connect();
|
|
67
|
-
if (newWebsocket) {
|
|
68
|
-
dialog.close();
|
|
69
|
-
console.info("reconnected to server, reloading the page...");
|
|
70
|
-
window.location.reload(true);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
attemptCount++;
|
|
74
|
-
if (attemptCount === 10) {
|
|
75
|
-
dialog.showModal();
|
|
76
|
-
reconnectButton.onclick = async () => {
|
|
77
|
-
reconnectButton.disabled = true;
|
|
78
|
-
const previousContext = reconnectButton.textContent;
|
|
79
|
-
reconnectButton.textContent = "Retrying...";
|
|
80
|
-
await retry();
|
|
81
|
-
reconnectButton.textContent = previousContext;
|
|
82
|
-
reconnectButton.disabled = false;
|
|
83
|
-
};
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
timeToWait += 100;
|
|
87
|
-
await new Promise((resolve) => setTimeout(resolve, timeToWait));
|
|
88
|
-
await tryToReconnect();
|
|
89
|
-
};
|
|
90
|
-
await tryToReconnect();
|
|
91
|
-
};
|
|
92
|
-
retry();
|
|
125
|
+
initialWebSocket.onclose = () => {
|
|
126
|
+
waitForServerToComeBack();
|
|
93
127
|
};
|
|
94
128
|
})();
|
|
95
129
|
}
|