@jsenv/core 41.5.35 → 41.5.37
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.
- package/dist/{page_picker.js → client/client_monitoring/clients_page.html} +650 -2
- package/dist/client/https_trust/https_trust.html +362 -0
- package/dist/js/page_switcher.js +809 -1
- package/dist/start_dev_server/start_dev_server.js +126 -4
- package/package.json +2 -2
- package/src/dev/dev_server_plugins/client/https_trust.html +352 -0
- package/src/dev/dev_server_plugins/dev_server_plugin_https_trust.js +93 -0
- package/src/dev/start_dev_server.js +35 -1
- package/dist/html/clients_page.html +0 -651
- /package/dist/{html → client/client_monitoring}/client_monitor_page.html +0 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Trust the dev server https</title>
|
|
7
|
+
<style>:root {
|
|
8
|
+
--lightningcss-light: initial;
|
|
9
|
+
--lightningcss-dark: ;
|
|
10
|
+
color-scheme: light dark;
|
|
11
|
+
--ink: #111827;
|
|
12
|
+
--muted: #6b7280;
|
|
13
|
+
--paper: #f3f4f6;
|
|
14
|
+
--card: #fff;
|
|
15
|
+
--line: #e5e7eb;
|
|
16
|
+
--accent: #2563eb;
|
|
17
|
+
--accent-ink: #fff;
|
|
18
|
+
--ok: #15803d;
|
|
19
|
+
--ok-paper: #dcfce7;
|
|
20
|
+
--warn: #b45309;
|
|
21
|
+
--warn-paper: #fef3c7;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@media (prefers-color-scheme: dark) {
|
|
25
|
+
:root {
|
|
26
|
+
--lightningcss-light: ;
|
|
27
|
+
--lightningcss-dark: initial;
|
|
28
|
+
--ink: #e5e7eb;
|
|
29
|
+
--muted: #9ca3af;
|
|
30
|
+
--paper: #0b1020;
|
|
31
|
+
--card: #111827;
|
|
32
|
+
--line: #1f2937;
|
|
33
|
+
--accent: #3b82f6;
|
|
34
|
+
--ok: #4ade80;
|
|
35
|
+
--ok-paper: #052e16;
|
|
36
|
+
--warn: #fbbf24;
|
|
37
|
+
--warn-paper: #2d1a02;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
body {
|
|
42
|
+
color: var(--ink);
|
|
43
|
+
background: var(--paper);
|
|
44
|
+
margin: 0;
|
|
45
|
+
padding: 16px;
|
|
46
|
+
font: 15px / 1.5 -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
main {
|
|
50
|
+
flex-direction: column;
|
|
51
|
+
gap: 16px;
|
|
52
|
+
max-width: 34rem;
|
|
53
|
+
margin: 0 auto;
|
|
54
|
+
display: flex;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
h1 {
|
|
58
|
+
margin: 8px 0 0;
|
|
59
|
+
font-size: 1.35rem;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
h2 {
|
|
63
|
+
margin: 0;
|
|
64
|
+
font-size: 1rem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
p {
|
|
68
|
+
margin: 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
ol {
|
|
72
|
+
margin: 0;
|
|
73
|
+
padding-left: 1.3em;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
li + li {
|
|
77
|
+
margin-top: 6px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
code {
|
|
81
|
+
font: .85em ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.muted {
|
|
85
|
+
color: var(--muted);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.card {
|
|
89
|
+
background: var(--card);
|
|
90
|
+
border: 1px solid var(--line);
|
|
91
|
+
border-radius: 10px;
|
|
92
|
+
flex-direction: column;
|
|
93
|
+
gap: 10px;
|
|
94
|
+
padding: 14px 16px;
|
|
95
|
+
display: flex;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.button {
|
|
99
|
+
color: var(--accent);
|
|
100
|
+
text-align: center;
|
|
101
|
+
border: 1px solid var(--accent);
|
|
102
|
+
border-radius: 8px;
|
|
103
|
+
padding: 12px 16px;
|
|
104
|
+
font-weight: 600;
|
|
105
|
+
text-decoration: none;
|
|
106
|
+
display: block;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.button[data-primary] {
|
|
110
|
+
color: var(--accent-ink);
|
|
111
|
+
background: var(--accent);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
#status {
|
|
115
|
+
font-weight: 600;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
#status_card[data-state="trusted"] {
|
|
119
|
+
background: var(--ok-paper);
|
|
120
|
+
border-color: var(--ok);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#status_card[data-state="trusted"] #status {
|
|
124
|
+
color: var(--ok);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#status_card[data-state="untrusted"] {
|
|
128
|
+
background: var(--warn-paper);
|
|
129
|
+
border-color: var(--warn);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
#status_card[data-state="untrusted"] #status {
|
|
133
|
+
color: var(--warn);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#fingerprint {
|
|
137
|
+
word-break: break-all;
|
|
138
|
+
display: block;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
details {
|
|
142
|
+
background: var(--card);
|
|
143
|
+
border: 1px solid var(--line);
|
|
144
|
+
border-radius: 10px;
|
|
145
|
+
padding: 14px 16px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
summary {
|
|
149
|
+
cursor: pointer;
|
|
150
|
+
font-weight: 600;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
details[open] summary {
|
|
154
|
+
margin-bottom: 10px;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
details .this_device {
|
|
158
|
+
color: var(--muted);
|
|
159
|
+
font-weight: 400;
|
|
160
|
+
display: none;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
details[data-current] .this_device {
|
|
164
|
+
display: inline;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
details p + ol, details ol + p, details p + p {
|
|
168
|
+
margin-top: 10px;
|
|
169
|
+
}
|
|
170
|
+
</style>
|
|
171
|
+
</head>
|
|
172
|
+
<body>
|
|
173
|
+
<main>
|
|
174
|
+
<h1>Trust the dev server https</h1>
|
|
175
|
+
<p class="muted">
|
|
176
|
+
Install the root certificate of the computer running the dev server and
|
|
177
|
+
this device opens it over https without a warning. Service workers,
|
|
178
|
+
push, install and the other secure-context APIs need it.
|
|
179
|
+
</p>
|
|
180
|
+
|
|
181
|
+
<div id="status_card" class="card" data-state="checking">
|
|
182
|
+
<p id="status">Checking whether this device trusts the dev server…</p>
|
|
183
|
+
<a id="continue" class="button" href="/">
|
|
184
|
+
Continue to https
|
|
185
|
+
</a>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<div class="card">
|
|
189
|
+
<a id="download" class="button" href="./root.crt" data-primary="">
|
|
190
|
+
Download the root certificate
|
|
191
|
+
</a>
|
|
192
|
+
<p>
|
|
193
|
+
<span class="muted">SHA-256 fingerprint</span>
|
|
194
|
+
<code id="fingerprint">…</code>
|
|
195
|
+
</p>
|
|
196
|
+
<p class="muted">
|
|
197
|
+
Once installed, the fingerprint the device shows must match the one
|
|
198
|
+
the dev server prints in its terminal: this page travels over plain
|
|
199
|
+
http, so the network could swap the file.
|
|
200
|
+
</p>
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
<div id="platforms">
|
|
204
|
+
<details data-platform="android" open="">
|
|
205
|
+
<summary>
|
|
206
|
+
Android <span class="this_device">(this device)</span>
|
|
207
|
+
</summary>
|
|
208
|
+
<ol>
|
|
209
|
+
<li>
|
|
210
|
+
Tap <b>Download the root certificate</b>. Opening the file only
|
|
211
|
+
says CA certificates must be installed from Settings: that is
|
|
212
|
+
expected.
|
|
213
|
+
</li>
|
|
214
|
+
<li>
|
|
215
|
+
In <b>Settings</b>, search for <b>CA certificate</b>. Or go to
|
|
216
|
+
Security → Encryption & credentials → Install a certificate →
|
|
217
|
+
CA certificate.
|
|
218
|
+
</li>
|
|
219
|
+
<li>
|
|
220
|
+
Tap <b>Install anyway</b> and pick the file in Downloads. Android
|
|
221
|
+
asks for a screen lock if there is none.
|
|
222
|
+
</li>
|
|
223
|
+
<li>Come back here, the check above turns green.</li>
|
|
224
|
+
</ol>
|
|
225
|
+
<p class="muted">
|
|
226
|
+
Android then shows “Network may be monitored”: expected, the device
|
|
227
|
+
trusts a certificate installed by hand. Chrome honors it, most apps
|
|
228
|
+
do not.
|
|
229
|
+
</p>
|
|
230
|
+
</details>
|
|
231
|
+
<details data-platform="ios" open="">
|
|
232
|
+
<summary>
|
|
233
|
+
iPhone, iPad <span class="this_device">(this device)</span>
|
|
234
|
+
</summary>
|
|
235
|
+
<ol>
|
|
236
|
+
<li>
|
|
237
|
+
Open this page in <b>Safari</b>, tap
|
|
238
|
+
<b>Download the root certificate</b>, then <b>Allow</b>.
|
|
239
|
+
</li>
|
|
240
|
+
<li>
|
|
241
|
+
In <b>Settings</b>, tap <b>Profile Downloaded</b> →
|
|
242
|
+
<b>Install</b>.
|
|
243
|
+
</li>
|
|
244
|
+
<li>
|
|
245
|
+
Then
|
|
246
|
+
<b>Settings → General → About → Certificate Trust Settings</b> and
|
|
247
|
+
turn on <b class="common_name">the root certificate</b>. Without
|
|
248
|
+
this step the certificate is installed but not trusted.
|
|
249
|
+
</li>
|
|
250
|
+
<li>Come back here, the check above turns green.</li>
|
|
251
|
+
</ol>
|
|
252
|
+
</details>
|
|
253
|
+
<details data-platform="desktop" open="">
|
|
254
|
+
<summary>
|
|
255
|
+
Computer <span class="this_device">(this device)</span>
|
|
256
|
+
</summary>
|
|
257
|
+
<p>
|
|
258
|
+
On the computer running the dev server,
|
|
259
|
+
<code>npx @jsenv/https-local init</code> installs and trusts the
|
|
260
|
+
root certificate.
|
|
261
|
+
</p>
|
|
262
|
+
<p>
|
|
263
|
+
On another computer, download it and add it to the trusted root
|
|
264
|
+
certificates of the system. On macOS: open the file, then set it to
|
|
265
|
+
“Always Trust” in Keychain Access.
|
|
266
|
+
</p>
|
|
267
|
+
</details>
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
<details open="">
|
|
271
|
+
<summary>Good to know</summary>
|
|
272
|
+
<p>
|
|
273
|
+
The device trusts anything signed by this root certificate. Its key
|
|
274
|
+
never leaves the computer running the dev server, only the certificate
|
|
275
|
+
is served: the trade-off already made on that computer.
|
|
276
|
+
</p>
|
|
277
|
+
<p>
|
|
278
|
+
<b>See the fingerprint, or remove it.</b> Android: Settings →
|
|
279
|
+
Encryption & credentials → Trusted credentials → User → tap it.
|
|
280
|
+
iPhone, iPad: Settings → General → VPN & Device Management → the
|
|
281
|
+
profile.
|
|
282
|
+
</p>
|
|
283
|
+
</details>
|
|
284
|
+
</main>
|
|
285
|
+
|
|
286
|
+
<script type="module">const readPlatform = () => {
|
|
287
|
+
const userAgent = window.navigator.userAgent;
|
|
288
|
+
if (/android/i.test(userAgent)) {
|
|
289
|
+
return "android";
|
|
290
|
+
}
|
|
291
|
+
if (/iPhone|iPad|iPod/.test(userAgent)) {
|
|
292
|
+
return "ios";
|
|
293
|
+
}
|
|
294
|
+
// iPadOS Safari introduces itself as a Mac
|
|
295
|
+
if (
|
|
296
|
+
/Macintosh/.test(userAgent) &&
|
|
297
|
+
window.navigator.maxTouchPoints > 1
|
|
298
|
+
) {
|
|
299
|
+
return "ios";
|
|
300
|
+
}
|
|
301
|
+
return "desktop";
|
|
302
|
+
};
|
|
303
|
+
const platform = readPlatform();
|
|
304
|
+
const platforms = document.querySelector("#platforms");
|
|
305
|
+
for (const details of platforms.querySelectorAll("[data-platform]")) {
|
|
306
|
+
if (details.getAttribute("data-platform") === platform) {
|
|
307
|
+
details.setAttribute("data-current", "");
|
|
308
|
+
platforms.prepend(details);
|
|
309
|
+
} else {
|
|
310
|
+
details.open = false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// http and https share the port of the dev server
|
|
315
|
+
const port =
|
|
316
|
+
window.location.port ||
|
|
317
|
+
(window.location.protocol === "https:" ? "443" : "80");
|
|
318
|
+
const httpsOrigin = new URL(`https://${window.location.hostname}:${port}`)
|
|
319
|
+
.origin;
|
|
320
|
+
const continueLink = document.querySelector("#continue");
|
|
321
|
+
continueLink.href = `${httpsOrigin}/`;
|
|
322
|
+
|
|
323
|
+
const statusCard = document.querySelector("#status_card");
|
|
324
|
+
const status = document.querySelector("#status");
|
|
325
|
+
const download = document.querySelector("#download");
|
|
326
|
+
const checkTrust = async () => {
|
|
327
|
+
try {
|
|
328
|
+
// rejects as long as the certificate is not trusted, unless this
|
|
329
|
+
// browser clicked through the certificate warning for this host
|
|
330
|
+
await fetch(`${httpsOrigin}${window.location.pathname}ping`, {
|
|
331
|
+
mode: "no-cors",
|
|
332
|
+
cache: "no-store",
|
|
333
|
+
});
|
|
334
|
+
} catch {
|
|
335
|
+
statusCard.setAttribute("data-state", "untrusted");
|
|
336
|
+
status.textContent = "This device does not trust the dev server yet.";
|
|
337
|
+
continueLink.removeAttribute("data-primary");
|
|
338
|
+
download.setAttribute("data-primary", "");
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
statusCard.setAttribute("data-state", "trusted");
|
|
342
|
+
status.textContent = "✓ This device trusts the dev server.";
|
|
343
|
+
continueLink.setAttribute("data-primary", "");
|
|
344
|
+
download.removeAttribute("data-primary");
|
|
345
|
+
};
|
|
346
|
+
checkTrust();
|
|
347
|
+
// back from the settings where the certificate was installed
|
|
348
|
+
document.addEventListener("visibilitychange", () => {
|
|
349
|
+
if (document.visibilityState === "visible") {
|
|
350
|
+
checkTrust();
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
const response = await fetch("./root.json");
|
|
355
|
+
const { fingerprint256, commonName } = await response.json();
|
|
356
|
+
document.querySelector("#fingerprint").textContent = fingerprint256;
|
|
357
|
+
for (const element of document.querySelectorAll(".common_name")) {
|
|
358
|
+
element.textContent = commonName;
|
|
359
|
+
}
|
|
360
|
+
</script>
|
|
361
|
+
</body>
|
|
362
|
+
</html>
|