quickwin 1.2.1 → 2.0.0
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/README.en.md +3 -3
- package/README.md +21 -24
- package/examples/PdfCanvas.js +8 -37
- package/examples/PdfCanvas.tsx +11 -41
- package/examples/exec_server.js +20054 -1423
- package/examples/exec_server.tsx +489 -0
- package/examples/exec_server_worker.js +383 -33
- package/examples/exec_server_worker.ts +408 -30
- package/examples/pdf_preview2.js +31 -73
- package/examples/pdf_preview2.ts +33 -76
- package/examples/pdf_viewer.js +1496 -616
- package/examples/pdf_viewer.tsx +23 -64
- package/examples/setres.js +22 -47
- package/examples/setres.ts +23 -57
- package/examples/test_gallery.js +1491 -525
- package/examples/test_react_lazy.js +1455 -500
- package/examples/test_react_listbox.js +1454 -499
- package/examples/test_root_window.js +1450 -495
- package/examples/test_tab.js +1455 -500
- package/lib/abort.js +70 -0
- package/lib/abort.ts +69 -0
- package/lib/certs.js +214 -0
- package/lib/certs.ts +224 -0
- package/lib/exec-log.js +15 -0
- package/lib/exec-log.ts +30 -0
- package/lib/fetch.js +37 -4
- package/lib/fetch.ts +35 -4
- package/lib/ffi/bind.js +352 -0
- package/lib/ffi/bind.ts +442 -0
- package/lib/ffi/ctype.js +168 -0
- package/lib/ffi/ctype.ts +331 -0
- package/lib/ffi/index.js +6 -0
- package/lib/ffi/index.ts +6 -0
- package/lib/ffi/struct.js +412 -0
- package/lib/ffi/struct.ts +664 -0
- package/lib/http-common.js +43 -5
- package/lib/http-common.ts +48 -6
- package/lib/http-server.js +187 -29
- package/lib/http-server.ts +177 -26
- package/lib/react-qw/components/DateTimePicker.js +20 -33
- package/lib/react-qw/components/Input.js +3 -10
- package/lib/react-qw/components/Link.js +7 -22
- package/lib/react-qw/components/ListView.js +80 -141
- package/lib/react-qw/components/PathPicker.js +36 -80
- package/lib/react-qw/components/ScrollView.js +15 -8
- package/lib/react-qw/components/Slider.js +2 -1
- package/lib/react-qw/components/Tab.js +15 -20
- package/lib/react-qw/components/Tooltip.js +21 -20
- package/lib/react-qw/components/TreeView.js +14 -47
- package/lib/react-qw/index.js +4 -2
- package/lib/react-qw/index.ts +4 -2
- package/lib/react-qw/nmhdr.js +5 -0
- package/lib/react-qw/nmhdr.ts +7 -0
- package/lib/react-qw/props.js +7 -2
- package/lib/react-qw/props.ts +7 -3
- package/lib/react-qw/reconciler.js +23 -13
- package/lib/react-qw/reconciler.ts +22 -13
- package/lib/text-measure.js +18 -35
- package/lib/text-measure.ts +20 -37
- package/lib/websocket.js +12 -2
- package/lib/websocket.ts +15 -2
- package/lib/windows/comctl32.js +18 -0
- package/lib/windows/comctl32.ts +20 -0
- package/lib/windows/comdlg32.js +7 -0
- package/lib/windows/comdlg32.ts +9 -0
- package/lib/windows/gdi32.js +90 -0
- package/lib/windows/gdi32.ts +97 -0
- package/lib/windows/kernel32.js +66 -0
- package/lib/windows/kernel32.ts +76 -0
- package/lib/windows/ntdll.js +8 -0
- package/lib/windows/ntdll.ts +10 -0
- package/lib/windows/ole32.js +7 -0
- package/lib/windows/ole32.ts +9 -0
- package/lib/windows/shell32.js +11 -0
- package/lib/windows/shell32.ts +14 -0
- package/lib/windows/structs.js +384 -0
- package/lib/windows/structs.ts +422 -0
- package/lib/windows/user32.js +469 -0
- package/lib/windows/user32.ts +502 -0
- package/package.json +1 -1
- package/quickwin.d.ts +22 -33
- package/quickwin_const.d.ts +1 -0
- package/qwin-nowasm-x86.exe +0 -0
- package/qwin-nowasm.exe +0 -0
- package/qwin-x86.exe +0 -0
- package/qwin.exe +0 -0
- package/test/bench_ffi_callraw.js +236 -0
- package/test/bench_ffi_callraw.ts +236 -0
- package/test/bench_ffi_overhead.js +139 -0
- package/test/bench_ffi_overhead.ts +141 -0
- package/test/bench_sendmessage.js +104 -0
- package/test/bench_sendmessage.ts +100 -0
- package/test/bench_sendmessage_nonnull.js +187 -0
- package/test/bench_sendmessage_nonnull.ts +188 -0
- package/test/run.js +14 -4
- package/test/run.ts +15 -5
- package/test/test_basic_fetch.js +8 -0
- package/test/test_basic_fetch.ts +9 -0
- package/test/test_ffi.js +33 -47
- package/test/test_ffi.ts +41 -60
- package/test/test_ffi_abi.js +235 -0
- package/test/test_ffi_abi.ts +218 -0
- package/test/test_ffi_bind.js +572 -0
- package/test/test_ffi_bind.ts +597 -0
- package/test/test_ffi_struct.js +745 -0
- package/test/test_ffi_struct.ts +798 -0
- package/test/test_ffi_struct_layout.js +495 -0
- package/test/test_ffi_struct_layout.ts +543 -0
- package/test/test_ffi_user32.js +103 -0
- package/test/test_ffi_user32.ts +113 -0
- package/test/test_ffi_windows.js +101 -0
- package/test/test_ffi_windows.ts +113 -0
- package/test/test_helper.js +8 -1
- package/test/test_helper.ts +9 -1
- package/test/test_http_import.js +18 -0
- package/test/test_http_import.ts +19 -0
- package/test/test_http_server.js +73 -1
- package/test/test_http_server.ts +75 -1
- package/test/test_ipv6.js +2 -2
- package/test/test_ipv6.ts +2 -2
- package/test/test_net_fetch.js +70 -8
- package/test/test_net_fetch.ts +69 -7
- package/test/test_net_websocket.js +1 -1
- package/test/test_net_websocket.ts +1 -1
- package/test/test_react_complex.js +1465 -510
- package/test/test_react_counter.js +1455 -500
- package/test/test_react_flex.js +1475 -513
- package/test/test_react_hidden.js +1458 -503
- package/test/test_react_listview_link.js +1455 -500
- package/test/test_react_notify.js +18534 -0
- package/test/test_react_render.js +1455 -500
- package/test/test_wolfssl_read.js +3 -2
- package/test/test_wolfssl_read.ts +3 -1
- package/test/test_worker_net.js +35 -17
- package/test/test_worker_net.ts +27 -15
- package/tsconfig.json +1 -1
- package/examples/exec_server.ts +0 -80
package/test/test_http_server.ts
CHANGED
|
@@ -4,6 +4,7 @@ import * as os from 'os'
|
|
|
4
4
|
import * as sock from 'sock'
|
|
5
5
|
import { Tester } from './test_helper.js'
|
|
6
6
|
import { createServer } from '../lib/http-server.js'
|
|
7
|
+
import '../lib/fetch.js'
|
|
7
8
|
|
|
8
9
|
// ── Minimal raw HTTP client for testing ──
|
|
9
10
|
|
|
@@ -69,6 +70,37 @@ export const suite = {
|
|
|
69
70
|
return new Response(req.method + ':' + await req.text(), { headers: { 'Content-Type': 'text/plain' } })
|
|
70
71
|
} else if (path === '/json') {
|
|
71
72
|
return new Response(JSON.stringify({ method: req.method, q: Object.fromEntries(url.searchParams) }), { headers: { 'Content-Type': 'application/json' } })
|
|
73
|
+
} else if (path === '/stream') {
|
|
74
|
+
// length unknown: server must send chunked frames
|
|
75
|
+
return new Response(new ReadableStream({
|
|
76
|
+
start(control) {
|
|
77
|
+
control.enqueue(new TextEncoder().encode('chunkA/'))
|
|
78
|
+
control.enqueue(new TextEncoder().encode('chunkB'))
|
|
79
|
+
control.close()
|
|
80
|
+
}
|
|
81
|
+
}), { headers: { 'Content-Type': 'text/plain' } })
|
|
82
|
+
} else if (path === '/file') {
|
|
83
|
+
// length known: server streams raw bytes with Content-Length
|
|
84
|
+
return new Response(new ReadableStream({
|
|
85
|
+
start(control) {
|
|
86
|
+
control.enqueue(new TextEncoder().encode('chunkA/'))
|
|
87
|
+
control.enqueue(new TextEncoder().encode('chunkB'))
|
|
88
|
+
control.close()
|
|
89
|
+
}
|
|
90
|
+
}), { headers: { 'Content-Type': 'text/plain', 'Content-Length': '13' } })
|
|
91
|
+
} else if (path === '/stream-trailer') {
|
|
92
|
+
// chunked stream with trailing headers (Node addTrailers
|
|
93
|
+
// semantics: `Trailer` declaration + frames, then the footer)
|
|
94
|
+
return new Response(new ReadableStream({
|
|
95
|
+
start(control) {
|
|
96
|
+
control.enqueue(new TextEncoder().encode('chunkA/'))
|
|
97
|
+
control.enqueue(new TextEncoder().encode('chunkB'))
|
|
98
|
+
control.close()
|
|
99
|
+
}
|
|
100
|
+
}), {
|
|
101
|
+
headers: { 'Content-Type': 'text/plain', 'Trailer': 'X-Exit-Code' },
|
|
102
|
+
trailers: Promise.resolve({ 'X-Exit-Code': '3' })
|
|
103
|
+
})
|
|
72
104
|
} else {
|
|
73
105
|
return new Response('not found', { status: 404 })
|
|
74
106
|
}
|
|
@@ -87,6 +119,48 @@ export const suite = {
|
|
|
87
119
|
t.checkTrue('200 status', r.indexOf('HTTP/1.1 200') === 0)
|
|
88
120
|
t.checkTrue('body = hello world', r.endsWith('hello world'))
|
|
89
121
|
t.checkTrue('content-type text', r.toLowerCase().indexOf('content-type: text/plain') >= 0)
|
|
122
|
+
t.checkTrue('static body gets content-length', r.toLowerCase().indexOf('content-length: 11') >= 0)
|
|
123
|
+
|
|
124
|
+
// ── streaming chunked response (length unknown) ──
|
|
125
|
+
t.section('streaming chunked response')
|
|
126
|
+
r = await httpSend('127.0.0.1', port, 'GET /stream HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n')
|
|
127
|
+
t.checkTrue('200 status', r.indexOf('HTTP/1.1 200') === 0)
|
|
128
|
+
t.checkTrue('transfer-encoding chunked', r.toLowerCase().indexOf('transfer-encoding: chunked') >= 0)
|
|
129
|
+
t.checkTrue('no content-length', r.toLowerCase().indexOf('content-length:') < 0)
|
|
130
|
+
t.checkTrue('chunk frames + terminator', r.indexOf('7\r\nchunkA/\r\n6\r\nchunkB\r\n0\r\n\r\n') >= 0)
|
|
131
|
+
|
|
132
|
+
// ── streaming raw response with known length ──
|
|
133
|
+
t.section('streaming known-length response')
|
|
134
|
+
r = await httpSend('127.0.0.1', port, 'GET /file HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n')
|
|
135
|
+
t.checkTrue('200 status', r.indexOf('HTTP/1.1 200') === 0)
|
|
136
|
+
t.checkTrue('content-length 13', r.toLowerCase().indexOf('content-length: 13') >= 0)
|
|
137
|
+
t.checkTrue('no chunked encoding', r.toLowerCase().indexOf('transfer-encoding: chunked') < 0)
|
|
138
|
+
t.checkTrue('streamed raw body', r.includes('chunkA/chunkB'))
|
|
139
|
+
t.checkTrue('no chunk frame headers', r.indexOf('7\r\nchunkA/') < 0)
|
|
140
|
+
|
|
141
|
+
// ── HEAD of a streamed body: headers only, empty body ──
|
|
142
|
+
t.section('HEAD streaming response')
|
|
143
|
+
r = await httpSend('127.0.0.1', port, 'HEAD /stream HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n')
|
|
144
|
+
t.checkTrue('HEAD 200', r.indexOf('HTTP/1.1 200') === 0)
|
|
145
|
+
t.checkTrue('HEAD content-length 13', r.toLowerCase().indexOf('content-length: 13') >= 0)
|
|
146
|
+
const headBodyIdx = r.indexOf('\r\n\r\n')
|
|
147
|
+
t.checkTrue('HEAD empty body', r.slice(headBodyIdx + 4).length === 0)
|
|
148
|
+
|
|
149
|
+
// ── streaming chunked response with trailing headers ──
|
|
150
|
+
t.section('streaming trailer response')
|
|
151
|
+
r = await httpSend('127.0.0.1', port, 'GET /stream-trailer HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n')
|
|
152
|
+
t.checkTrue('200 status', r.indexOf('HTTP/1.1 200') === 0)
|
|
153
|
+
t.checkTrue('trailer declared in head', r.toLowerCase().indexOf('trailer: x-exit-code') >= 0)
|
|
154
|
+
t.checkTrue('terminator carries trailer', r.indexOf('0\r\nX-Exit-Code: 3\r\n\r\n') >= 0)
|
|
155
|
+
t.checkTrue('trailer footer after body', r.indexOf('7\r\nchunkA/\r\n6\r\nchunkB\r\n0\r\nX-Exit-Code: 3\r\n\r\n') >= 0)
|
|
156
|
+
|
|
157
|
+
// ── fetch client consumes chunked response with trailer footer ──
|
|
158
|
+
t.section('fetch consumes trailer response')
|
|
159
|
+
{
|
|
160
|
+
const rr = await fetch(`http://127.0.0.1:${port}/stream-trailer`)
|
|
161
|
+
t.checkTrue('status 200', rr.status === 200)
|
|
162
|
+
t.checkTrue('body decoded without trailer', await rr.text() === 'chunkA/chunkB')
|
|
163
|
+
}
|
|
90
164
|
|
|
91
165
|
// ── POST with body ──
|
|
92
166
|
t.section('POST with body')
|
|
@@ -124,7 +198,7 @@ export const suite = {
|
|
|
124
198
|
|
|
125
199
|
// ── server received expected requests ──
|
|
126
200
|
t.section('request log')
|
|
127
|
-
t.check('total requests',
|
|
201
|
+
t.check('total requests', 12, requests.length)
|
|
128
202
|
t.checkTrue('saw GET /', requests.indexOf('GET /') >= 0)
|
|
129
203
|
t.checkTrue('saw POST /echo', requests.indexOf('POST /echo') >= 0)
|
|
130
204
|
|
package/test/test_ipv6.js
CHANGED
|
@@ -79,7 +79,7 @@ export const suite = {
|
|
|
79
79
|
// ── 4. fetch HTTPS IPv6 ──
|
|
80
80
|
t.section('fetch https://[::1]');
|
|
81
81
|
{
|
|
82
|
-
const r = await fetch(`https://[::1]:${HTTPS_PORT}/`, { timeout: 5000 });
|
|
82
|
+
const r = await fetch(`https://[::1]:${HTTPS_PORT}/`, { timeout: 5000, rejectUnauthorized: false });
|
|
83
83
|
assert('fetch IPv6 HTTPS status 200', r.status === 200);
|
|
84
84
|
const body = await r.text();
|
|
85
85
|
assert('fetch IPv6 HTTPS body', body === 'hello from test server');
|
|
@@ -110,7 +110,7 @@ export const suite = {
|
|
|
110
110
|
// ── 6. WebSocket WSS IPv6 ──
|
|
111
111
|
t.section('wss://[::1] echo');
|
|
112
112
|
await new Promise((resolve, reject) => {
|
|
113
|
-
const ws = new WebSocket(`wss://[::1]:${HTTPS_PORT}
|
|
113
|
+
const ws = new WebSocket(`wss://[::1]:${HTTPS_PORT}/`, { rejectUnauthorized: false });
|
|
114
114
|
const timeoutId = os.setTimeout(() => reject(new Error('WSS IPv6 timeout')), 10000);
|
|
115
115
|
ws.onopen = () => {
|
|
116
116
|
t.ok++;
|
package/test/test_ipv6.ts
CHANGED
|
@@ -70,7 +70,7 @@ export const suite = {
|
|
|
70
70
|
// ── 4. fetch HTTPS IPv6 ──
|
|
71
71
|
t.section('fetch https://[::1]')
|
|
72
72
|
{
|
|
73
|
-
const r = await fetch(`https://[::1]:${HTTPS_PORT}/`, { timeout: 5000 })
|
|
73
|
+
const r = await fetch(`https://[::1]:${HTTPS_PORT}/`, { timeout: 5000, rejectUnauthorized: false })
|
|
74
74
|
assert('fetch IPv6 HTTPS status 200', r.status === 200)
|
|
75
75
|
const body = await r.text()
|
|
76
76
|
assert('fetch IPv6 HTTPS body', body === 'hello from test server')
|
|
@@ -104,7 +104,7 @@ export const suite = {
|
|
|
104
104
|
// ── 6. WebSocket WSS IPv6 ──
|
|
105
105
|
t.section('wss://[::1] echo')
|
|
106
106
|
await new Promise<void>((resolve, reject) => {
|
|
107
|
-
const ws = new WebSocket(`wss://[::1]:${HTTPS_PORT}
|
|
107
|
+
const ws = new WebSocket(`wss://[::1]:${HTTPS_PORT}/`, { rejectUnauthorized: false })
|
|
108
108
|
const timeoutId = os.setTimeout(() => reject(new Error('WSS IPv6 timeout')), 10000)
|
|
109
109
|
|
|
110
110
|
ws.onopen = () => {
|
package/test/test_net_fetch.js
CHANGED
|
@@ -14,13 +14,24 @@ export const suite = {
|
|
|
14
14
|
std.printf(' FAIL: %s\n', name);
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
+
// Retry: public-site reachability (baidu/jsdelivr/esm.sh) depends on
|
|
18
|
+
// CI network conditions, and a single dropped connection must not be
|
|
19
|
+
// reported as a certificate regression. The last error is printed so a
|
|
20
|
+
// real failure is still diagnosable.
|
|
17
21
|
async function safeFetch(url, init) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
let last = 'unknown';
|
|
23
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
24
|
+
try {
|
|
25
|
+
return await fetch(url, init);
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
last = String(e?.message || e);
|
|
29
|
+
if (attempt < 2)
|
|
30
|
+
os.sleep(500);
|
|
31
|
+
}
|
|
23
32
|
}
|
|
33
|
+
std.printf(' fetch %s failed after 3 attempts: %s\n', String(url), last);
|
|
34
|
+
return null;
|
|
24
35
|
}
|
|
25
36
|
const BASE = 'http://localhost:18923';
|
|
26
37
|
// ── Plain HTTP GET (regression: slot 0 + !s bug) ──
|
|
@@ -247,7 +258,7 @@ export const suite = {
|
|
|
247
258
|
}
|
|
248
259
|
// ── HTTPS fetch ──
|
|
249
260
|
t.section('HTTPS fetch');
|
|
250
|
-
const r11 = await safeFetch('https://localhost:18924/', { timeout: 5000 });
|
|
261
|
+
const r11 = await safeFetch('https://localhost:18924/', { timeout: 5000, rejectUnauthorized: false });
|
|
251
262
|
if (r11) {
|
|
252
263
|
assert('https status 200', r11.status === 200);
|
|
253
264
|
const body = await r11.text();
|
|
@@ -256,6 +267,57 @@ export const suite = {
|
|
|
256
267
|
else {
|
|
257
268
|
assert('https endpoint reachable', false);
|
|
258
269
|
}
|
|
270
|
+
// ── GlobalSign R3 root chain (regression: ASN_SIG_CONFIRM_E -155) ──
|
|
271
|
+
// baidu.com and jsdelivr terminate in GlobalSign's 2018 re-keyed R3,
|
|
272
|
+
// which reuses the self-signed R3's subject-key-identifier with a
|
|
273
|
+
// different public key. Shipping the self-signed R3 in lib/certs.ts
|
|
274
|
+
// made wolfSSL match the wrong key and fail the handshake with -155.
|
|
275
|
+
// Both are public CDNs: a 500/429 or a dropped connection then counts
|
|
276
|
+
// as a retried attempt, not an immediate failure — only a persistent
|
|
277
|
+
// failure (cert regression or long-lived outage) should fail CI.
|
|
278
|
+
t.section('GlobalSign R3 root chain');
|
|
279
|
+
{
|
|
280
|
+
async function fetchR3(url) {
|
|
281
|
+
const init = { timeout: 10000 };
|
|
282
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
283
|
+
try {
|
|
284
|
+
const r = await fetch(url, init);
|
|
285
|
+
const body = await r.text();
|
|
286
|
+
if (r.status === 200 && body.length > 0)
|
|
287
|
+
return body;
|
|
288
|
+
std.printf(' fetch %s status=%s len=%d (attempt %d)\n', String(url), String(r.status), body.length, attempt);
|
|
289
|
+
}
|
|
290
|
+
catch (e) {
|
|
291
|
+
std.printf(' fetch %s error: %s (attempt %d)\n', String(url), String(e?.message || e), attempt);
|
|
292
|
+
}
|
|
293
|
+
os.sleep(1000);
|
|
294
|
+
}
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
const bb = await fetchR3('https://www.baidu.com/');
|
|
298
|
+
if (bb) {
|
|
299
|
+
assert('baidu (GlobalSign R3) status', true);
|
|
300
|
+
assert('baidu (GlobalSign R3) body non-empty', bb.length > 0);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
assert('baidu (GlobalSign R3) reachable', false);
|
|
304
|
+
}
|
|
305
|
+
const JSDELIVR_HOSTS = ['cdn.jsdelivr.net', 'fastly.jsdelivr.net', 'gcore.jsdelivr.net'];
|
|
306
|
+
for (const host of JSDELIVR_HOSTS) {
|
|
307
|
+
const bj = await fetchR3('https://' + host + '/npm/left-pad@1.3.0');
|
|
308
|
+
if (bj) {
|
|
309
|
+
assert('jsdelivr (GlobalSign R3) status', true);
|
|
310
|
+
assert('jsdelivr (GlobalSign R3) body non-empty', bj.length > 0);
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
if (host !== JSDELIVR_HOSTS[JSDELIVR_HOSTS.length - 1]) {
|
|
314
|
+
std.printf(' jsdelivr host %s failed, trying next\n', host);
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
assert('jsdelivr (GlobalSign R3) reachable', false);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
259
321
|
// ── large HTTPS body (wolfSSL_read null regression) ──
|
|
260
322
|
// Pre-fix: wolfSSL_read returned number -1 on WANT_READ →
|
|
261
323
|
// receivedBytes became NaN → stream never closed → hang.
|
|
@@ -264,7 +326,7 @@ export const suite = {
|
|
|
264
326
|
const LARGE = 200000;
|
|
265
327
|
try {
|
|
266
328
|
const r = await Promise.race([
|
|
267
|
-
fetch(`https://localhost:18924/large/${LARGE}`, { timeout: 15000 }),
|
|
329
|
+
fetch(`https://localhost:18924/large/${LARGE}`, { timeout: 15000, rejectUnauthorized: false }),
|
|
268
330
|
new Promise((_, rej) => os.setTimeout(() => rej(new Error('Body hang watchdog 15s')), 15000))
|
|
269
331
|
]);
|
|
270
332
|
assert('large HTTPS status 200', r.status === 200);
|
|
@@ -304,7 +366,7 @@ export const suite = {
|
|
|
304
366
|
let rejected = false;
|
|
305
367
|
let msg = '';
|
|
306
368
|
try {
|
|
307
|
-
const r = await fetch('https://localhost:18924/stall', { timeout: 2000 });
|
|
369
|
+
const r = await fetch('https://localhost:18924/stall', { timeout: 2000, rejectUnauthorized: false });
|
|
308
370
|
await Promise.race([
|
|
309
371
|
r.arrayBuffer(),
|
|
310
372
|
new Promise((_, rej) => os.setTimeout(() => rej(new Error('Body timeout')), 8000))
|
package/test/test_net_fetch.ts
CHANGED
|
@@ -11,12 +11,22 @@ export const suite = {
|
|
|
11
11
|
else { t.fail++; std.printf(' FAIL: %s\n', name) }
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
// Retry: public-site reachability (baidu/jsdelivr/esm.sh) depends on
|
|
15
|
+
// CI network conditions, and a single dropped connection must not be
|
|
16
|
+
// reported as a certificate regression. The last error is printed so a
|
|
17
|
+
// real failure is still diagnosable.
|
|
14
18
|
async function safeFetch(url: any, init?: RequestInit) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
let last = 'unknown'
|
|
20
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
21
|
+
try {
|
|
22
|
+
return await fetch(url, init)
|
|
23
|
+
} catch (e) {
|
|
24
|
+
last = String((e as Error)?.message || e)
|
|
25
|
+
if (attempt < 2) os.sleep(500)
|
|
26
|
+
}
|
|
19
27
|
}
|
|
28
|
+
std.printf(' fetch %s failed after 3 attempts: %s\n', String(url), last)
|
|
29
|
+
return null
|
|
20
30
|
}
|
|
21
31
|
const BASE = 'http://localhost:18923'
|
|
22
32
|
|
|
@@ -227,7 +237,7 @@ export const suite = {
|
|
|
227
237
|
|
|
228
238
|
// ── HTTPS fetch ──
|
|
229
239
|
t.section('HTTPS fetch')
|
|
230
|
-
const r11 = await safeFetch('https://localhost:18924/', { timeout: 5000 })
|
|
240
|
+
const r11 = await safeFetch('https://localhost:18924/', { timeout: 5000, rejectUnauthorized: false })
|
|
231
241
|
if (r11) {
|
|
232
242
|
assert('https status 200', r11.status === 200)
|
|
233
243
|
const body = await r11.text()
|
|
@@ -236,6 +246,58 @@ export const suite = {
|
|
|
236
246
|
assert('https endpoint reachable', false)
|
|
237
247
|
}
|
|
238
248
|
|
|
249
|
+
// ── GlobalSign R3 root chain (regression: ASN_SIG_CONFIRM_E -155) ──
|
|
250
|
+
// baidu.com and jsdelivr terminate in GlobalSign's 2018 re-keyed R3,
|
|
251
|
+
// which reuses the self-signed R3's subject-key-identifier with a
|
|
252
|
+
// different public key. Shipping the self-signed R3 in lib/certs.ts
|
|
253
|
+
// made wolfSSL match the wrong key and fail the handshake with -155.
|
|
254
|
+
// Both are public CDNs: a 500/429 or a dropped connection then counts
|
|
255
|
+
// as a retried attempt, not an immediate failure — only a persistent
|
|
256
|
+
// failure (cert regression or long-lived outage) should fail CI.
|
|
257
|
+
t.section('GlobalSign R3 root chain')
|
|
258
|
+
{
|
|
259
|
+
async function fetchR3(url: any): Promise<string | null> {
|
|
260
|
+
const init = { timeout: 10000 }
|
|
261
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
262
|
+
try {
|
|
263
|
+
const r = await fetch(url, init)
|
|
264
|
+
const body = await r.text()
|
|
265
|
+
if (r.status === 200 && body.length > 0) return body
|
|
266
|
+
std.printf(' fetch %s status=%s len=%d (attempt %d)\n',
|
|
267
|
+
String(url), String(r.status), body.length, attempt)
|
|
268
|
+
} catch (e) {
|
|
269
|
+
std.printf(' fetch %s error: %s (attempt %d)\n',
|
|
270
|
+
String(url), String((e as Error)?.message || e), attempt)
|
|
271
|
+
}
|
|
272
|
+
os.sleep(1000)
|
|
273
|
+
}
|
|
274
|
+
return null
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const bb = await fetchR3('https://www.baidu.com/')
|
|
278
|
+
if (bb) {
|
|
279
|
+
assert('baidu (GlobalSign R3) status', true)
|
|
280
|
+
assert('baidu (GlobalSign R3) body non-empty', bb.length > 0)
|
|
281
|
+
} else {
|
|
282
|
+
assert('baidu (GlobalSign R3) reachable', false)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const JSDELIVR_HOSTS = ['cdn.jsdelivr.net', 'fastly.jsdelivr.net', 'gcore.jsdelivr.net']
|
|
286
|
+
for (const host of JSDELIVR_HOSTS) {
|
|
287
|
+
const bj = await fetchR3('https://' + host + '/npm/left-pad@1.3.0')
|
|
288
|
+
if (bj) {
|
|
289
|
+
assert('jsdelivr (GlobalSign R3) status', true)
|
|
290
|
+
assert('jsdelivr (GlobalSign R3) body non-empty', bj.length > 0)
|
|
291
|
+
break
|
|
292
|
+
}
|
|
293
|
+
if (host !== JSDELIVR_HOSTS[JSDELIVR_HOSTS.length - 1]) {
|
|
294
|
+
std.printf(' jsdelivr host %s failed, trying next\n', host)
|
|
295
|
+
} else {
|
|
296
|
+
assert('jsdelivr (GlobalSign R3) reachable', false)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
239
301
|
// ── large HTTPS body (wolfSSL_read null regression) ──
|
|
240
302
|
// Pre-fix: wolfSSL_read returned number -1 on WANT_READ →
|
|
241
303
|
// receivedBytes became NaN → stream never closed → hang.
|
|
@@ -244,7 +306,7 @@ export const suite = {
|
|
|
244
306
|
const LARGE = 200000
|
|
245
307
|
try {
|
|
246
308
|
const r = await Promise.race([
|
|
247
|
-
fetch(`https://localhost:18924/large/${LARGE}`, { timeout: 15000 }),
|
|
309
|
+
fetch(`https://localhost:18924/large/${LARGE}`, { timeout: 15000, rejectUnauthorized: false }),
|
|
248
310
|
new Promise<never>((_, rej) =>
|
|
249
311
|
os.setTimeout(() => rej(new Error('Body hang watchdog 15s')), 15000)
|
|
250
312
|
)
|
|
@@ -292,7 +354,7 @@ export const suite = {
|
|
|
292
354
|
let rejected = false
|
|
293
355
|
let msg = ''
|
|
294
356
|
try {
|
|
295
|
-
const r = await fetch('https://localhost:18924/stall', { timeout: 2000 })
|
|
357
|
+
const r = await fetch('https://localhost:18924/stall', { timeout: 2000, rejectUnauthorized: false })
|
|
296
358
|
await Promise.race([
|
|
297
359
|
r.arrayBuffer(),
|
|
298
360
|
new Promise<never>((_, rej) =>
|
|
@@ -158,7 +158,7 @@ export const suite = {
|
|
|
158
158
|
t.section('wss basic text message');
|
|
159
159
|
const p7 = waitForTest();
|
|
160
160
|
let opened7 = false, received7 = false, closed7 = false;
|
|
161
|
-
const ws7 = new WebSocket("wss://localhost:18924");
|
|
161
|
+
const ws7 = new WebSocket("wss://localhost:18924", { rejectUnauthorized: false });
|
|
162
162
|
ws7.onopen = () => { opened7 = true; ws7.send("Hello WSS!"); };
|
|
163
163
|
ws7.onmessage = () => { received7 = true; ws7.close(1000, "done"); };
|
|
164
164
|
ws7.onclose = (e) => {
|
|
@@ -145,7 +145,7 @@ export const suite = {
|
|
|
145
145
|
t.section('wss basic text message')
|
|
146
146
|
const p7 = waitForTest()
|
|
147
147
|
let opened7 = false, received7 = false, closed7 = false
|
|
148
|
-
const ws7 = new WebSocket("wss://localhost:18924")
|
|
148
|
+
const ws7 = new WebSocket("wss://localhost:18924", { rejectUnauthorized: false })
|
|
149
149
|
ws7.onopen = () => { opened7 = true; ws7.send("Hello WSS!") }
|
|
150
150
|
ws7.onmessage = () => { received7 = true; ws7.close(1000, "done") }
|
|
151
151
|
ws7.onclose = (e) => {
|