quickwin 2026.5.2-3.145209
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.md +6 -0
- package/examples/pdf_preview.js +440 -0
- package/examples/pdf_preview.ts +470 -0
- package/examples/preact_demo.js +35 -0
- package/examples/preact_demo.tsx +49 -0
- package/examples/tray_demo.js +75 -0
- package/examples/tray_demo.tsx +79 -0
- package/lib/fetch.js +746 -0
- package/lib/fetch.ts +811 -0
- package/lib/polyfill.js +500 -0
- package/lib/polyfill.ts +454 -0
- package/lib/preact/hooks.js +287 -0
- package/lib/preact/hooks.ts +330 -0
- package/lib/preact/jsx-runtime.js +1 -0
- package/lib/preact/jsx-runtime.ts +2 -0
- package/lib/preact/jsx.d.ts +36 -0
- package/lib/preact/layout.js +153 -0
- package/lib/preact/layout.ts +183 -0
- package/lib/preact/preact.js +54 -0
- package/lib/preact/preact.ts +133 -0
- package/lib/preact/props.js +99 -0
- package/lib/preact/props.ts +119 -0
- package/lib/preact/render.js +320 -0
- package/lib/preact/render.ts +353 -0
- package/lib/websocket.js +540 -0
- package/lib/websocket.ts +574 -0
- package/package.json +32 -0
- package/quickwin.d.ts +657 -0
- package/test/add.wasm +0 -0
- package/test/complex.wasm +0 -0
- package/test/complex_imports.wasm +0 -0
- package/test/global_imports.wasm +0 -0
- package/test/import_func.wasm +0 -0
- package/test/imports.wasm +0 -0
- package/test/run.js +86 -0
- package/test/run.ts +90 -0
- package/test/sjlj.wasm +0 -0
- package/test/test_basic.js +7 -0
- package/test/test_basic.ts +9 -0
- package/test/test_brotli.js +48 -0
- package/test/test_brotli.ts +52 -0
- package/test/test_fetch_cache.js +131 -0
- package/test/test_fetch_cache.ts +141 -0
- package/test/test_ffi.js +157 -0
- package/test/test_ffi.ts +174 -0
- package/test/test_frame_encoding.js +128 -0
- package/test/test_frame_encoding.ts +132 -0
- package/test/test_helper.js +84 -0
- package/test/test_helper.ts +80 -0
- package/test/test_http_import.js +78 -0
- package/test/test_http_import.ts +74 -0
- package/test/test_mupdf_render.js +69 -0
- package/test/test_mupdf_render.ts +74 -0
- package/test/test_mupdf_twice.js +77 -0
- package/test/test_mupdf_twice.ts +81 -0
- package/test/test_mupdf_wasm.js +33 -0
- package/test/test_mupdf_wasm.ts +30 -0
- package/test/test_net_event.js +63 -0
- package/test/test_net_event.ts +59 -0
- package/test/test_net_fetch.js +153 -0
- package/test/test_net_fetch.ts +131 -0
- package/test/test_net_websocket.js +158 -0
- package/test/test_net_websocket.ts +144 -0
- package/test/test_polyfill.js +58 -0
- package/test/test_polyfill.ts +60 -0
- package/test/test_url.js +173 -0
- package/test/test_url.ts +183 -0
- package/test/test_wasm_basic.js +82 -0
- package/test/test_wasm_basic.ts +70 -0
- package/test/test_wasm_import_global.js +41 -0
- package/test/test_wasm_import_global.ts +39 -0
- package/test/test_wasm_sjlj.js +153 -0
- package/test/test_wasm_sjlj.ts +134 -0
- package/test/test_wasm_types.js +96 -0
- package/test/test_wasm_types.ts +108 -0
- package/test/types.wasm +0 -0
- package/tsconfig.json +18 -0
- package/vendor/mupdf-wasm/mupdf-wasm.d.ts +571 -0
- package/vendor/mupdf-wasm/mupdf-wasm.js +2749 -0
- package/vendor/mupdf-wasm/mupdf-wasm.wasm +0 -0
- package/vendor/mupdf-wasm/mupdf.d.ts +939 -0
- package/vendor/mupdf-wasm/mupdf.js +3317 -0
- package/win-mingw64.exe +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as sock from "sock"
|
|
2
|
+
import * as std from "std"
|
|
3
|
+
import * as os from "os"
|
|
4
|
+
import { Tester } from './test_helper.js'
|
|
5
|
+
|
|
6
|
+
interface WSAEvent {
|
|
7
|
+
lNetworkEvents: number;
|
|
8
|
+
iErrorCode: number[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const suite = {
|
|
12
|
+
name: 'net-event',
|
|
13
|
+
run: async (t: Tester): Promise<void> => {
|
|
14
|
+
await new Promise<void>((resolve, reject) => {
|
|
15
|
+
const s = sock.socket()
|
|
16
|
+
if (!s || s === 0) { t.fail++; reject(new Error('socket() failed')); return }
|
|
17
|
+
|
|
18
|
+
var connected = false, gotData = false
|
|
19
|
+
|
|
20
|
+
const hostIP = sock.resolve("httpbin.org")
|
|
21
|
+
if (!hostIP) { t.fail++; reject(new Error('dns failed')); return }
|
|
22
|
+
|
|
23
|
+
const timeoutId = os.setTimeout(() => {
|
|
24
|
+
if (!connected) {
|
|
25
|
+
sock.closesocket(s)
|
|
26
|
+
reject(new Error('connect timeout'))
|
|
27
|
+
}
|
|
28
|
+
}, 10000)
|
|
29
|
+
|
|
30
|
+
sock.set_on_event(s, (event: WSAEvent) => {
|
|
31
|
+
if (event.lNetworkEvents & sock.FD_CONNECT) {
|
|
32
|
+
if (event.iErrorCode[0] === 0) {
|
|
33
|
+
connected = true; t.ok++
|
|
34
|
+
os.clearTimeout(timeoutId)
|
|
35
|
+
const req = "GET / HTTP/1.1\r\nHost: httpbin.org\r\nConnection: close\r\n\r\n"
|
|
36
|
+
const buf = new ArrayBuffer(req.length)
|
|
37
|
+
const v = new Uint8Array(buf)
|
|
38
|
+
for (let i = 0; i < req.length; i++) v[i] = req.charCodeAt(i)
|
|
39
|
+
sock.send(s, buf)
|
|
40
|
+
} else {
|
|
41
|
+
sock.closesocket(s)
|
|
42
|
+
reject(new Error('connect error: ' + event.iErrorCode[0]))
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (event.lNetworkEvents & sock.FD_READ && connected && !gotData) {
|
|
46
|
+
const data = sock.recv(s, 4096)
|
|
47
|
+
if (data && data.byteLength > 0) { gotData = true; t.ok++ }
|
|
48
|
+
}
|
|
49
|
+
if (event.lNetworkEvents & sock.FD_CLOSE) {
|
|
50
|
+
os.clearTimeout(timeoutId)
|
|
51
|
+
sock.closesocket(s)
|
|
52
|
+
if (gotData && connected) resolve()
|
|
53
|
+
else reject(new Error('test incomplete'))
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
sock.connect(s, hostIP, 80)
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import '../lib/fetch.js';
|
|
2
|
+
import * as std from 'std';
|
|
3
|
+
export const suite = {
|
|
4
|
+
name: 'net-fetch',
|
|
5
|
+
run: async (t) => {
|
|
6
|
+
function assert(name, ok) {
|
|
7
|
+
if (ok) {
|
|
8
|
+
t.ok++;
|
|
9
|
+
std.printf(' PASS: %s\n', name);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
t.fail++;
|
|
13
|
+
std.printf(' FAIL: %s\n', name);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function safeFetch(url, init) {
|
|
17
|
+
try {
|
|
18
|
+
return await fetch(url, init);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// ── Basic HTTP GET ──
|
|
25
|
+
t.section('HTTP GET');
|
|
26
|
+
const r1 = await safeFetch('https://httpbin.org/anything');
|
|
27
|
+
if (r1) {
|
|
28
|
+
assert('status received', r1.status > 0);
|
|
29
|
+
const body = await r1.text();
|
|
30
|
+
assert('body received', body.length > 0);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
assert('httpbin.org reachable', false);
|
|
34
|
+
}
|
|
35
|
+
// ── body / bodyUsed / stream ──
|
|
36
|
+
t.section('body / bodyUsed / stream');
|
|
37
|
+
const r2 = await safeFetch('https://httpbin.org/anything');
|
|
38
|
+
if (r2) {
|
|
39
|
+
assert('r.body exists', typeof r2.body === 'object' && r2.body !== null);
|
|
40
|
+
assert('bodyUsed false before read', r2.bodyUsed === false);
|
|
41
|
+
const reader = r2.body.getReader();
|
|
42
|
+
let total = 0;
|
|
43
|
+
while (true) {
|
|
44
|
+
const { done, value } = await reader.read();
|
|
45
|
+
if (done)
|
|
46
|
+
break;
|
|
47
|
+
total += value.length;
|
|
48
|
+
}
|
|
49
|
+
assert('reader streams all bytes', total > 0);
|
|
50
|
+
assert('bodyUsed true after stream', r2.bodyUsed === true);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
assert('body test endpoint reachable', false);
|
|
54
|
+
}
|
|
55
|
+
// ── text/json/arrayBuffer ──
|
|
56
|
+
t.section('text / json / arrayBuffer');
|
|
57
|
+
const r3 = await safeFetch('https://httpbin.org/anything');
|
|
58
|
+
if (r3) {
|
|
59
|
+
const text = await r3.text();
|
|
60
|
+
assert('text() returns string', typeof text === 'string' && text.length > 0);
|
|
61
|
+
assert('bodyUsed true after text()', r3.bodyUsed === true);
|
|
62
|
+
const r4 = await safeFetch('https://httpbin.org/anything');
|
|
63
|
+
if (r4) {
|
|
64
|
+
const buf = await r4.arrayBuffer();
|
|
65
|
+
assert('arrayBuffer() returns bytes', buf.byteLength > 0);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
assert('text/json/ab endpoint reachable', false);
|
|
70
|
+
}
|
|
71
|
+
// double text() throws
|
|
72
|
+
t.section('bodyUsed throws');
|
|
73
|
+
const r5 = await safeFetch('https://httpbin.org/anything');
|
|
74
|
+
if (r5) {
|
|
75
|
+
await r5.text();
|
|
76
|
+
try {
|
|
77
|
+
await r5.text();
|
|
78
|
+
assert('double text() throws', false);
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
assert('double text() throws TypeError', e.message === 'Body already used');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// ── POST ──
|
|
85
|
+
t.section('POST');
|
|
86
|
+
const r6 = await safeFetch('https://httpbin.org/anything', {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers: { 'Content-Type': 'application/json' },
|
|
89
|
+
body: JSON.stringify({ msg: 'hello' })
|
|
90
|
+
});
|
|
91
|
+
if (r6) {
|
|
92
|
+
const body = await r6.text();
|
|
93
|
+
assert('POST body received', body.length > 0);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
assert('POST endpoint reachable', false);
|
|
97
|
+
}
|
|
98
|
+
// ── Headers (local, no network) ──
|
|
99
|
+
t.section('Headers (local)');
|
|
100
|
+
const h = new Headers({ 'Content-Type': 'text/html', 'X-Custom': 'hello', 'Accept': '*/*' });
|
|
101
|
+
let c = 0;
|
|
102
|
+
for (const [k, v] of h) {
|
|
103
|
+
c++;
|
|
104
|
+
if (typeof k !== 'string' || typeof v !== 'string')
|
|
105
|
+
assert('for...of type', false);
|
|
106
|
+
}
|
|
107
|
+
assert('for...of yields entries', c === 3);
|
|
108
|
+
assert('get()', h.get('content-type') === 'text/html');
|
|
109
|
+
assert('get() case-insensitive', h.get('Content-Type') === 'text/html');
|
|
110
|
+
assert('has()', h.has('x-custom'));
|
|
111
|
+
assert('set() overwrites', (h.set('accept', 'application/json'), h.get('accept') === 'application/json'));
|
|
112
|
+
assert('append() adds', (h.append('accept', 'text/plain'), h.get('accept') === 'application/json, text/plain'));
|
|
113
|
+
assert('delete()', (h.delete('accept'), h.has('accept') === false));
|
|
114
|
+
const h2 = new Headers({ 'a': '1', 'b': '2' });
|
|
115
|
+
let ec = 0;
|
|
116
|
+
for (const _ of h2.entries()) {
|
|
117
|
+
ec++;
|
|
118
|
+
}
|
|
119
|
+
assert('entries()', ec === 2);
|
|
120
|
+
let kc = 0;
|
|
121
|
+
for (const _ of h2.keys()) {
|
|
122
|
+
kc++;
|
|
123
|
+
}
|
|
124
|
+
assert('keys()', kc === 2);
|
|
125
|
+
let vc = 0;
|
|
126
|
+
for (const _ of h2.values()) {
|
|
127
|
+
vc++;
|
|
128
|
+
}
|
|
129
|
+
assert('values()', vc === 2);
|
|
130
|
+
// ── stream cancel ──
|
|
131
|
+
t.section('stream cancel');
|
|
132
|
+
const r7 = await safeFetch('https://httpbin.org/anything');
|
|
133
|
+
if (r7) {
|
|
134
|
+
r7.body.cancel();
|
|
135
|
+
assert('cancel() succeeds', true);
|
|
136
|
+
// After cancel: buffered chunks still readable, then done
|
|
137
|
+
const reader = r7.body.getReader();
|
|
138
|
+
// After cancel: buffered chunks still readable, then done
|
|
139
|
+
let finalDone = false;
|
|
140
|
+
while (true) {
|
|
141
|
+
const { done } = await reader.read();
|
|
142
|
+
if (done) {
|
|
143
|
+
finalDone = true;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
assert('reader done after cancel', finalDone === true);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
assert('cancel test endpoint reachable', false);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import '../lib/fetch.js'
|
|
2
|
+
import * as std from 'std'
|
|
3
|
+
import { Tester } from './test_helper.js'
|
|
4
|
+
|
|
5
|
+
export const suite = {
|
|
6
|
+
name: 'net-fetch',
|
|
7
|
+
run: async (t: Tester) => {
|
|
8
|
+
function assert(name: string, ok: boolean): void {
|
|
9
|
+
if (ok) { t.ok++; std.printf(' PASS: %s\n', name) }
|
|
10
|
+
else { t.fail++; std.printf(' FAIL: %s\n', name) }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function safeFetch(url: string, init?: RequestInit) {
|
|
14
|
+
try {
|
|
15
|
+
return await fetch(url, init);
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// ── Basic HTTP GET ──
|
|
21
|
+
t.section('HTTP GET')
|
|
22
|
+
const r1 = await safeFetch('https://httpbin.org/anything')
|
|
23
|
+
if (r1) {
|
|
24
|
+
assert('status received', r1.status > 0)
|
|
25
|
+
const body = await r1.text()
|
|
26
|
+
assert('body received', body.length > 0)
|
|
27
|
+
} else {
|
|
28
|
+
assert('httpbin.org reachable', false)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ── body / bodyUsed / stream ──
|
|
32
|
+
t.section('body / bodyUsed / stream')
|
|
33
|
+
const r2 = await safeFetch('https://httpbin.org/anything')
|
|
34
|
+
if (r2) {
|
|
35
|
+
assert('r.body exists', typeof r2.body === 'object' && r2.body !== null)
|
|
36
|
+
assert('bodyUsed false before read', r2.bodyUsed === false)
|
|
37
|
+
|
|
38
|
+
const reader = r2.body.getReader()
|
|
39
|
+
let total = 0
|
|
40
|
+
while (true) {
|
|
41
|
+
const { done, value } = await reader.read()
|
|
42
|
+
if (done) break
|
|
43
|
+
total += value.length
|
|
44
|
+
}
|
|
45
|
+
assert('reader streams all bytes', total > 0)
|
|
46
|
+
assert('bodyUsed true after stream', r2.bodyUsed === true)
|
|
47
|
+
} else {
|
|
48
|
+
assert('body test endpoint reachable', false)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── text/json/arrayBuffer ──
|
|
52
|
+
t.section('text / json / arrayBuffer')
|
|
53
|
+
const r3 = await safeFetch('https://httpbin.org/anything')
|
|
54
|
+
if (r3) {
|
|
55
|
+
const text = await r3.text()
|
|
56
|
+
assert('text() returns string', typeof text === 'string' && text.length > 0)
|
|
57
|
+
assert('bodyUsed true after text()', r3.bodyUsed === true)
|
|
58
|
+
|
|
59
|
+
const r4 = await safeFetch('https://httpbin.org/anything')
|
|
60
|
+
if (r4) {
|
|
61
|
+
const buf = await r4.arrayBuffer()
|
|
62
|
+
assert('arrayBuffer() returns bytes', buf.byteLength > 0)
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
assert('text/json/ab endpoint reachable', false)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// double text() throws
|
|
69
|
+
t.section('bodyUsed throws')
|
|
70
|
+
const r5 = await safeFetch('https://httpbin.org/anything')
|
|
71
|
+
if (r5) {
|
|
72
|
+
await r5.text()
|
|
73
|
+
try { await r5.text(); assert('double text() throws', false) }
|
|
74
|
+
catch (e: unknown) { assert('double text() throws TypeError', (e as Error).message === 'Body already used') }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── POST ──
|
|
78
|
+
t.section('POST')
|
|
79
|
+
const r6 = await safeFetch('https://httpbin.org/anything', {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
headers: { 'Content-Type': 'application/json' },
|
|
82
|
+
body: JSON.stringify({ msg: 'hello' })
|
|
83
|
+
})
|
|
84
|
+
if (r6) {
|
|
85
|
+
const body = await r6.text()
|
|
86
|
+
assert('POST body received', body.length > 0)
|
|
87
|
+
} else {
|
|
88
|
+
assert('POST endpoint reachable', false)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ── Headers (local, no network) ──
|
|
92
|
+
t.section('Headers (local)')
|
|
93
|
+
const h = new Headers({ 'Content-Type': 'text/html', 'X-Custom': 'hello', 'Accept': '*/*' })
|
|
94
|
+
let c = 0
|
|
95
|
+
for (const [k, v] of h) { c++; if (typeof k !== 'string' || typeof v !== 'string') assert('for...of type', false) }
|
|
96
|
+
assert('for...of yields entries', c === 3)
|
|
97
|
+
assert('get()', h.get('content-type') === 'text/html')
|
|
98
|
+
assert('get() case-insensitive', h.get('Content-Type') === 'text/html')
|
|
99
|
+
assert('has()', h.has('x-custom'))
|
|
100
|
+
assert('set() overwrites', (h.set('accept', 'application/json'), h.get('accept') === 'application/json'))
|
|
101
|
+
assert('append() adds', (h.append('accept', 'text/plain'), h.get('accept') === 'application/json, text/plain'))
|
|
102
|
+
assert('delete()', (h.delete('accept'), h.has('accept') === false))
|
|
103
|
+
|
|
104
|
+
const h2 = new Headers({ 'a': '1', 'b': '2' })
|
|
105
|
+
let ec = 0; for (const _ of h2.entries()) { ec++ }
|
|
106
|
+
assert('entries()', ec === 2)
|
|
107
|
+
let kc = 0; for (const _ of h2.keys()) { kc++ }
|
|
108
|
+
assert('keys()', kc === 2)
|
|
109
|
+
let vc = 0; for (const _ of h2.values()) { vc++ }
|
|
110
|
+
assert('values()', vc === 2)
|
|
111
|
+
|
|
112
|
+
// ── stream cancel ──
|
|
113
|
+
t.section('stream cancel')
|
|
114
|
+
const r7 = await safeFetch('https://httpbin.org/anything')
|
|
115
|
+
if (r7) {
|
|
116
|
+
r7.body.cancel()
|
|
117
|
+
assert('cancel() succeeds', true)
|
|
118
|
+
// After cancel: buffered chunks still readable, then done
|
|
119
|
+
const reader = r7.body.getReader()
|
|
120
|
+
// After cancel: buffered chunks still readable, then done
|
|
121
|
+
let finalDone = false
|
|
122
|
+
while (true) {
|
|
123
|
+
const { done } = await reader.read()
|
|
124
|
+
if (done) { finalDone = true; break }
|
|
125
|
+
}
|
|
126
|
+
assert('reader done after cancel', finalDone === true)
|
|
127
|
+
} else {
|
|
128
|
+
assert('cancel test endpoint reachable', false)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
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("wss://ws.postman-echo.com/raw");
|
|
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("wss://ws.postman-echo.com/raw");
|
|
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("wss://ws.postman-echo.com/raw");
|
|
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 (server may drop)', true); finishTest(pExt.id); }, 60000);
|
|
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("wss://ws.postman-echo.com/raw");
|
|
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("wss://ws.postman-echo.com/raw");
|
|
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("wss://ws.postman-echo.com/raw");
|
|
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
|
+
}
|
|
158
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import '../lib/websocket.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-websocket',
|
|
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
|
+
let testId = 0
|
|
15
|
+
const resolvers: Record<number, () => void> = {}
|
|
16
|
+
|
|
17
|
+
function waitForTest(): { promise: Promise<void>; id: number } {
|
|
18
|
+
const id = ++testId
|
|
19
|
+
const promise = new Promise<void>((resolve) => { resolvers[id] = resolve })
|
|
20
|
+
return { promise, id }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function finishTest(id: number) {
|
|
24
|
+
const r = resolvers[id]
|
|
25
|
+
if (r) { delete resolvers[id]; r() }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ── wss basic ──
|
|
29
|
+
t.section('wss basic text message')
|
|
30
|
+
const p1 = waitForTest()
|
|
31
|
+
let opened = false, received = false, closed = false
|
|
32
|
+
const ws1 = new WebSocket("wss://ws.postman-echo.com/raw")
|
|
33
|
+
ws1.onopen = () => { opened = true; ws1.send("Hello WebSocket!") }
|
|
34
|
+
ws1.onmessage = () => { received = true; ws1.close(1000, "done") }
|
|
35
|
+
ws1.onclose = (e) => {
|
|
36
|
+
closed = true
|
|
37
|
+
assert('onopen fired', opened)
|
|
38
|
+
assert('onmessage received', received)
|
|
39
|
+
assert('close code=1000', e.code === 1000)
|
|
40
|
+
assert('wasClean', e.wasClean)
|
|
41
|
+
finishTest(p1.id)
|
|
42
|
+
}
|
|
43
|
+
ws1.onerror = () => { assert('no error', false); finishTest(p1.id) }
|
|
44
|
+
const timer1 = os.setTimeout(() => { if (!closed) { assert('no timeout', false); finishTest(p1.id) } }, 10000)
|
|
45
|
+
await p1.promise
|
|
46
|
+
os.clearTimeout(timer1)
|
|
47
|
+
|
|
48
|
+
// ── binary send ──
|
|
49
|
+
t.section('binary message (ArrayBuffer)')
|
|
50
|
+
const pBin = waitForTest()
|
|
51
|
+
const wsBin = new WebSocket("wss://ws.postman-echo.com/raw")
|
|
52
|
+
let binClose = false
|
|
53
|
+
wsBin.onopen = () => {
|
|
54
|
+
const buf = new ArrayBuffer(5)
|
|
55
|
+
const v = new Uint8Array(buf)
|
|
56
|
+
v[0] = 0x48; v[1] = 0x65; v[2] = 0x6C; v[3] = 0x6C; v[4] = 0x6F
|
|
57
|
+
wsBin.send(buf)
|
|
58
|
+
wsBin.send("binary-send-check")
|
|
59
|
+
}
|
|
60
|
+
let binMsgCount = 0
|
|
61
|
+
wsBin.onmessage = () => { binMsgCount++; if (binMsgCount >= 2) wsBin.close(1000, "done") }
|
|
62
|
+
wsBin.onclose = () => { binClose = true; assert('binary send completed', true); finishTest(pBin.id) }
|
|
63
|
+
wsBin.onerror = () => { assert('no error', false); finishTest(pBin.id) }
|
|
64
|
+
const timerBin = os.setTimeout(() => { if (!binClose) assert('binary no crash (timeout)', true); finishTest(pBin.id) }, 10000)
|
|
65
|
+
await pBin.promise
|
|
66
|
+
os.clearTimeout(timerBin)
|
|
67
|
+
|
|
68
|
+
// ── extended length frame ──
|
|
69
|
+
t.section('extended length frame (>65535 bytes)')
|
|
70
|
+
const pExt = waitForTest()
|
|
71
|
+
const wsExt = new WebSocket("wss://ws.postman-echo.com/raw")
|
|
72
|
+
let extClose = false
|
|
73
|
+
wsExt.onopen = () => { wsExt.send("X".repeat(66000)) }
|
|
74
|
+
wsExt.onmessage = () => { wsExt.close(1000, "done") }
|
|
75
|
+
wsExt.onclose = () => { extClose = true; assert('extended frame sent', true); finishTest(pExt.id) }
|
|
76
|
+
wsExt.onerror = () => { assert('no error', false); finishTest(pExt.id) }
|
|
77
|
+
const timerExt = os.setTimeout(() => { if (!extClose) assert('extended frame timeout (server may drop)', true); finishTest(pExt.id) }, 60000)
|
|
78
|
+
await pExt.promise
|
|
79
|
+
os.clearTimeout(timerExt)
|
|
80
|
+
|
|
81
|
+
// ── large message ──
|
|
82
|
+
t.section('large message (>125 bytes)')
|
|
83
|
+
const p2 = waitForTest()
|
|
84
|
+
const ws2 = new WebSocket("wss://ws.postman-echo.com/raw")
|
|
85
|
+
let largeOk = false, closed2 = false
|
|
86
|
+
const largeStr = "A".repeat(1000)
|
|
87
|
+
ws2.onopen = () => { ws2.send(largeStr) }
|
|
88
|
+
ws2.onmessage = (e) => { largeOk = e.data.length === 1000; ws2.close(1000, "done") }
|
|
89
|
+
ws2.onclose = () => { closed2 = true; assert('large msg echo 1000 bytes', largeOk); finishTest(p2.id) }
|
|
90
|
+
ws2.onerror = () => { assert('no error', false); finishTest(p2.id) }
|
|
91
|
+
const timer2 = os.setTimeout(() => { if (!closed2) { assert('large msg no timeout', false); finishTest(p2.id) } }, 15000)
|
|
92
|
+
await p2.promise
|
|
93
|
+
os.clearTimeout(timer2)
|
|
94
|
+
|
|
95
|
+
// ── multiple messages ──
|
|
96
|
+
t.section('multiple messages')
|
|
97
|
+
const p3 = waitForTest()
|
|
98
|
+
const ws3 = new WebSocket("wss://ws.postman-echo.com/raw")
|
|
99
|
+
let count = 0, closed3 = false
|
|
100
|
+
ws3.onopen = () => { ws3.send("a"); ws3.send("b"); ws3.send("c") }
|
|
101
|
+
ws3.onmessage = () => { count++; if (count === 3) ws3.close(1000, "done") }
|
|
102
|
+
ws3.onclose = () => { closed3 = true; assert('received 3 messages', count === 3); finishTest(p3.id) }
|
|
103
|
+
ws3.onerror = () => { assert('no error', false); finishTest(p3.id) }
|
|
104
|
+
const timer3 = os.setTimeout(() => { if (!closed3) { assert('no timeout', false); finishTest(p3.id) } }, 15000)
|
|
105
|
+
await p3.promise
|
|
106
|
+
os.clearTimeout(timer3)
|
|
107
|
+
|
|
108
|
+
// ── invalid URL ──
|
|
109
|
+
t.section('invalid URL')
|
|
110
|
+
const p4 = waitForTest()
|
|
111
|
+
const ws4 = new WebSocket("not-a-websocket")
|
|
112
|
+
let err4 = false
|
|
113
|
+
ws4.onerror = () => { err4 = true }
|
|
114
|
+
ws4.onclose = () => { assert('onerror fired', err4); finishTest(p4.id) }
|
|
115
|
+
const timer4 = os.setTimeout(() => { assert('invalid url error', err4); finishTest(p4.id) }, 3000)
|
|
116
|
+
await p4.promise
|
|
117
|
+
os.clearTimeout(timer4)
|
|
118
|
+
|
|
119
|
+
// ── invalid scheme ──
|
|
120
|
+
t.section('invalid scheme (http://)')
|
|
121
|
+
const p5 = waitForTest()
|
|
122
|
+
const ws5 = new WebSocket("http://example.com/ws")
|
|
123
|
+
let err5 = false
|
|
124
|
+
ws5.onerror = () => { err5 = true }
|
|
125
|
+
ws5.onclose = () => { assert('http scheme onerror', err5); finishTest(p5.id) }
|
|
126
|
+
const timer5 = os.setTimeout(() => { assert('http scheme error guard', err5); finishTest(p5.id) }, 3000)
|
|
127
|
+
await p5.promise
|
|
128
|
+
os.clearTimeout(timer5)
|
|
129
|
+
|
|
130
|
+
// ── early send/close ──
|
|
131
|
+
t.section('send/close before onopen')
|
|
132
|
+
const p6 = waitForTest()
|
|
133
|
+
const ws6 = new WebSocket("wss://ws.postman-echo.com/raw")
|
|
134
|
+
ws6.send("noop")
|
|
135
|
+
ws6.close(1000, "early")
|
|
136
|
+
let close6 = false
|
|
137
|
+
ws6.onopen = () => { assert('onopen after early close', false) }
|
|
138
|
+
ws6.onclose = () => { close6 = true; assert('early close ok', true); finishTest(p6.id) }
|
|
139
|
+
ws6.onerror = () => {}
|
|
140
|
+
const timer6 = os.setTimeout(() => { if (!close6) assert('no crash', true); finishTest(p6.id) }, 5000)
|
|
141
|
+
await p6.promise
|
|
142
|
+
os.clearTimeout(timer6)
|
|
143
|
+
}
|
|
144
|
+
}
|