quickwin 3.0.1 → 3.0.3
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 -0
- package/README.md +2 -0
- package/package.json +2 -3
- 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/examples/PdfCanvas.js +0 -38
- package/examples/PdfCanvas.tsx +0 -54
- package/examples/execd/main.js +0 -23379
- package/examples/execd/tray.js +0 -192
- package/examples/execd/worker.js +0 -122
- package/examples/pdf_preview2.js +0 -398
- package/examples/pdf_preview2.ts +0 -353
- package/examples/pdf_viewer.js +0 -22515
- package/examples/pdf_viewer.tsx +0 -228
- package/examples/test_gallery.js +0 -18692
- package/examples/test_gallery.tsx +0 -291
- package/examples/test_react_lazy.js +0 -18560
- package/examples/test_react_lazy.tsx +0 -65
- package/examples/test_react_listbox.js +0 -18562
- package/examples/test_react_listbox.tsx +0 -73
- package/examples/test_root_window.js +0 -18532
- package/examples/test_root_window.tsx +0 -41
- package/examples/test_tab.js +0 -18563
- package/examples/test_tab.tsx +0 -77
- package/examples/tray_demo.js +0 -86
- package/examples/tray_demo.tsx +0 -89
- package/test/add.wasm +0 -0
- package/test/bench_ffi_callraw.js +0 -235
- package/test/bench_ffi_callraw.ts +0 -235
- package/test/bench_ffi_overhead.js +0 -139
- package/test/bench_ffi_overhead.ts +0 -141
- package/test/bench_sendmessage.js +0 -104
- package/test/bench_sendmessage.ts +0 -100
- package/test/bench_sendmessage_nonnull.js +0 -187
- package/test/bench_sendmessage_nonnull.ts +0 -188
- package/test/complex.wasm +0 -0
- package/test/complex_imports.wasm +0 -0
- package/test/debug/http_server_e2e.js +0 -31
- package/test/debug/minwasm.js +0 -52
- package/test/debug/mupdf_debug.js +0 -86
- package/test/debug/wasm.js +0 -122
- package/test/debug/wasm_export_debug.js +0 -54
- package/test/debug/wasm_names.js +0 -36
- package/test/example.pdf +0 -0
- package/test/global_imports.wasm +0 -0
- package/test/import_func.wasm +0 -0
- package/test/imports.wasm +0 -0
- package/test/mupdf-cdn.js +0 -40
- package/test/mupdf-cdn.ts +0 -43
- package/test/run.js +0 -111
- package/test/run.ts +0 -116
- package/test/sjlj.wasm +0 -0
- package/test/test_basic.js +0 -7
- package/test/test_basic.ts +0 -9
- package/test/test_basic_fetch.js +0 -63
- package/test/test_basic_fetch.ts +0 -59
- package/test/test_brotli.js +0 -48
- package/test/test_brotli.ts +0 -52
- package/test/test_fetch_cache.js +0 -169
- package/test/test_fetch_cache.ts +0 -187
- package/test/test_ffi.js +0 -157
- package/test/test_ffi.ts +0 -170
- package/test/test_ffi_abi.js +0 -290
- package/test/test_ffi_abi.ts +0 -274
- package/test/test_ffi_array.js +0 -116
- package/test/test_ffi_array.ts +0 -141
- package/test/test_ffi_bind.js +0 -656
- package/test/test_ffi_bind.ts +0 -670
- package/test/test_ffi_struct.js +0 -779
- package/test/test_ffi_struct.ts +0 -828
- package/test/test_ffi_struct_layout.js +0 -495
- package/test/test_ffi_struct_layout.ts +0 -543
- package/test/test_ffi_user32.js +0 -103
- package/test/test_ffi_user32.ts +0 -113
- package/test/test_ffi_windows.js +0 -172
- package/test/test_ffi_windows.ts +0 -187
- package/test/test_files.js +0 -102
- package/test/test_files.ts +0 -121
- package/test/test_frame_encoding.js +0 -128
- package/test/test_frame_encoding.ts +0 -132
- package/test/test_gdi_draw.js +0 -681
- package/test/test_gdi_draw.ts +0 -725
- package/test/test_helper.js +0 -91
- package/test/test_helper.ts +0 -88
- package/test/test_http_import.js +0 -96
- package/test/test_http_import.ts +0 -93
- package/test/test_http_server.js +0 -214
- package/test/test_http_server.ts +0 -211
- package/test/test_import_dir.js +0 -4
- package/test/test_import_dir.ts +0 -5
- package/test/test_ipv6.js +0 -134
- package/test/test_ipv6.ts +0 -130
- package/test/test_jsx.js +0 -1314
- package/test/test_meta_url.js +0 -3
- package/test/test_meta_url.ts +0 -4
- package/test/test_mupdf_render.js +0 -51
- package/test/test_mupdf_render.ts +0 -55
- package/test/test_mupdf_twice.js +0 -62
- package/test/test_mupdf_twice.ts +0 -65
- package/test/test_mupdf_wasm.js +0 -39
- package/test/test_mupdf_wasm.ts +0 -32
- package/test/test_net_event.js +0 -57
- package/test/test_net_event.ts +0 -56
- package/test/test_net_fetch.js +0 -384
- package/test/test_net_fetch.ts +0 -373
- package/test/test_net_websocket.js +0 -180
- package/test/test_net_websocket.ts +0 -164
- package/test/test_polyfill.js +0 -144
- package/test/test_polyfill.ts +0 -159
- package/test/test_printer.js +0 -137
- package/test/test_printer.ts +0 -148
- package/test/test_proc.js +0 -141
- package/test/test_proc.ts +0 -163
- package/test/test_react_complex.js +0 -18678
- package/test/test_react_counter.js +0 -18555
- package/test/test_react_flex.js +0 -18871
- package/test/test_react_hidden.js +0 -18560
- package/test/test_react_listview_link.js +0 -18590
- package/test/test_react_notify.js +0 -18797
- package/test/test_react_render.js +0 -18550
- package/test/test_registry.js +0 -74
- package/test/test_registry.ts +0 -95
- package/test/test_sysinfo.js +0 -122
- package/test/test_sysinfo.ts +0 -141
- package/test/test_url.js +0 -173
- package/test/test_url.ts +0 -183
- package/test/test_wasm_basic.js +0 -85
- package/test/test_wasm_basic.ts +0 -73
- package/test/test_wasm_import_global.js +0 -41
- package/test/test_wasm_import_global.ts +0 -45
- package/test/test_wasm_sjlj.js +0 -145
- package/test/test_wasm_sjlj.ts +0 -125
- package/test/test_wasm_types.js +0 -97
- package/test/test_wasm_types.ts +0 -124
- package/test/test_wolfssl_read.js +0 -203
- package/test/test_wolfssl_read.ts +0 -184
- package/test/test_worker.js +0 -21
- package/test/test_worker.ts +0 -24
- package/test/test_worker_concurrent.js +0 -45
- package/test/test_worker_concurrent.ts +0 -56
- package/test/test_worker_data_url.js +0 -46
- package/test/test_worker_data_url.ts +0 -51
- package/test/test_worker_fetch.js +0 -26
- package/test/test_worker_fetch.ts +0 -27
- package/test/test_worker_net.js +0 -69
- package/test/test_worker_net.ts +0 -66
- package/test/test_worker_wasm_concurrent.js +0 -44
- package/test/test_worker_wasm_concurrent.ts +0 -45
- package/test/types.wasm +0 -0
- package/test/worker_fetch.js +0 -13
- package/test/worker_fetch.ts +0 -13
- package/test/worker_helper.js +0 -1
- package/test/worker_helper.ts +0 -1
- package/test/worker_http.js +0 -18
- package/test/worker_http.ts +0 -20
- package/test/worker_import_lib.js +0 -12
- package/test/worker_import_lib.ts +0 -13
- package/test/worker_module.js +0 -16
- package/test/worker_module.ts +0 -17
- package/test/worker_wasm_concurrent.js +0 -22
- package/test/worker_wasm_concurrent.ts +0 -22
package/test/test_net_fetch.ts
DELETED
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
import '../lib/fetch.js'
|
|
2
|
-
import * as std from 'std'
|
|
3
|
-
import * as os from 'os'
|
|
4
|
-
import { Tester } from './test_helper.js'
|
|
5
|
-
|
|
6
|
-
export const suite = {
|
|
7
|
-
name: 'net-fetch',
|
|
8
|
-
run: async (t: Tester) => {
|
|
9
|
-
function assert(name: string, ok: boolean): void {
|
|
10
|
-
if (ok) { t.ok++; std.printf(' PASS: %s\n', name) }
|
|
11
|
-
else { t.fail++; std.printf(' FAIL: %s\n', name) }
|
|
12
|
-
}
|
|
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.
|
|
18
|
-
async function safeFetch(url: any, init?: RequestInit) {
|
|
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
|
-
}
|
|
27
|
-
}
|
|
28
|
-
std.printf(' fetch %s failed after 3 attempts: %s\n', String(url), last)
|
|
29
|
-
return null
|
|
30
|
-
}
|
|
31
|
-
const BASE = 'http://localhost:18923'
|
|
32
|
-
|
|
33
|
-
// ── Plain HTTP GET (regression: slot 0 + !s bug) ──
|
|
34
|
-
t.section('plain HTTP')
|
|
35
|
-
const r0 = await safeFetch(BASE + '/', { timeout: 5000 })
|
|
36
|
-
if (r0) {
|
|
37
|
-
assert('plain HTTP status 200', r0.status === 200)
|
|
38
|
-
const body = await r0.text()
|
|
39
|
-
assert('plain HTTP body non-empty', body.length > 0)
|
|
40
|
-
assert('plain HTTP body matches', body === 'hello from test server')
|
|
41
|
-
} else {
|
|
42
|
-
assert('plain HTTP endpoint reachable', false)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ── Basic HTTP GET ──
|
|
46
|
-
t.section('HTTP GET')
|
|
47
|
-
const r1 = await safeFetch(BASE + '/any')
|
|
48
|
-
if (r1) {
|
|
49
|
-
assert('status received', r1.status > 0)
|
|
50
|
-
const body = await r1.text()
|
|
51
|
-
assert('body received', body.length > 0)
|
|
52
|
-
} else {
|
|
53
|
-
assert('local server reachable', false)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// ── Query string ──
|
|
57
|
-
t.section('query string')
|
|
58
|
-
const rq = await safeFetch(BASE + '/any?foo=bar&baz=42')
|
|
59
|
-
if (rq) {
|
|
60
|
-
const body = JSON.parse(await rq.text())
|
|
61
|
-
assert('query args received', body.args !== undefined)
|
|
62
|
-
assert('?foo=bar preserved', body.args.foo === 'bar')
|
|
63
|
-
assert('?baz=42 preserved', body.args.baz === '42')
|
|
64
|
-
} else {
|
|
65
|
-
assert('query string endpoint reachable', false)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// ── Request constructor ──
|
|
69
|
-
t.section('Request constructor')
|
|
70
|
-
{
|
|
71
|
-
const r = new Request('https://httpbun.com/any?x=1&y=2', { method: 'POST', headers: { 'X-Test': 'val' }, body: 'hello' })
|
|
72
|
-
assert('Request.url', r.url === 'https://httpbun.com/any?x=1&y=2')
|
|
73
|
-
assert('Request.method', r.method === 'POST')
|
|
74
|
-
assert('Request.headers get', r.headers.get('x-test') === 'val')
|
|
75
|
-
assert('Request.body is ReadableStream', r.body !== null && typeof r.body.getReader === 'function')
|
|
76
|
-
}
|
|
77
|
-
{
|
|
78
|
-
const r1 = new Request('https://example.com/path')
|
|
79
|
-
assert('Request default method GET', r1.method === 'GET')
|
|
80
|
-
assert('Request default redirect follow', r1.redirect === 'follow')
|
|
81
|
-
}
|
|
82
|
-
{
|
|
83
|
-
const r2 = new Request('https://httpbun.com/any')
|
|
84
|
-
const r3 = new Request(r2, { method: 'PUT' })
|
|
85
|
-
assert('Request clone same url', r3.url === r2.url)
|
|
86
|
-
assert('Request clone override method', r3.method === 'PUT')
|
|
87
|
-
}
|
|
88
|
-
{
|
|
89
|
-
const r = await safeFetch(new Request(BASE + '/any', { method: 'POST', headers: { 'Content-Type': 'text/plain' }, body: 'req-class' }))
|
|
90
|
-
if (r) {
|
|
91
|
-
const body = JSON.parse(await r.text())
|
|
92
|
-
assert('fetch(Request) status', r.status > 0)
|
|
93
|
-
assert('fetch(Request) method POST', body.method === 'POST')
|
|
94
|
-
assert('fetch(Request) body received', body.data === 'req-class')
|
|
95
|
-
assert('fetch(Request) content-type', body.headers['content-type'] === 'text/plain')
|
|
96
|
-
} else {
|
|
97
|
-
assert('fetch(Request) endpoint reachable', false)
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// ── body / bodyUsed / stream ──
|
|
102
|
-
t.section('body / bodyUsed / stream')
|
|
103
|
-
const r2 = await safeFetch(BASE + '/any')
|
|
104
|
-
if (r2) {
|
|
105
|
-
assert('r.body exists', typeof r2.body === 'object' && r2.body !== null)
|
|
106
|
-
assert('bodyUsed false before read', r2.bodyUsed === false)
|
|
107
|
-
|
|
108
|
-
const reader = r2.body!.getReader()
|
|
109
|
-
let total = 0
|
|
110
|
-
while (true) {
|
|
111
|
-
const { done, value } = await reader.read()
|
|
112
|
-
if (done) break
|
|
113
|
-
total += value.length
|
|
114
|
-
}
|
|
115
|
-
assert('reader streams all bytes', total > 0)
|
|
116
|
-
assert('bodyUsed true after stream', r2.bodyUsed === true)
|
|
117
|
-
} else {
|
|
118
|
-
assert('body test endpoint reachable', false)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ── text/json/arrayBuffer ──
|
|
122
|
-
t.section('text / json / arrayBuffer')
|
|
123
|
-
const r3 = await safeFetch(BASE + '/any')
|
|
124
|
-
if (r3) {
|
|
125
|
-
const text = await r3.text()
|
|
126
|
-
assert('text() returns string', typeof text === 'string' && text.length > 0)
|
|
127
|
-
assert('bodyUsed true after text()', r3.bodyUsed === true)
|
|
128
|
-
|
|
129
|
-
const r4 = await safeFetch(BASE + '/any')
|
|
130
|
-
if (r4) {
|
|
131
|
-
const buf = await r4.arrayBuffer()
|
|
132
|
-
assert('arrayBuffer() returns bytes', buf.byteLength > 0)
|
|
133
|
-
}
|
|
134
|
-
} else {
|
|
135
|
-
assert('text/json/ab endpoint reachable', false)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// double text() throws
|
|
139
|
-
t.section('bodyUsed throws')
|
|
140
|
-
const r5 = await safeFetch(BASE + '/any')
|
|
141
|
-
if (r5) {
|
|
142
|
-
await r5.text()
|
|
143
|
-
try { await r5.text(); assert('double text() throws', false) }
|
|
144
|
-
catch (e: unknown) { assert('double text() throws TypeError', (e as Error).message === 'Body already used') }
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// ── POST ──
|
|
148
|
-
t.section('POST')
|
|
149
|
-
const r6 = await safeFetch(BASE + '/any', {
|
|
150
|
-
method: 'POST',
|
|
151
|
-
headers: { 'Content-Type': 'application/json' },
|
|
152
|
-
body: JSON.stringify({ msg: 'hello' })
|
|
153
|
-
})
|
|
154
|
-
if (r6) {
|
|
155
|
-
const body = await r6.text()
|
|
156
|
-
assert('POST body received', body.length > 0)
|
|
157
|
-
} else {
|
|
158
|
-
assert('POST endpoint reachable', false)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// ── Headers (local, no network) ──
|
|
162
|
-
t.section('Headers (local)')
|
|
163
|
-
const h = new Headers({ 'Content-Type': 'text/html', 'X-Custom': 'hello', 'Accept': '*/*' })
|
|
164
|
-
let c = 0
|
|
165
|
-
for (const [k, v] of h) { c++; if (typeof k !== 'string' || typeof v !== 'string') assert('for...of type', false) }
|
|
166
|
-
assert('for...of yields entries', c === 3)
|
|
167
|
-
assert('get()', h.get('content-type') === 'text/html')
|
|
168
|
-
assert('get() case-insensitive', h.get('Content-Type') === 'text/html')
|
|
169
|
-
assert('has()', h.has('x-custom'))
|
|
170
|
-
assert('set() overwrites', (h.set('accept', 'application/json'), h.get('accept') === 'application/json'))
|
|
171
|
-
assert('append() adds', (h.append('accept', 'text/plain'), h.get('accept') === 'application/json, text/plain'))
|
|
172
|
-
assert('delete()', (h.delete('accept'), h.has('accept') === false))
|
|
173
|
-
|
|
174
|
-
const h2 = new Headers({ 'a': '1', 'b': '2' })
|
|
175
|
-
let ec = 0; for (const _ of h2.entries()) { ec++ }
|
|
176
|
-
assert('entries()', ec === 2)
|
|
177
|
-
let kc = 0; for (const _ of h2.keys()) { kc++ }
|
|
178
|
-
assert('keys()', kc === 2)
|
|
179
|
-
let vc = 0; for (const _ of h2.values()) { vc++ }
|
|
180
|
-
assert('values()', vc === 2)
|
|
181
|
-
|
|
182
|
-
// ── Chunked Transfer-Encoding ──
|
|
183
|
-
t.section('chunked transfer encoding')
|
|
184
|
-
const r8 = await safeFetch(BASE + '/anything')
|
|
185
|
-
if (r8) {
|
|
186
|
-
assert('chunked status 200', r8.status === 200)
|
|
187
|
-
const body = await r8.json<{ url: string }>()
|
|
188
|
-
assert('chunked json has url', typeof body.url === 'string' && body.url.length > 0)
|
|
189
|
-
} else {
|
|
190
|
-
assert('local server reachable', false)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// ── stream cancel ──
|
|
194
|
-
t.section('stream cancel')
|
|
195
|
-
const r7 = await safeFetch(BASE + '/any')
|
|
196
|
-
if (r7) {
|
|
197
|
-
r7.body!.cancel()
|
|
198
|
-
assert('cancel() succeeds', true)
|
|
199
|
-
// After cancel: buffered chunks still readable, then done
|
|
200
|
-
const reader = r7.body!.getReader()
|
|
201
|
-
let finalDone = false
|
|
202
|
-
while (true) {
|
|
203
|
-
const { done } = await reader.read()
|
|
204
|
-
if (done) { finalDone = true; break }
|
|
205
|
-
}
|
|
206
|
-
assert('reader done after cancel', finalDone === true)
|
|
207
|
-
} else {
|
|
208
|
-
assert('cancel test endpoint reachable', false)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// ── UTF-8 Chinese text body ──
|
|
212
|
-
t.section('utf-8 chinese text')
|
|
213
|
-
const r9 = await safeFetch(BASE + '/base64/5Lit5paH')
|
|
214
|
-
if (r9) {
|
|
215
|
-
assert('chinese status 200', r9.status === 200)
|
|
216
|
-
const body = await r9.text()
|
|
217
|
-
assert('chinese body is 中文', body === '\u4e2d\u6587')
|
|
218
|
-
assert('chinese body length=2', body.length === 2)
|
|
219
|
-
} else {
|
|
220
|
-
assert('local server reachable', false)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// ── POST with Chinese body ──
|
|
224
|
-
t.section('post with chinese body')
|
|
225
|
-
const r10 = await safeFetch(BASE + '/post', {
|
|
226
|
-
method: 'POST',
|
|
227
|
-
body: '\u4e2d\u6587',
|
|
228
|
-
headers: { 'Content-Type': 'text/plain; charset=utf-8' }
|
|
229
|
-
})
|
|
230
|
-
if (r10) {
|
|
231
|
-
assert('post status 200', r10.status === 200)
|
|
232
|
-
const body = await r10.text()
|
|
233
|
-
assert('post body contains 中文', body.includes('\u4e2d\u6587'))
|
|
234
|
-
} else {
|
|
235
|
-
assert('local server reachable', false)
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// ── HTTPS fetch ──
|
|
239
|
-
t.section('HTTPS fetch')
|
|
240
|
-
const r11 = await safeFetch('https://localhost:18924/', { timeout: 5000, rejectUnauthorized: false })
|
|
241
|
-
if (r11) {
|
|
242
|
-
assert('https status 200', r11.status === 200)
|
|
243
|
-
const body = await r11.text()
|
|
244
|
-
assert('https body matches', body === 'hello from test server')
|
|
245
|
-
} else {
|
|
246
|
-
assert('https endpoint reachable', false)
|
|
247
|
-
}
|
|
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
|
-
|
|
301
|
-
// ── large HTTPS body (wolfSSL_read int contract) ──
|
|
302
|
-
// read returns the raw int; -1/WANT_READ must never leak into
|
|
303
|
-
// byte accounting (regression: a leaked -1 made receivedBytes
|
|
304
|
-
// NaN → stream never closed → hang).
|
|
305
|
-
t.section('large HTTPS body')
|
|
306
|
-
{
|
|
307
|
-
const LARGE = 200000
|
|
308
|
-
try {
|
|
309
|
-
const r = await Promise.race([
|
|
310
|
-
fetch(`https://localhost:18924/large/${LARGE}`, { timeout: 15000, rejectUnauthorized: false }),
|
|
311
|
-
new Promise<never>((_, rej) =>
|
|
312
|
-
os.setTimeout(() => rej(new Error('Body hang watchdog 15s')), 15000)
|
|
313
|
-
)
|
|
314
|
-
])
|
|
315
|
-
assert('large HTTPS status 200', r.status === 200)
|
|
316
|
-
const buf = await Promise.race([
|
|
317
|
-
r.arrayBuffer(),
|
|
318
|
-
new Promise<never>((_, rej) =>
|
|
319
|
-
os.setTimeout(() => rej(new Error('arrayBuffer hang watchdog 15s')), 15000)
|
|
320
|
-
)
|
|
321
|
-
])
|
|
322
|
-
assert('large HTTPS byteLength exact', buf.byteLength === LARGE)
|
|
323
|
-
} catch (e: unknown) {
|
|
324
|
-
assert('large HTTPS completed without hang (' + String((e as Error).message) + ')', false)
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// ── large HTTP body (sock.recv path same null contract) ──
|
|
329
|
-
t.section('large HTTP body')
|
|
330
|
-
{
|
|
331
|
-
const LARGE = 200000
|
|
332
|
-
try {
|
|
333
|
-
const r = await Promise.race([
|
|
334
|
-
fetch(`http://localhost:18923/large/${LARGE}`, { timeout: 15000 }),
|
|
335
|
-
new Promise<never>((_, rej) =>
|
|
336
|
-
os.setTimeout(() => rej(new Error('Body hang watchdog 15s')), 15000)
|
|
337
|
-
)
|
|
338
|
-
])
|
|
339
|
-
assert('large HTTP status 200', r.status === 200)
|
|
340
|
-
const buf = await Promise.race([
|
|
341
|
-
r.arrayBuffer(),
|
|
342
|
-
new Promise<never>((_, rej) =>
|
|
343
|
-
os.setTimeout(() => rej(new Error('arrayBuffer hang watchdog 15s')), 15000)
|
|
344
|
-
)
|
|
345
|
-
])
|
|
346
|
-
assert('large HTTP byteLength exact', buf.byteLength === LARGE)
|
|
347
|
-
} catch (e: unknown) {
|
|
348
|
-
assert('large HTTP completed (' + String((e as Error).message) + ')', false)
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// ── body timeout after headers (armTimeout after doResolve) ──
|
|
353
|
-
t.section('HTTPS body timeout')
|
|
354
|
-
{
|
|
355
|
-
let rejected = false
|
|
356
|
-
let msg = ''
|
|
357
|
-
try {
|
|
358
|
-
const r = await fetch('https://localhost:18924/stall', { timeout: 2000, rejectUnauthorized: false })
|
|
359
|
-
await Promise.race([
|
|
360
|
-
r.arrayBuffer(),
|
|
361
|
-
new Promise<never>((_, rej) =>
|
|
362
|
-
os.setTimeout(() => rej(new Error('Body timeout')), 8000)
|
|
363
|
-
)
|
|
364
|
-
])
|
|
365
|
-
} catch (e: unknown) {
|
|
366
|
-
rejected = true
|
|
367
|
-
msg = String((e as Error).message)
|
|
368
|
-
}
|
|
369
|
-
assert('stall body rejects (timeout)', rejected)
|
|
370
|
-
assert('stall error mentions timeout', /timeout/i.test(msg))
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import '../lib/websocket.js';
|
|
2
|
-
import * as std from 'std';
|
|
3
|
-
import * as os from 'os';
|
|
4
|
-
export const suite = {
|
|
5
|
-
name: 'net-websocket',
|
|
6
|
-
run: async (t) => {
|
|
7
|
-
function assert(name, ok) {
|
|
8
|
-
if (ok) {
|
|
9
|
-
t.ok++;
|
|
10
|
-
std.printf(' PASS: %s\n', name);
|
|
11
|
-
}
|
|
12
|
-
else {
|
|
13
|
-
t.fail++;
|
|
14
|
-
std.printf(' FAIL: %s\n', name);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
let testId = 0;
|
|
18
|
-
const resolvers = {};
|
|
19
|
-
function waitForTest() {
|
|
20
|
-
const id = ++testId;
|
|
21
|
-
const promise = new Promise((resolve) => { resolvers[id] = resolve; });
|
|
22
|
-
return { promise, id };
|
|
23
|
-
}
|
|
24
|
-
function finishTest(id) {
|
|
25
|
-
const r = resolvers[id];
|
|
26
|
-
if (r) {
|
|
27
|
-
delete resolvers[id];
|
|
28
|
-
r();
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
// ── wss basic ──
|
|
32
|
-
t.section('wss basic text message');
|
|
33
|
-
const p1 = waitForTest();
|
|
34
|
-
let opened = false, received = false, closed = false;
|
|
35
|
-
const ws1 = new WebSocket("ws://localhost:18923");
|
|
36
|
-
ws1.onopen = () => { opened = true; ws1.send("Hello WebSocket!"); };
|
|
37
|
-
ws1.onmessage = () => { received = true; ws1.close(1000, "done"); };
|
|
38
|
-
ws1.onclose = (e) => {
|
|
39
|
-
closed = true;
|
|
40
|
-
assert('onopen fired', opened);
|
|
41
|
-
assert('onmessage received', received);
|
|
42
|
-
assert('close code=1000', e.code === 1000);
|
|
43
|
-
assert('wasClean', e.wasClean);
|
|
44
|
-
finishTest(p1.id);
|
|
45
|
-
};
|
|
46
|
-
ws1.onerror = () => { assert('no error', false); finishTest(p1.id); };
|
|
47
|
-
const timer1 = os.setTimeout(() => { if (!closed) {
|
|
48
|
-
assert('no timeout', false);
|
|
49
|
-
finishTest(p1.id);
|
|
50
|
-
} }, 10000);
|
|
51
|
-
await p1.promise;
|
|
52
|
-
os.clearTimeout(timer1);
|
|
53
|
-
// ── binary send ──
|
|
54
|
-
t.section('binary message (ArrayBuffer)');
|
|
55
|
-
const pBin = waitForTest();
|
|
56
|
-
const wsBin = new WebSocket("ws://localhost:18923");
|
|
57
|
-
let binClose = false;
|
|
58
|
-
wsBin.onopen = () => {
|
|
59
|
-
const buf = new ArrayBuffer(5);
|
|
60
|
-
const v = new Uint8Array(buf);
|
|
61
|
-
v[0] = 0x48;
|
|
62
|
-
v[1] = 0x65;
|
|
63
|
-
v[2] = 0x6C;
|
|
64
|
-
v[3] = 0x6C;
|
|
65
|
-
v[4] = 0x6F;
|
|
66
|
-
wsBin.send(buf);
|
|
67
|
-
wsBin.send("binary-send-check");
|
|
68
|
-
};
|
|
69
|
-
let binMsgCount = 0;
|
|
70
|
-
wsBin.onmessage = () => { binMsgCount++; if (binMsgCount >= 2)
|
|
71
|
-
wsBin.close(1000, "done"); };
|
|
72
|
-
wsBin.onclose = () => { binClose = true; assert('binary send completed', true); finishTest(pBin.id); };
|
|
73
|
-
wsBin.onerror = () => { assert('no error', false); finishTest(pBin.id); };
|
|
74
|
-
const timerBin = os.setTimeout(() => { if (!binClose)
|
|
75
|
-
assert('binary no crash (timeout)', true); finishTest(pBin.id); }, 10000);
|
|
76
|
-
await pBin.promise;
|
|
77
|
-
os.clearTimeout(timerBin);
|
|
78
|
-
// ── extended length frame ──
|
|
79
|
-
t.section('extended length frame (>65535 bytes)');
|
|
80
|
-
const pExt = waitForTest();
|
|
81
|
-
const wsExt = new WebSocket("ws://localhost:18923");
|
|
82
|
-
let extClose = false;
|
|
83
|
-
wsExt.onopen = () => { wsExt.send("X".repeat(66000)); };
|
|
84
|
-
wsExt.onmessage = () => { wsExt.close(1000, "done"); };
|
|
85
|
-
wsExt.onclose = () => { extClose = true; assert('extended frame sent', true); finishTest(pExt.id); };
|
|
86
|
-
wsExt.onerror = () => { assert('no error', false); finishTest(pExt.id); };
|
|
87
|
-
const timerExt = os.setTimeout(() => { if (!extClose)
|
|
88
|
-
assert('extended frame timeout', true); finishTest(pExt.id); }, 10000);
|
|
89
|
-
await pExt.promise;
|
|
90
|
-
os.clearTimeout(timerExt);
|
|
91
|
-
// ── large message ──
|
|
92
|
-
t.section('large message (>125 bytes)');
|
|
93
|
-
const p2 = waitForTest();
|
|
94
|
-
const ws2 = new WebSocket("ws://localhost:18923");
|
|
95
|
-
let largeOk = false, closed2 = false;
|
|
96
|
-
const largeStr = "A".repeat(1000);
|
|
97
|
-
ws2.onopen = () => { ws2.send(largeStr); };
|
|
98
|
-
ws2.onmessage = (e) => { largeOk = e.data.length === 1000; ws2.close(1000, "done"); };
|
|
99
|
-
ws2.onclose = () => { closed2 = true; assert('large msg echo 1000 bytes', largeOk); finishTest(p2.id); };
|
|
100
|
-
ws2.onerror = () => { assert('no error', false); finishTest(p2.id); };
|
|
101
|
-
const timer2 = os.setTimeout(() => { if (!closed2) {
|
|
102
|
-
assert('large msg no timeout', false);
|
|
103
|
-
finishTest(p2.id);
|
|
104
|
-
} }, 15000);
|
|
105
|
-
await p2.promise;
|
|
106
|
-
os.clearTimeout(timer2);
|
|
107
|
-
// ── multiple messages ──
|
|
108
|
-
t.section('multiple messages');
|
|
109
|
-
const p3 = waitForTest();
|
|
110
|
-
const ws3 = new WebSocket("ws://localhost:18923");
|
|
111
|
-
let count = 0, closed3 = false;
|
|
112
|
-
ws3.onopen = () => { ws3.send("a"); ws3.send("b"); ws3.send("c"); };
|
|
113
|
-
ws3.onmessage = () => { count++; if (count === 3)
|
|
114
|
-
ws3.close(1000, "done"); };
|
|
115
|
-
ws3.onclose = () => { closed3 = true; assert('received 3 messages', count === 3); finishTest(p3.id); };
|
|
116
|
-
ws3.onerror = () => { assert('no error', false); finishTest(p3.id); };
|
|
117
|
-
const timer3 = os.setTimeout(() => { if (!closed3) {
|
|
118
|
-
assert('no timeout', false);
|
|
119
|
-
finishTest(p3.id);
|
|
120
|
-
} }, 15000);
|
|
121
|
-
await p3.promise;
|
|
122
|
-
os.clearTimeout(timer3);
|
|
123
|
-
// ── invalid URL ──
|
|
124
|
-
t.section('invalid URL');
|
|
125
|
-
const p4 = waitForTest();
|
|
126
|
-
const ws4 = new WebSocket("not-a-websocket");
|
|
127
|
-
let err4 = false;
|
|
128
|
-
ws4.onerror = () => { err4 = true; };
|
|
129
|
-
ws4.onclose = () => { assert('onerror fired', err4); finishTest(p4.id); };
|
|
130
|
-
const timer4 = os.setTimeout(() => { assert('invalid url error', err4); finishTest(p4.id); }, 3000);
|
|
131
|
-
await p4.promise;
|
|
132
|
-
os.clearTimeout(timer4);
|
|
133
|
-
// ── invalid scheme ──
|
|
134
|
-
t.section('invalid scheme (http://)');
|
|
135
|
-
const p5 = waitForTest();
|
|
136
|
-
const ws5 = new WebSocket("http://example.com/ws");
|
|
137
|
-
let err5 = false;
|
|
138
|
-
ws5.onerror = () => { err5 = true; };
|
|
139
|
-
ws5.onclose = () => { assert('http scheme onerror', err5); finishTest(p5.id); };
|
|
140
|
-
const timer5 = os.setTimeout(() => { assert('http scheme error guard', err5); finishTest(p5.id); }, 3000);
|
|
141
|
-
await p5.promise;
|
|
142
|
-
os.clearTimeout(timer5);
|
|
143
|
-
// ── early send/close ──
|
|
144
|
-
t.section('send/close before onopen');
|
|
145
|
-
const p6 = waitForTest();
|
|
146
|
-
const ws6 = new WebSocket("ws://localhost:18923");
|
|
147
|
-
ws6.send("noop");
|
|
148
|
-
ws6.close(1000, "early");
|
|
149
|
-
let close6 = false;
|
|
150
|
-
ws6.onopen = () => { assert('onopen after early close', false); };
|
|
151
|
-
ws6.onclose = () => { close6 = true; assert('early close ok', true); finishTest(p6.id); };
|
|
152
|
-
ws6.onerror = () => { };
|
|
153
|
-
const timer6 = os.setTimeout(() => { if (!close6)
|
|
154
|
-
assert('no crash', true); finishTest(p6.id); }, 5000);
|
|
155
|
-
await p6.promise;
|
|
156
|
-
os.clearTimeout(timer6);
|
|
157
|
-
// ── WSS (Secure WebSocket) ──
|
|
158
|
-
t.section('wss basic text message');
|
|
159
|
-
const p7 = waitForTest();
|
|
160
|
-
let opened7 = false, received7 = false, closed7 = false;
|
|
161
|
-
const ws7 = new WebSocket("wss://localhost:18924", { rejectUnauthorized: false });
|
|
162
|
-
ws7.onopen = () => { opened7 = true; ws7.send("Hello WSS!"); };
|
|
163
|
-
ws7.onmessage = () => { received7 = true; ws7.close(1000, "done"); };
|
|
164
|
-
ws7.onclose = (e) => {
|
|
165
|
-
closed7 = true;
|
|
166
|
-
assert('wss onopen fired', opened7);
|
|
167
|
-
assert('wss onmessage received', received7);
|
|
168
|
-
assert('wss close code=1000', e.code === 1000);
|
|
169
|
-
assert('wss wasClean', e.wasClean);
|
|
170
|
-
finishTest(p7.id);
|
|
171
|
-
};
|
|
172
|
-
ws7.onerror = () => { assert('wss no error', false); finishTest(p7.id); };
|
|
173
|
-
const timer7 = os.setTimeout(() => { if (!closed7) {
|
|
174
|
-
assert('wss no timeout', false);
|
|
175
|
-
finishTest(p7.id);
|
|
176
|
-
} }, 10000);
|
|
177
|
-
await p7.promise;
|
|
178
|
-
os.clearTimeout(timer7);
|
|
179
|
-
}
|
|
180
|
-
};
|