quickwin 1.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.
Files changed (175) hide show
  1. package/README.en.md +197 -0
  2. package/README.md +196 -0
  3. package/examples/PdfCanvas.js +67 -0
  4. package/examples/PdfCanvas.tsx +83 -0
  5. package/examples/pdf_preview2.js +431 -0
  6. package/examples/pdf_preview2.ts +375 -0
  7. package/examples/pdf_viewer.js +17623 -0
  8. package/examples/pdf_viewer.tsx +279 -0
  9. package/examples/test_gallery.js +17452 -0
  10. package/examples/test_gallery.tsx +285 -0
  11. package/examples/test_react_lazy.js +17336 -0
  12. package/examples/test_react_lazy.tsx +63 -0
  13. package/examples/test_react_listbox.js +17339 -0
  14. package/examples/test_react_listbox.tsx +71 -0
  15. package/examples/test_root_window.js +17313 -0
  16. package/examples/test_root_window.tsx +41 -0
  17. package/examples/test_tab.js +17339 -0
  18. package/examples/test_tab.tsx +75 -0
  19. package/examples/tray_demo.js +79 -0
  20. package/examples/tray_demo.tsx +81 -0
  21. package/lib/assert.js +8 -0
  22. package/lib/assert.ts +7 -0
  23. package/lib/cache-utils.js +43 -0
  24. package/lib/cache-utils.ts +40 -0
  25. package/lib/dumpRects.js +25 -0
  26. package/lib/dumpRects.ts +25 -0
  27. package/lib/fetch.js +490 -0
  28. package/lib/fetch.ts +502 -0
  29. package/lib/http-common.js +342 -0
  30. package/lib/http-common.ts +399 -0
  31. package/lib/http-server.js +416 -0
  32. package/lib/http-server.ts +424 -0
  33. package/lib/polyfill.js +156 -0
  34. package/lib/polyfill.ts +171 -0
  35. package/lib/react-qw/components/Button.js +8 -0
  36. package/lib/react-qw/components/CheckBox.js +31 -0
  37. package/lib/react-qw/components/ComboBox.js +42 -0
  38. package/lib/react-qw/components/DateTimePicker.js +96 -0
  39. package/lib/react-qw/components/Input.js +50 -0
  40. package/lib/react-qw/components/Link.js +43 -0
  41. package/lib/react-qw/components/ListBox.js +48 -0
  42. package/lib/react-qw/components/ListView.js +328 -0
  43. package/lib/react-qw/components/PathPicker.js +136 -0
  44. package/lib/react-qw/components/ProgressBar.js +22 -0
  45. package/lib/react-qw/components/RadioButton.js +31 -0
  46. package/lib/react-qw/components/ScrollView.js +139 -0
  47. package/lib/react-qw/components/Slider.js +45 -0
  48. package/lib/react-qw/components/Tab.js +61 -0
  49. package/lib/react-qw/components/Tooltip.js +55 -0
  50. package/lib/react-qw/components/TreeView.js +130 -0
  51. package/lib/react-qw/components/index.js +16 -0
  52. package/lib/react-qw/components/index.ts +17 -0
  53. package/lib/react-qw/index.js +73 -0
  54. package/lib/react-qw/index.ts +90 -0
  55. package/lib/react-qw/jsx.d.ts +34 -0
  56. package/lib/react-qw/layout.js +82 -0
  57. package/lib/react-qw/layout.ts +111 -0
  58. package/lib/react-qw/props.js +27 -0
  59. package/lib/react-qw/props.ts +36 -0
  60. package/lib/react-qw/reconciler.js +333 -0
  61. package/lib/react-qw/reconciler.ts +373 -0
  62. package/lib/stream.js +2 -0
  63. package/lib/stream.ts +22 -0
  64. package/lib/text-codec.js +197 -0
  65. package/lib/text-codec.ts +171 -0
  66. package/lib/text-measure.js +52 -0
  67. package/lib/text-measure.ts +54 -0
  68. package/lib/url.js +278 -0
  69. package/lib/url.ts +201 -0
  70. package/lib/vendor/web-streams/ponyfill.d.mts +829 -0
  71. package/lib/vendor/web-streams/ponyfill.mjs +8 -0
  72. package/lib/websocket.js +541 -0
  73. package/lib/websocket.ts +555 -0
  74. package/package.json +44 -0
  75. package/quickwin.d.ts +594 -0
  76. package/quickwin_const.d.ts +900 -0
  77. package/qwin-nowasm.exe +0 -0
  78. package/qwin.exe +0 -0
  79. package/test/add.wasm +0 -0
  80. package/test/complex.wasm +0 -0
  81. package/test/complex_imports.wasm +0 -0
  82. package/test/debug/http_server_e2e.js +31 -0
  83. package/test/debug/minwasm.js +52 -0
  84. package/test/debug/mupdf_debug.js +104 -0
  85. package/test/debug/wasm.js +122 -0
  86. package/test/debug/wasm_export_debug.js +64 -0
  87. package/test/debug/wasm_names.js +46 -0
  88. package/test/global_imports.wasm +0 -0
  89. package/test/import_func.wasm +0 -0
  90. package/test/imports.wasm +0 -0
  91. package/test/run.js +91 -0
  92. package/test/run.ts +96 -0
  93. package/test/sjlj.wasm +0 -0
  94. package/test/test_basic.js +7 -0
  95. package/test/test_basic.ts +9 -0
  96. package/test/test_basic_fetch.js +34 -0
  97. package/test/test_basic_fetch.ts +33 -0
  98. package/test/test_brotli.js +48 -0
  99. package/test/test_brotli.ts +52 -0
  100. package/test/test_fetch_cache.js +169 -0
  101. package/test/test_fetch_cache.ts +187 -0
  102. package/test/test_ffi.js +157 -0
  103. package/test/test_ffi.ts +174 -0
  104. package/test/test_frame_encoding.js +128 -0
  105. package/test/test_frame_encoding.ts +132 -0
  106. package/test/test_helper.js +84 -0
  107. package/test/test_helper.ts +80 -0
  108. package/test/test_http_import.js +78 -0
  109. package/test/test_http_import.ts +74 -0
  110. package/test/test_http_server.js +142 -0
  111. package/test/test_http_server.ts +136 -0
  112. package/test/test_import_dir.js +4 -0
  113. package/test/test_import_dir.ts +5 -0
  114. package/test/test_ipv6.js +134 -0
  115. package/test/test_ipv6.ts +129 -0
  116. package/test/test_jsx.js +1314 -0
  117. package/test/test_meta_url.js +3 -0
  118. package/test/test_meta_url.ts +4 -0
  119. package/test/test_mupdf_render.js +69 -0
  120. package/test/test_mupdf_render.ts +74 -0
  121. package/test/test_mupdf_twice.js +77 -0
  122. package/test/test_mupdf_twice.ts +81 -0
  123. package/test/test_mupdf_wasm.js +33 -0
  124. package/test/test_mupdf_wasm.ts +30 -0
  125. package/test/test_net_event.js +57 -0
  126. package/test/test_net_event.ts +55 -0
  127. package/test/test_net_fetch.js +259 -0
  128. package/test/test_net_fetch.ts +238 -0
  129. package/test/test_net_websocket.js +180 -0
  130. package/test/test_net_websocket.ts +164 -0
  131. package/test/test_polyfill.js +144 -0
  132. package/test/test_polyfill.ts +159 -0
  133. package/test/test_react_complex.js +17440 -0
  134. package/test/test_react_counter.js +17331 -0
  135. package/test/test_react_flex.js +17643 -0
  136. package/test/test_react_hidden.js +17336 -0
  137. package/test/test_react_listview_link.js +17371 -0
  138. package/test/test_react_render.js +17326 -0
  139. package/test/test_url.js +173 -0
  140. package/test/test_url.ts +183 -0
  141. package/test/test_wasm_basic.js +85 -0
  142. package/test/test_wasm_basic.ts +73 -0
  143. package/test/test_wasm_import_global.js +41 -0
  144. package/test/test_wasm_import_global.ts +45 -0
  145. package/test/test_wasm_sjlj.js +145 -0
  146. package/test/test_wasm_sjlj.ts +125 -0
  147. package/test/test_wasm_types.js +97 -0
  148. package/test/test_wasm_types.ts +124 -0
  149. package/test/test_worker.js +21 -0
  150. package/test/test_worker.ts +24 -0
  151. package/test/test_worker_fetch.js +26 -0
  152. package/test/test_worker_fetch.ts +27 -0
  153. package/test/test_worker_net.js +51 -0
  154. package/test/test_worker_net.ts +54 -0
  155. package/test/test_worker_wasm_concurrent.js +44 -0
  156. package/test/test_worker_wasm_concurrent.ts +45 -0
  157. package/test/types.wasm +0 -0
  158. package/test/worker_fetch.js +13 -0
  159. package/test/worker_fetch.ts +13 -0
  160. package/test/worker_helper.js +1 -0
  161. package/test/worker_helper.ts +1 -0
  162. package/test/worker_http.js +18 -0
  163. package/test/worker_http.ts +20 -0
  164. package/test/worker_import_lib.js +12 -0
  165. package/test/worker_import_lib.ts +13 -0
  166. package/test/worker_module.js +16 -0
  167. package/test/worker_module.ts +17 -0
  168. package/test/worker_wasm_concurrent.js +22 -0
  169. package/test/worker_wasm_concurrent.ts +22 -0
  170. package/tsconfig.json +27 -0
  171. package/vendor/mupdf-wasm/mupdf-wasm.d.ts +571 -0
  172. package/vendor/mupdf-wasm/mupdf-wasm.js +2749 -0
  173. package/vendor/mupdf-wasm/mupdf-wasm.wasm +0 -0
  174. package/vendor/mupdf-wasm/mupdf.d.ts +939 -0
  175. package/vendor/mupdf-wasm/mupdf.js +3317 -0
@@ -0,0 +1,555 @@
1
+ import '../lib/polyfill.js'
2
+ import * as sock from 'sock'
3
+ import * as wolfssl from 'wolfssl'
4
+ import * as os from 'os'
5
+
6
+ // ── Constants ──
7
+
8
+ const MAGIC_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
9
+
10
+ enum State {
11
+ CONNECTING = 0,
12
+ OPEN = 1,
13
+ CLOSING = 2,
14
+ CLOSED = 3,
15
+ }
16
+
17
+ enum Opcode {
18
+ CONTINUATION = 0x0,
19
+ TEXT = 0x1,
20
+ BINARY = 0x2,
21
+ CLOSE = 0x8,
22
+ PING = 0x9,
23
+ PONG = 0xA,
24
+ }
25
+
26
+ // ── Random key generation ──
27
+
28
+ function generateKey(): string {
29
+ const bytes = new Uint8Array(16)
30
+ crypto.getRandomValues(bytes)
31
+ return btoa(String.fromCharCode(...bytes))
32
+ }
33
+
34
+ async function computeAccept(key: string): Promise<string> {
35
+ const data = new TextEncoder().encode(key + MAGIC_GUID)
36
+ const hash = await crypto.subtle.digest('SHA-1', data)
37
+ const hashBytes = new Uint8Array(hash)
38
+ return btoa(String.fromCharCode(...hashBytes))
39
+ }
40
+
41
+ // ── Frame helpers ──
42
+
43
+ function createFrame(opcode: number, payload: Uint8Array): ArrayBuffer {
44
+ const maskingKey = new Uint8Array(4)
45
+ for (let i = 0; i < 4; i++) maskingKey[i] = (Math.random() * 256) | 0
46
+
47
+ let headerSize: number
48
+ if (payload.length < 126) {
49
+ headerSize = 6
50
+ } else if (payload.length < 65536) {
51
+ headerSize = 8
52
+ } else {
53
+ headerSize = 14
54
+ }
55
+
56
+ const buf = new ArrayBuffer(headerSize + payload.length)
57
+ const view = new Uint8Array(buf)
58
+ let off = 0
59
+
60
+ view[off++] = 0x80 | opcode
61
+ view[off++] = 0x80 | (payload.length < 126 ? payload.length :
62
+ payload.length < 65536 ? 126 : 127)
63
+
64
+ if (payload.length >= 126 && payload.length < 65536) {
65
+ view[off++] = (payload.length >> 8) & 0xFF
66
+ view[off++] = payload.length & 0xFF
67
+ } else if (payload.length >= 65536) {
68
+ const len = payload.length
69
+ const hi = (len / 0x100000000) >>> 0
70
+ const lo = len >>> 0
71
+ view[off++] = (hi >> 24) & 0xFF
72
+ view[off++] = (hi >> 16) & 0xFF
73
+ view[off++] = (hi >> 8) & 0xFF
74
+ view[off++] = hi & 0xFF
75
+ view[off++] = (lo >> 24) & 0xFF
76
+ view[off++] = (lo >> 16) & 0xFF
77
+ view[off++] = (lo >> 8) & 0xFF
78
+ view[off++] = lo & 0xFF
79
+ }
80
+
81
+ view[off++] = maskingKey[0]!
82
+ view[off++] = maskingKey[1]!
83
+ view[off++] = maskingKey[2]!
84
+ view[off++] = maskingKey[3]!
85
+
86
+ for (let i = 0; i < payload.length; i++) {
87
+ view[off++] = payload[i]! ^ maskingKey[i % 4]!
88
+ }
89
+
90
+ return buf
91
+ }
92
+
93
+ function createCloseFrame(code: number, reason: string): ArrayBuffer {
94
+ const reasonBytes = new Uint8Array([(code >> 8) & 0xFF, code & 0xFF, ...new TextEncoder().encode(reason)])
95
+ return createFrame(Opcode.CLOSE, reasonBytes)
96
+ }
97
+
98
+ // ── Frame parser (returns null if incomplete) ──
99
+
100
+ interface ParsedFrame {
101
+ opcode: number
102
+ payload: Uint8Array
103
+ fin: boolean
104
+ totalLen: number
105
+ }
106
+
107
+ function tryParseFrame(buffer: Uint8Array): ParsedFrame | null {
108
+ if (buffer.length < 2) return null
109
+
110
+ const b0 = buffer[0]!
111
+ const b1 = buffer[1]!
112
+ const fin = (b0 & 0x80) !== 0
113
+ const opcode = b0 & 0x0F
114
+ const masked = (b1 & 0x80) !== 0
115
+ let payloadLen = b1 & 0x7F
116
+ let offset = 2
117
+
118
+ if (payloadLen === 126) {
119
+ if (buffer.length < 4) return null
120
+ payloadLen = (buffer[2]! << 8) | buffer[3]!
121
+ offset = 4
122
+ } else if (payloadLen === 127) {
123
+ if (buffer.length < 10) return null
124
+ let hi = 0, lo = 0
125
+ for (let i = 0; i < 4; i++) hi = (hi * 256 + buffer[offset + i]!) >>> 0
126
+ for (let i = 4; i < 8; i++) lo = (lo * 256 + buffer[offset + i]!) >>> 0
127
+ payloadLen = hi * 0x100000000 + lo
128
+ offset = 10
129
+ }
130
+
131
+ if (masked) offset += 4
132
+
133
+ const totalLen = offset + payloadLen
134
+ if (buffer.length < totalLen) return null
135
+
136
+ let payload: Uint8Array
137
+ if (masked) {
138
+ const maskKey = buffer.slice(offset - 4, offset)
139
+ payload = new Uint8Array(payloadLen)
140
+ for (let i = 0; i < payloadLen; i++) {
141
+ payload[i] = buffer[offset + i]! ^ maskKey[i % 4]!
142
+ }
143
+ } else {
144
+ payload = buffer.slice(offset, offset + payloadLen)
145
+ }
146
+
147
+ return { opcode, payload, fin, totalLen }
148
+ }
149
+
150
+ // ── WebSocket class ──
151
+
152
+ interface WebSocketOptions {
153
+ headers?: Record<string, string>
154
+ }
155
+
156
+
157
+
158
+ class WebSocketImpl {
159
+ readonly url: string
160
+ readyState: State = State.CONNECTING
161
+ onopen: ((event: Event) => void) | null = null
162
+ onclose: ((event: CloseEvent) => void) | null = null
163
+ onerror: ((event: Event) => void) | null = null
164
+ onmessage: ((event: MessageEvent) => void) | null = null
165
+
166
+ private _options: WebSocketOptions
167
+ private _sock: sock.SockHandle | null = null
168
+ private _ssl: wolfssl.WOLFSSL | null = null
169
+ private _ctx: wolfssl.WOLFSSL_CTX | null = null
170
+ private _readBuffer: Uint8Array = new Uint8Array(0)
171
+ private _state: State = State.CONNECTING
172
+ private _processingHandshake: boolean = false
173
+ static readonly CONNECTING = State.CONNECTING
174
+ static readonly OPEN = State.OPEN
175
+ static readonly CLOSING = State.CLOSING
176
+ static readonly CLOSED = State.CLOSED
177
+
178
+ private _closeCode: number = 1005
179
+ private _closeReason: string = ''
180
+
181
+ constructor(url: string, options?: WebSocketOptions) {
182
+ this.url = url
183
+ this._options = options || {}
184
+ this._connect()
185
+ }
186
+
187
+ send(data: string | ArrayBuffer | Uint8Array): void {
188
+ if (this._state !== State.OPEN) return
189
+
190
+ let payload: Uint8Array
191
+ if (typeof data === 'string') {
192
+ payload = new TextEncoder().encode(data)
193
+ const frame = createFrame(Opcode.TEXT, payload)
194
+ this._sendRaw(frame)
195
+ } else if (data instanceof ArrayBuffer) {
196
+ payload = new Uint8Array(data)
197
+ const frame = createFrame(Opcode.BINARY, payload)
198
+ this._sendRaw(frame)
199
+ } else if (data instanceof Uint8Array) {
200
+ const frame = createFrame(Opcode.BINARY, data)
201
+ this._sendRaw(frame)
202
+ }
203
+ }
204
+
205
+ close(code?: number, reason?: string): void {
206
+ if (this._state === State.CLOSED || this._state === State.CLOSING) return
207
+ this._state = State.CLOSING
208
+
209
+ const closeCode = code || 1000
210
+ const closeReason = reason || ''
211
+ this._closeCode = closeCode
212
+ this._closeReason = closeReason
213
+ const frame = createCloseFrame(closeCode, closeReason)
214
+ this._sendRaw(frame)
215
+
216
+ this._cleanup()
217
+ this._setState(State.CLOSED)
218
+ }
219
+
220
+ private _connect(): void {
221
+ let url: URL
222
+ try { url = new URL(this.url) } catch {
223
+ const self = this
224
+ os.setTimeout(() => {
225
+ self._fireError(new Error('Invalid WebSocket URL: ' + self.url))
226
+ self._setState(State.CLOSED)
227
+ }, 0)
228
+ return
229
+ }
230
+ const host = url.hostname
231
+ const hostHeader = host.includes(':') ? `[${host}]` : host
232
+ const isWSS = url.protocol === 'wss:'
233
+ const port = url.port ? parseInt(url.port, 10) : (isWSS ? 443 : 80)
234
+ const path = url.pathname + (url.search || '')
235
+
236
+ const requestKey = generateKey()
237
+
238
+ const customHeadersList: string[] = []
239
+ let hasUserAgent = false
240
+ if (this._options.headers) {
241
+ for (const name in this._options.headers) {
242
+ const value = this._options.headers[name]
243
+ customHeadersList.push(name + ': ' + value + '\r\n')
244
+ if (name.toLowerCase() === 'user-agent') hasUserAgent = true
245
+ }
246
+ }
247
+ const customHeaders = customHeadersList.join('')
248
+
249
+ const request = (
250
+ 'GET ' + path + ' HTTP/1.1\r\n' +
251
+ 'Host: ' + hostHeader + ':' + port + '\r\n' +
252
+ 'Upgrade: websocket\r\n' +
253
+ 'Connection: Upgrade\r\n' +
254
+ 'Sec-WebSocket-Key: ' + requestKey + '\r\n' +
255
+ 'Sec-WebSocket-Version: 13\r\n' +
256
+ (hasUserAgent ? '' : 'User-Agent: ' + navigator.userAgent + '\r\n') +
257
+ customHeaders +
258
+ '\r\n'
259
+ )
260
+
261
+ let state: 'resolve' | 'connect' | 'handshake' | 'request' | 'open' = 'resolve'
262
+ let headerBuffer = ''
263
+ let resolved = false
264
+
265
+ const cleanup = (): void => {
266
+ if (this._ssl) { wolfssl.wolfSSL_free(this._ssl); this._ssl = null }
267
+ if (this._ctx) { wolfssl.wolfSSL_CTX_free(this._ctx); this._ctx = null }
268
+ if (this._sock !== null && this._sock >= 0) { sock.closesocket(this._sock); this._sock = null }
269
+ }
270
+
271
+ const doError = (err: Error): void => {
272
+ if (resolved) return
273
+ resolved = true
274
+ cleanup()
275
+ this._fireError(err)
276
+ if (this._state !== State.CLOSED) this._setState(State.CLOSED)
277
+ }
278
+
279
+ this._sock = sock.socket()
280
+ if (this._sock < 0) {
281
+ const self = this
282
+ os.setTimeout(() => {
283
+ self._fireError(new Error('Failed to create socket'))
284
+ if (self._state !== State.CLOSED) self._setState(State.CLOSED)
285
+ }, 0)
286
+ return
287
+ }
288
+ const fd = this._sock
289
+
290
+ sock.set_on_event(fd, async (event: { lNetworkEvents: number; iErrorCode: number[] }) => {
291
+ if (this._processingHandshake) return
292
+ if (resolved && state === 'open') {
293
+ if (event.lNetworkEvents & sock.FdEvent.FD_READ) {
294
+ this._onData()
295
+ }
296
+ if (event.lNetworkEvents & sock.FdEvent.FD_CLOSE) {
297
+ this._cleanup()
298
+ this._setState(State.CLOSED)
299
+ }
300
+ return
301
+ }
302
+
303
+ if (event.lNetworkEvents & sock.FdEvent.FD_CONNECT) {
304
+ const errCode = event.iErrorCode[0]
305
+ if (errCode !== 0) {
306
+ doError(new Error('Connection failed: ' + errCode))
307
+ return
308
+ }
309
+ if (isWSS) {
310
+ const method = wolfssl.wolfTLSv1_2_client_method()
311
+ this._ctx = wolfssl.wolfSSL_CTX_new(method)
312
+ if (!this._ctx) { doError(new Error('SSL_CTX_new failed')); return }
313
+ wolfssl.wolfSSL_CTX_set_verify(this._ctx, wolfssl.VerifyMode.SSL_VERIFY_NONE)
314
+ this._ssl = wolfssl.wolfSSL_new(this._ctx)
315
+ if (!this._ssl) { doError(new Error('SSL_new failed')); return }
316
+ wolfssl.wolfSSL_set_fd(this._ssl, sock.get_fd(fd))
317
+ const sniHost = host
318
+ if (sniHost) wolfssl.wolfSSL_UseSNI(this._ssl, wolfssl.SniType.WOLFSSL_SNI_HOST_NAME, sniHost)
319
+ state = 'handshake'
320
+ } else {
321
+ this._sendRawStr(request)
322
+ state = 'request'
323
+ }
324
+ }
325
+
326
+ if ((event.lNetworkEvents & sock.FdEvent.FD_READ) || (event.lNetworkEvents & sock.FdEvent.FD_WRITE)) {
327
+ if (state === 'handshake') {
328
+ if (!this._ssl) { doError(new Error('TLS not initialized')); return }
329
+ const ret = wolfssl.wolfSSL_connect(this._ssl)
330
+ if (ret === wolfssl.ReturnCode.SSL_SUCCESS) {
331
+ this._sendRawStr(request)
332
+ state = 'request'
333
+ } else {
334
+ const err = wolfssl.wolfSSL_get_error(this._ssl, ret)
335
+ if (err !== wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_READ &&
336
+ err !== wolfssl.ErrorCode.WOLFSSL_ERROR_WANT_WRITE) {
337
+ doError(new Error('TLS handshake failed: ' + err))
338
+ }
339
+ }
340
+ }
341
+ else if (state === 'request') {
342
+ while (true) {
343
+ let data: ArrayBuffer | null
344
+ if (isWSS && this._ssl) {
345
+ data = wolfssl.wolfSSL_read(this._ssl, 8192)
346
+ } else if (this._sock !== null && this._sock >= 0) {
347
+ data = sock.recv(this._sock, 8192)
348
+ } else { break }
349
+ if (!data || data.byteLength === 0) break
350
+ headerBuffer += _ab2str(data)
351
+ const headerEnd = headerBuffer.indexOf('\r\n\r\n')
352
+ if (headerEnd >= 0) {
353
+ const headerPart = headerBuffer.slice(0, headerEnd)
354
+ const lines = headerPart.split('\r\n')
355
+ const statusLine = lines[0]!
356
+ const statusParts = statusLine.split(' ')
357
+ if (statusParts.length < 2) {
358
+ doError(new Error('Invalid HTTP status line: ' + statusLine))
359
+ return
360
+ }
361
+ const statusCode = parseInt(statusParts[1]!, 10)
362
+ if (statusCode !== 101) {
363
+ doError(new Error('WebSocket handshake failed: HTTP ' + statusCode))
364
+ return
365
+ }
366
+
367
+ let acceptHeader = ''
368
+ for (let i = 1; i < lines.length; i++) {
369
+ const line = lines[i]!
370
+ const colonIdx2 = line.indexOf(':')
371
+ if (colonIdx2 >= 0) {
372
+ const name = line.slice(0, colonIdx2).toLowerCase().trim()
373
+ if (name === 'sec-websocket-accept') {
374
+ acceptHeader = line.slice(colonIdx2 + 1).trim()
375
+ break
376
+ }
377
+ }
378
+ }
379
+ if (!acceptHeader) {
380
+ doError(new Error('WebSocket handshake missing Sec-WebSocket-Accept'))
381
+ return
382
+ }
383
+
384
+ this._processingHandshake = true
385
+ const expectedAccept = await computeAccept(requestKey)
386
+ if (acceptHeader !== expectedAccept) {
387
+ doError(new Error('WebSocket handshake invalid Sec-WebSocket-Accept'))
388
+ this._processingHandshake = false
389
+ return
390
+ }
391
+
392
+ state = 'open'
393
+ resolved = true
394
+ this._processingHandshake = false
395
+ this._setState(State.OPEN)
396
+ break
397
+ }
398
+ }
399
+ }
400
+ }
401
+
402
+ if (event.lNetworkEvents & sock.FdEvent.FD_CLOSE) {
403
+ if (state !== 'open') {
404
+ doError(new Error('Connection closed'))
405
+ }
406
+ }
407
+ })
408
+
409
+ const ip = sock.resolve(host)
410
+ if (!ip) {
411
+ cleanup()
412
+ const self = this
413
+ os.setTimeout(() => {
414
+ self._fireError(new Error('DNS resolution failed for: ' + host))
415
+ if (self._state !== State.CLOSED) self._setState(State.CLOSED)
416
+ }, 0)
417
+ return
418
+ }
419
+ sock.connect(fd, ip, port)
420
+ }
421
+
422
+ private _sendRaw(buf: ArrayBuffer): void {
423
+ if (this._sock === null || this._sock < 0) return
424
+ if (this._ssl) {
425
+ wolfssl.wolfSSL_write(this._ssl, buf)
426
+ } else {
427
+ sock.send(this._sock, buf)
428
+ }
429
+ }
430
+
431
+ private _sendRawStr(str: string): void {
432
+ const buf = new ArrayBuffer(str.length)
433
+ const view = new Uint8Array(buf)
434
+ for (let i = 0; i < str.length; i++) view[i] = str.charCodeAt(i) & 0xFF
435
+ this._sendRaw(buf)
436
+ }
437
+
438
+ private _onData(): void {
439
+ while (true) {
440
+ let data: ArrayBuffer | null
441
+ if (this._ssl) {
442
+ data = wolfssl.wolfSSL_read(this._ssl, 8192)
443
+ } else if (this._sock !== null && this._sock >= 0) {
444
+ data = sock.recv(this._sock, 8192)
445
+ } else { break }
446
+ if (!data || data.byteLength === 0) break
447
+
448
+ const newBuf = new Uint8Array(this._readBuffer.length + data.byteLength)
449
+ newBuf.set(this._readBuffer)
450
+ newBuf.set(new Uint8Array(data), this._readBuffer.length)
451
+ this._readBuffer = newBuf
452
+
453
+ while (true) {
454
+ const frame = tryParseFrame(this._readBuffer)
455
+ if (!frame) break
456
+
457
+ this._readBuffer = this._readBuffer.slice(frame.totalLen)
458
+
459
+ if (frame.opcode === Opcode.TEXT || frame.opcode === Opcode.BINARY) {
460
+ const event = new MessageEvent('message', { data: frame.opcode === Opcode.TEXT ? _ab2str(frame.payload.slice(0).buffer) : frame.payload.slice(0).buffer })
461
+ this._dispatchEvent('message', event)
462
+ } else if (frame.opcode === Opcode.PING) {
463
+ const pong = createFrame(Opcode.PONG, frame.payload)
464
+ this._sendRaw(pong)
465
+ } else if (frame.opcode === Opcode.PONG) {
466
+ } else if (frame.opcode === Opcode.CLOSE) {
467
+ if (frame.payload.length >= 2) {
468
+ this._closeCode = (frame.payload[0]! << 8) | frame.payload[1]!
469
+ this._closeReason = _ab2str(frame.payload.slice(2).buffer)
470
+ }
471
+ this._cleanup()
472
+ this._setState(State.CLOSED)
473
+ return
474
+ }
475
+ }
476
+ }
477
+ }
478
+
479
+ private _setState(state: State): void {
480
+ if (this._state === state) return
481
+ this.readyState = state
482
+ this._state = state
483
+
484
+ if (state === State.OPEN) {
485
+ this._dispatchEvent('open', new Event('open'))
486
+ } else if (state === State.CLOSED) {
487
+ const event = new CloseEvent('close', { code: this._closeCode, reason: this._closeReason, wasClean: true })
488
+ this._dispatchEvent('close', event)
489
+ }
490
+ }
491
+
492
+ private _dispatchEvent(type: string, event: Event | MessageEvent | CloseEvent): void {
493
+ if (type === 'open' && this.onopen) this.onopen(event as Event)
494
+ else if (type === 'close' && this.onclose) this.onclose(event as CloseEvent)
495
+ else if (type === 'error' && this.onerror) this.onerror(event as Event)
496
+ else if (type === 'message' && this.onmessage) this.onmessage(event as MessageEvent)
497
+ }
498
+
499
+ private _fireError(err: Error): void {
500
+ console.error('WebSocket error:', err.message)
501
+ this._dispatchEvent('error', new Event('error'))
502
+ }
503
+
504
+ private _cleanup(): void {
505
+ if (this._ssl) { wolfssl.wolfSSL_free(this._ssl); this._ssl = null }
506
+ if (this._ctx) { wolfssl.wolfSSL_CTX_free(this._ctx); this._ctx = null }
507
+ if (this._sock !== null && this._sock >= 0) { sock.closesocket(this._sock); this._sock = null }
508
+ }
509
+ }
510
+
511
+ // ── Event/MessageEvent/CloseEvent polyfills ──
512
+
513
+ class Event {
514
+ readonly type: string
515
+ constructor(type: string, _init?: unknown) { this.type = type }
516
+ }
517
+
518
+ class MessageEvent {
519
+ readonly type: string
520
+ readonly data: unknown
521
+ constructor(type: string, init: { data: unknown }) {
522
+ this.type = type
523
+ this.data = init.data
524
+ }
525
+ }
526
+
527
+ class CloseEvent {
528
+ readonly type: string
529
+ readonly code: number
530
+ readonly reason: string
531
+ readonly wasClean: boolean
532
+ constructor(type: string, init: { code?: number; reason?: string; wasClean?: boolean }) {
533
+ this.type = type
534
+ this.code = init.code || 0
535
+ this.reason = init.reason || ''
536
+ this.wasClean = init.wasClean || false
537
+ }
538
+ }
539
+
540
+ // ── Utility ──
541
+
542
+ function _ab2str(buf: ArrayBuffer): string {
543
+ return new TextDecoder('utf-8').decode(buf)
544
+ }
545
+
546
+ // ── Global declarations ──
547
+
548
+ declare global {
549
+ interface WebSocket extends WebSocketImpl {}
550
+ var WebSocket: typeof WebSocketImpl
551
+ }
552
+
553
+ // ── Register globals ──
554
+
555
+ globalThis.WebSocket = WebSocketImpl
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "type": "module",
3
+ "name": "quickwin",
4
+ "version": "1.0.0",
5
+ "description": "QuickJS Win32 runtime — GUI, networking, WASM, FFI, and more",
6
+ "bin": {
7
+ "quickwin": "qwin.exe",
8
+ "quickwin-nowasm": "qwin-nowasm.exe",
9
+ "qwin": "qwin.exe",
10
+ "qwin-nowasm": "qwin-nowasm.exe"
11
+ },
12
+ "files": [
13
+ "qwin.exe",
14
+ "qwin-nowasm.exe",
15
+ "lib/",
16
+ "test/",
17
+ "examples/",
18
+ "vendor/",
19
+ "quickwin.d.ts",
20
+ "quickwin_const.d.ts",
21
+ "tsconfig.json",
22
+ "README.md",
23
+ "README.en.md"
24
+ ],
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/xna00/quickwin.git"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^24.13.2",
32
+ "@types/react": "^19.2.17",
33
+ "@types/react-reconciler": "^0.33.0",
34
+ "@types/ws": "^8.18.1",
35
+ "esbuild": "^0.28.0",
36
+ "typescript": "^7.0.2"
37
+ },
38
+ "dependencies": {
39
+ "react": "^19.2.7",
40
+ "react-reconciler": "^0.33.0",
41
+ "web-streams-polyfill": "^4.3.0",
42
+ "ws": "^8.21.0"
43
+ }
44
+ }