@jsenv/core 41.5.34 → 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.
@@ -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 &amp; 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 &amp; credentials → Trusted credentials → User → tap it.
280
+ iPhone, iPad: Settings → General → VPN &amp; 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>