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.
Files changed (83) hide show
  1. package/README.md +6 -0
  2. package/examples/pdf_preview.js +440 -0
  3. package/examples/pdf_preview.ts +470 -0
  4. package/examples/preact_demo.js +35 -0
  5. package/examples/preact_demo.tsx +49 -0
  6. package/examples/tray_demo.js +75 -0
  7. package/examples/tray_demo.tsx +79 -0
  8. package/lib/fetch.js +746 -0
  9. package/lib/fetch.ts +811 -0
  10. package/lib/polyfill.js +500 -0
  11. package/lib/polyfill.ts +454 -0
  12. package/lib/preact/hooks.js +287 -0
  13. package/lib/preact/hooks.ts +330 -0
  14. package/lib/preact/jsx-runtime.js +1 -0
  15. package/lib/preact/jsx-runtime.ts +2 -0
  16. package/lib/preact/jsx.d.ts +36 -0
  17. package/lib/preact/layout.js +153 -0
  18. package/lib/preact/layout.ts +183 -0
  19. package/lib/preact/preact.js +54 -0
  20. package/lib/preact/preact.ts +133 -0
  21. package/lib/preact/props.js +99 -0
  22. package/lib/preact/props.ts +119 -0
  23. package/lib/preact/render.js +320 -0
  24. package/lib/preact/render.ts +353 -0
  25. package/lib/websocket.js +540 -0
  26. package/lib/websocket.ts +574 -0
  27. package/package.json +32 -0
  28. package/quickwin.d.ts +657 -0
  29. package/test/add.wasm +0 -0
  30. package/test/complex.wasm +0 -0
  31. package/test/complex_imports.wasm +0 -0
  32. package/test/global_imports.wasm +0 -0
  33. package/test/import_func.wasm +0 -0
  34. package/test/imports.wasm +0 -0
  35. package/test/run.js +86 -0
  36. package/test/run.ts +90 -0
  37. package/test/sjlj.wasm +0 -0
  38. package/test/test_basic.js +7 -0
  39. package/test/test_basic.ts +9 -0
  40. package/test/test_brotli.js +48 -0
  41. package/test/test_brotli.ts +52 -0
  42. package/test/test_fetch_cache.js +131 -0
  43. package/test/test_fetch_cache.ts +141 -0
  44. package/test/test_ffi.js +157 -0
  45. package/test/test_ffi.ts +174 -0
  46. package/test/test_frame_encoding.js +128 -0
  47. package/test/test_frame_encoding.ts +132 -0
  48. package/test/test_helper.js +84 -0
  49. package/test/test_helper.ts +80 -0
  50. package/test/test_http_import.js +78 -0
  51. package/test/test_http_import.ts +74 -0
  52. package/test/test_mupdf_render.js +69 -0
  53. package/test/test_mupdf_render.ts +74 -0
  54. package/test/test_mupdf_twice.js +77 -0
  55. package/test/test_mupdf_twice.ts +81 -0
  56. package/test/test_mupdf_wasm.js +33 -0
  57. package/test/test_mupdf_wasm.ts +30 -0
  58. package/test/test_net_event.js +63 -0
  59. package/test/test_net_event.ts +59 -0
  60. package/test/test_net_fetch.js +153 -0
  61. package/test/test_net_fetch.ts +131 -0
  62. package/test/test_net_websocket.js +158 -0
  63. package/test/test_net_websocket.ts +144 -0
  64. package/test/test_polyfill.js +58 -0
  65. package/test/test_polyfill.ts +60 -0
  66. package/test/test_url.js +173 -0
  67. package/test/test_url.ts +183 -0
  68. package/test/test_wasm_basic.js +82 -0
  69. package/test/test_wasm_basic.ts +70 -0
  70. package/test/test_wasm_import_global.js +41 -0
  71. package/test/test_wasm_import_global.ts +39 -0
  72. package/test/test_wasm_sjlj.js +153 -0
  73. package/test/test_wasm_sjlj.ts +134 -0
  74. package/test/test_wasm_types.js +96 -0
  75. package/test/test_wasm_types.ts +108 -0
  76. package/test/types.wasm +0 -0
  77. package/tsconfig.json +18 -0
  78. package/vendor/mupdf-wasm/mupdf-wasm.d.ts +571 -0
  79. package/vendor/mupdf-wasm/mupdf-wasm.js +2749 -0
  80. package/vendor/mupdf-wasm/mupdf-wasm.wasm +0 -0
  81. package/vendor/mupdf-wasm/mupdf.d.ts +939 -0
  82. package/vendor/mupdf-wasm/mupdf.js +3317 -0
  83. 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
+ }