quickwin 1.0.2 → 1.1.1
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 +204 -204
- package/README.md +203 -203
- package/examples/PdfCanvas.tsx +83 -83
- package/examples/exec_server.ts +35 -35
- package/examples/pdf_preview2.ts +375 -375
- package/examples/pdf_viewer.tsx +279 -279
- package/examples/test_gallery.tsx +285 -285
- package/examples/test_react_lazy.tsx +63 -63
- package/examples/test_react_listbox.tsx +71 -71
- package/examples/test_root_window.tsx +41 -41
- package/examples/test_tab.tsx +75 -75
- package/examples/tray_demo.tsx +81 -81
- package/lib/assert.ts +6 -6
- package/lib/cache-utils.ts +40 -40
- package/lib/dumpRects.ts +25 -25
- package/lib/fetch.ts +502 -502
- package/lib/http-common.ts +399 -399
- package/lib/http-server.ts +424 -424
- package/lib/polyfill.ts +171 -171
- package/lib/react-qw/components/index.ts +17 -17
- package/lib/react-qw/index.ts +90 -90
- package/lib/react-qw/jsx.d.ts +34 -34
- package/lib/react-qw/layout.ts +111 -111
- package/lib/react-qw/props.ts +36 -36
- package/lib/react-qw/reconciler.ts +373 -373
- package/lib/stream.ts +21 -21
- package/lib/text-codec.ts +171 -171
- package/lib/text-measure.ts +54 -54
- package/lib/url.ts +201 -201
- package/lib/vendor/web-streams/ponyfill.d.mts +829 -829
- package/lib/vendor/web-streams/ponyfill.mjs +8 -8
- package/lib/websocket.js +8 -0
- package/lib/websocket.ts +563 -555
- package/package.json +50 -44
- package/qwin-nowasm-x86.exe +0 -0
- package/qwin-nowasm.exe +0 -0
- package/qwin-x86.exe +0 -0
- package/qwin.exe +0 -0
- package/test/run.ts +96 -96
- package/test/test_basic.ts +9 -9
- package/test/test_basic_fetch.ts +33 -33
- package/test/test_brotli.ts +52 -52
- package/test/test_fetch_cache.ts +187 -187
- package/test/test_ffi.ts +174 -174
- package/test/test_frame_encoding.ts +132 -132
- package/test/test_helper.ts +80 -80
- package/test/test_http_import.ts +74 -74
- package/test/test_http_server.ts +136 -136
- package/test/test_import_dir.ts +5 -5
- package/test/test_ipv6.ts +129 -129
- package/test/test_meta_url.ts +4 -4
- package/test/test_mupdf_render.ts +74 -74
- package/test/test_mupdf_twice.ts +81 -81
- package/test/test_mupdf_wasm.ts +30 -30
- package/test/test_net_event.ts +55 -55
- package/test/test_net_fetch.ts +238 -238
- package/test/test_net_websocket.ts +164 -164
- package/test/test_polyfill.ts +159 -159
- package/test/test_url.ts +183 -183
- package/test/test_wasm_basic.ts +73 -73
- package/test/test_wasm_import_global.ts +45 -45
- package/test/test_wasm_sjlj.ts +125 -125
- package/test/test_wasm_types.ts +124 -124
- package/test/test_worker.ts +24 -24
- package/test/test_worker_fetch.ts +27 -27
- package/test/test_worker_net.ts +54 -54
- package/test/test_worker_wasm_concurrent.ts +45 -45
- package/test/worker_fetch.ts +13 -13
- package/test/worker_helper.ts +1 -1
- package/test/worker_http.ts +20 -20
- package/test/worker_import_lib.ts +13 -13
- package/test/worker_module.ts +17 -17
- package/test/worker_wasm_concurrent.ts +22 -22
- package/tsconfig.json +27 -27
- package/vendor/mupdf-wasm/mupdf-wasm.d.ts +571 -571
- package/vendor/mupdf-wasm/mupdf-wasm.js +2749 -2749
- package/vendor/mupdf-wasm/mupdf.d.ts +939 -939
- package/vendor/mupdf-wasm/mupdf.js +3317 -3317
package/lib/http-common.ts
CHANGED
|
@@ -1,399 +1,399 @@
|
|
|
1
|
-
import './polyfill.js'
|
|
2
|
-
import './stream.js'
|
|
3
|
-
|
|
4
|
-
// ── Byte helpers ──
|
|
5
|
-
|
|
6
|
-
export function _concat(parts: Uint8Array[]): Uint8Array<ArrayBuffer> {
|
|
7
|
-
if (parts.length === 0) return new Uint8Array(0)
|
|
8
|
-
let totalLen = 0
|
|
9
|
-
for (const p of parts) totalLen += p.length
|
|
10
|
-
const result = new Uint8Array(totalLen)
|
|
11
|
-
let offset = 0
|
|
12
|
-
for (const p of parts) { result.set(p, offset); offset += p.length }
|
|
13
|
-
return result
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function _toReadableStream(body: BodyInit): ReadableStream<Uint8Array> {
|
|
17
|
-
if (body instanceof ReadableStream) return body
|
|
18
|
-
const bytes = (typeof body === 'string')
|
|
19
|
-
? new TextEncoder().encode(body)
|
|
20
|
-
: (body instanceof ArrayBuffer)
|
|
21
|
-
? new Uint8Array(body)
|
|
22
|
-
: new Uint8Array(body.buffer, body.byteOffset, body.byteLength)
|
|
23
|
-
return new ReadableStream<Uint8Array>({
|
|
24
|
-
start(ctrl) {
|
|
25
|
-
ctrl.enqueue(bytes)
|
|
26
|
-
ctrl.close()
|
|
27
|
-
}
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function _readStream(stream: ReadableStream<Uint8Array>): Promise<Uint8Array<ArrayBuffer>> {
|
|
32
|
-
const reader = stream.getReader()
|
|
33
|
-
const chunks: Uint8Array[] = []
|
|
34
|
-
while (true) {
|
|
35
|
-
const { done, value } = await reader.read()
|
|
36
|
-
if (done) break
|
|
37
|
-
chunks.push(value)
|
|
38
|
-
}
|
|
39
|
-
return _concat(chunks)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function _hasChunkedEnd(data: Uint8Array): boolean {
|
|
43
|
-
if (data.length < 7) return false
|
|
44
|
-
let i = data.length - 7
|
|
45
|
-
return data[i] === 0x0D && data[i+1] === 0x0A && data[i+2] === 0x30 && data[i+3] === 0x0D && data[i+4] === 0x0A && data[i+5] === 0x0D && data[i+6] === 0x0A
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function decodeChunked(data: Uint8Array): Uint8Array {
|
|
49
|
-
const chunks: Uint8Array[] = []
|
|
50
|
-
let pos = 0
|
|
51
|
-
while (pos < data.length) {
|
|
52
|
-
let crlf = -1
|
|
53
|
-
for (let i = pos; i < data.length - 1; i++) {
|
|
54
|
-
if (data[i] === 0x0D && data[i + 1] === 0x0A) { crlf = i; break }
|
|
55
|
-
}
|
|
56
|
-
if (crlf < 0) break
|
|
57
|
-
|
|
58
|
-
let size = 0
|
|
59
|
-
for (let i = pos; i < crlf; i++) {
|
|
60
|
-
const b = data[i]!
|
|
61
|
-
if (b >= 0x30 && b <= 0x39) size = size * 16 + (b - 0x30)
|
|
62
|
-
else if (b >= 0x41 && b <= 0x46) size = size * 16 + (b - 0x37)
|
|
63
|
-
else if (b >= 0x61 && b <= 0x66) size = size * 16 + (b - 0x57)
|
|
64
|
-
else break
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const dataStart = crlf + 2
|
|
68
|
-
if (size === 0) {
|
|
69
|
-
for (let i = dataStart; i < data.length - 3; i++) {
|
|
70
|
-
if (data[i] === 0x0D && data[i+1] === 0x0A && data[i+2] === 0x0D && data[i+3] === 0x0A) {
|
|
71
|
-
pos = i + 4; break
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (pos === dataStart) break
|
|
75
|
-
break
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (dataStart + size > data.length) break
|
|
79
|
-
chunks.push(data.subarray(dataStart, dataStart + size))
|
|
80
|
-
pos = dataStart + size + 2
|
|
81
|
-
}
|
|
82
|
-
return _concat(chunks)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ── Headers ──
|
|
86
|
-
|
|
87
|
-
// Flat array storage: [name0, value0, name1, value1, ...]
|
|
88
|
-
// Pairs at even/odd indices. Strings are immutable, so [...this._headers] is a correct deep copy.
|
|
89
|
-
|
|
90
|
-
export class HeadersImpl {
|
|
91
|
-
private _headers: string[] = []
|
|
92
|
-
|
|
93
|
-
constructor(init?: HeadersInit) {
|
|
94
|
-
if (init) {
|
|
95
|
-
if (init instanceof HeadersImpl) {
|
|
96
|
-
this._headers = [...init._headers]
|
|
97
|
-
} else if (Array.isArray(init)) {
|
|
98
|
-
this._headers = init.flatMap(([k, v]) => [k.toLowerCase(), v])
|
|
99
|
-
} else if (typeof init === 'object') {
|
|
100
|
-
this._headers = Object.entries(init).flatMap(([k, v]) => [k.toLowerCase(), v])
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
append(name: string, value: string): void {
|
|
106
|
-
this._headers.push(name.toLowerCase(), value)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
delete(name: string): void {
|
|
110
|
-
const key = name.toLowerCase()
|
|
111
|
-
const h = this._headers
|
|
112
|
-
let i = h.length - 2
|
|
113
|
-
while (i >= 0) {
|
|
114
|
-
if (h[i] === key) { h.splice(i, 2); i -= 2 } else i -= 2
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
get(name: string): string | null {
|
|
119
|
-
const key = name.toLowerCase()
|
|
120
|
-
const matches = [...this.entries()].filter(([k]) => k === key).map(([, v]) => v)
|
|
121
|
-
return matches.length ? matches.join(', ') : null
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
has(name: string): boolean {
|
|
125
|
-
return [...this.keys()].includes(name.toLowerCase())
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
set(name: string, value: string): void {
|
|
129
|
-
const key = name.toLowerCase()
|
|
130
|
-
const h = this._headers
|
|
131
|
-
let found = false
|
|
132
|
-
let i = h.length - 2
|
|
133
|
-
while (i >= 0) {
|
|
134
|
-
if (h[i] === key) {
|
|
135
|
-
if (!found) {
|
|
136
|
-
h[i + 1] = value
|
|
137
|
-
found = true
|
|
138
|
-
} else {
|
|
139
|
-
h.splice(i, 2)
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
i -= 2
|
|
143
|
-
}
|
|
144
|
-
if (!found) h.push(key, value)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
forEach(callback: (value: string, name: string, headers: HeadersImpl) => void): void {
|
|
148
|
-
for (const [name, value] of this) {
|
|
149
|
-
callback(value, name, this)
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
*entries(): IterableIterator<[string, string]> {
|
|
154
|
-
const h = this._headers
|
|
155
|
-
for (let i = 0; i < h.length; i += 2) {
|
|
156
|
-
yield [h[i]!, h[i + 1]!]
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
*keys(): IterableIterator<string> {
|
|
161
|
-
for (const [k] of this) yield k
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
*values(): IterableIterator<string> {
|
|
165
|
-
for (const [, v] of this) yield v
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
[Symbol.iterator](): IterableIterator<[string, string]> {
|
|
169
|
-
return this.entries()
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// ── Request ──
|
|
174
|
-
|
|
175
|
-
export class RequestImpl {
|
|
176
|
-
readonly url: string
|
|
177
|
-
readonly method: string
|
|
178
|
-
readonly headers: HeadersImpl
|
|
179
|
-
private _body: ReadableStream<Uint8Array> | null
|
|
180
|
-
readonly redirect: RequestRedirect
|
|
181
|
-
readonly timeout: number
|
|
182
|
-
readonly maxRedirects: number
|
|
183
|
-
private _bodyConsumed: boolean = false
|
|
184
|
-
readonly integrity: string = ''
|
|
185
|
-
readonly keepalive: boolean = false
|
|
186
|
-
readonly mode: string = 'cors'
|
|
187
|
-
readonly cache: string = 'default'
|
|
188
|
-
readonly credentials: string = 'same-origin'
|
|
189
|
-
readonly referrer: string = 'about:client'
|
|
190
|
-
readonly referrerPolicy: string = ''
|
|
191
|
-
readonly signal: AbortSignal | null = null
|
|
192
|
-
readonly destination: string = ''
|
|
193
|
-
|
|
194
|
-
get body(): ReadableStream<Uint8Array> | null { return this._body }
|
|
195
|
-
|
|
196
|
-
get bodyUsed(): boolean {
|
|
197
|
-
return this._bodyConsumed || (this._body?.locked ?? false)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
constructor(input: string | RequestImpl, init: RequestInit = {}) {
|
|
201
|
-
const initBody = init.body
|
|
202
|
-
if (input instanceof RequestImpl) {
|
|
203
|
-
this.url = input.url
|
|
204
|
-
this.method = init.method || input.method
|
|
205
|
-
this.headers = new HeadersImpl(init.headers || input.headers)
|
|
206
|
-
if (initBody !== undefined && initBody !== null) {
|
|
207
|
-
this._body = _toReadableStream(initBody)
|
|
208
|
-
} else {
|
|
209
|
-
this._body = input._body
|
|
210
|
-
}
|
|
211
|
-
this.redirect = init.redirect || input.redirect
|
|
212
|
-
this.timeout = init.timeout || input.timeout
|
|
213
|
-
this.maxRedirects = init.maxRedirects || input.maxRedirects
|
|
214
|
-
} else {
|
|
215
|
-
this.url = input
|
|
216
|
-
this.method = init.method || 'GET'
|
|
217
|
-
this.headers = new HeadersImpl(init.headers)
|
|
218
|
-
this._body = initBody !== null && initBody !== undefined ? _toReadableStream(initBody) : null
|
|
219
|
-
this.redirect = init.redirect || 'follow'
|
|
220
|
-
this.timeout = init.timeout || 30000
|
|
221
|
-
this.maxRedirects = init.maxRedirects || 5
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
async arrayBuffer(): Promise<ArrayBuffer> {
|
|
226
|
-
if (!this.body) return new ArrayBuffer(0)
|
|
227
|
-
if (this._bodyConsumed) throw new TypeError('Body already used')
|
|
228
|
-
this._bodyConsumed = true
|
|
229
|
-
const bytes = await _readStream(this.body)
|
|
230
|
-
return bytes.buffer
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async json<T = unknown>(): Promise<T> {
|
|
234
|
-
return JSON.parse(await this.text())
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
async text(): Promise<string> {
|
|
238
|
-
if (!this.body) return ''
|
|
239
|
-
if (this._bodyConsumed) throw new TypeError('Body already used')
|
|
240
|
-
this._bodyConsumed = true
|
|
241
|
-
const bytes = await _readStream(this.body)
|
|
242
|
-
return new TextDecoder('utf-8').decode(bytes)
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
clone(): RequestImpl {
|
|
246
|
-
if (this.bodyUsed) throw new TypeError('Body already used')
|
|
247
|
-
if (this._body) {
|
|
248
|
-
const [branch1, branch2] = this._body.tee()
|
|
249
|
-
this._body = branch1
|
|
250
|
-
const req = new RequestImpl(this.url, {
|
|
251
|
-
method: this.method,
|
|
252
|
-
headers: this.headers,
|
|
253
|
-
body: branch2,
|
|
254
|
-
redirect: this.redirect,
|
|
255
|
-
timeout: this.timeout,
|
|
256
|
-
maxRedirects: this.maxRedirects,
|
|
257
|
-
})
|
|
258
|
-
return req
|
|
259
|
-
}
|
|
260
|
-
return new RequestImpl(this.url, {
|
|
261
|
-
method: this.method,
|
|
262
|
-
headers: this.headers,
|
|
263
|
-
redirect: this.redirect,
|
|
264
|
-
timeout: this.timeout,
|
|
265
|
-
maxRedirects: this.maxRedirects,
|
|
266
|
-
})
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// ── Response ──
|
|
271
|
-
|
|
272
|
-
export class ResponseImpl {
|
|
273
|
-
readonly status: number
|
|
274
|
-
readonly statusText: string
|
|
275
|
-
readonly ok: boolean
|
|
276
|
-
|
|
277
|
-
private _headers: HeadersImpl
|
|
278
|
-
_redirected: boolean = false
|
|
279
|
-
private _type: ResponseType = 'basic'
|
|
280
|
-
_url: string = ''
|
|
281
|
-
private _body: ReadableStream<Uint8Array>
|
|
282
|
-
private _bodyConsumed: boolean = false
|
|
283
|
-
|
|
284
|
-
get body(): ReadableStream<Uint8Array> { return this._body }
|
|
285
|
-
get bodyUsed(): boolean { return this._bodyConsumed || this._body.locked }
|
|
286
|
-
get headers(): HeadersImpl { return this._headers }
|
|
287
|
-
get redirected(): boolean { return this._redirected }
|
|
288
|
-
get type(): ResponseType { return this._type }
|
|
289
|
-
get url(): string { return this._url }
|
|
290
|
-
|
|
291
|
-
constructor(body?: BodyInit | null, init?: ResponseInit) {
|
|
292
|
-
const status = init?.status ?? 200
|
|
293
|
-
const statusText = init?.statusText ?? ''
|
|
294
|
-
const headers = new HeadersImpl(init?.headers)
|
|
295
|
-
this.status = status
|
|
296
|
-
this.statusText = statusText
|
|
297
|
-
this._headers = headers
|
|
298
|
-
this.ok = status >= 200 && status < 300
|
|
299
|
-
this._body = body != null ? _toReadableStream(body) : new ReadableStream<Uint8Array>({
|
|
300
|
-
start(ctrl) { ctrl.close() }
|
|
301
|
-
})
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
async text(): Promise<string> {
|
|
305
|
-
if (this.bodyUsed) throw new TypeError('Body already used')
|
|
306
|
-
this._bodyConsumed = true
|
|
307
|
-
const bytes = await _readStream(this._body)
|
|
308
|
-
return new TextDecoder('utf-8').decode(bytes)
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
async json<T = unknown>(): Promise<T> {
|
|
312
|
-
return JSON.parse(await this.text())
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
async arrayBuffer(): Promise<ArrayBuffer> {
|
|
316
|
-
if (this.bodyUsed) throw new TypeError('Body already used')
|
|
317
|
-
this._bodyConsumed = true
|
|
318
|
-
const bytes = await _readStream(this._body)
|
|
319
|
-
return bytes.buffer
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
async bytes(): Promise<Uint8Array> {
|
|
323
|
-
if (this.bodyUsed) throw new TypeError('Body already used')
|
|
324
|
-
this._bodyConsumed = true
|
|
325
|
-
return await _readStream(this._body)
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
clone(): Response {
|
|
329
|
-
if (this.bodyUsed) throw new TypeError('Body already used')
|
|
330
|
-
const [branch1, branch2] = this._body.tee()
|
|
331
|
-
this._body = branch1
|
|
332
|
-
return new ResponseImpl(branch2, { status: this.status, statusText: this.statusText, headers: this._headers })
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
_applyDecompressedBody(stream: ReadableStream<Uint8Array>, _body: ArrayBuffer, newHeaders: HeadersImpl): void {
|
|
336
|
-
this._bodyConsumed = false
|
|
337
|
-
this._body = stream
|
|
338
|
-
this._headers = newHeaders
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// ── Global declarations ──
|
|
343
|
-
|
|
344
|
-
declare global {
|
|
345
|
-
type HeadersInit = [string, string][] | Record<string, string> | Headers
|
|
346
|
-
type BodyInit = ReadableStream<Uint8Array> | string | ArrayBuffer | ArrayBufferView
|
|
347
|
-
type RequestRedirect = 'error' | 'follow' | 'manual'
|
|
348
|
-
type RequestCache = 'default' | 'force-cache' | 'no-cache' | 'no-store' | 'only-if-cached' | 'reload'
|
|
349
|
-
type RequestCredentials = 'include' | 'omit' | 'same-origin'
|
|
350
|
-
type RequestMode = 'cors' | 'navigate' | 'no-cors' | 'same-origin'
|
|
351
|
-
type ResponseType = 'basic' | 'cors' | 'default' | 'error' | 'opaque' | 'opaqueredirect'
|
|
352
|
-
|
|
353
|
-
interface AbortSignal {
|
|
354
|
-
readonly aborted: boolean;
|
|
355
|
-
readonly reason: unknown;
|
|
356
|
-
onabort: ((event: Event) => void) | null;
|
|
357
|
-
throwIfAborted(): void;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
interface RequestInit {
|
|
361
|
-
body?: BodyInit | null;
|
|
362
|
-
cache?: RequestCache;
|
|
363
|
-
credentials?: RequestCredentials;
|
|
364
|
-
headers?: HeadersInit;
|
|
365
|
-
integrity?: string;
|
|
366
|
-
keepalive?: boolean;
|
|
367
|
-
method?: string;
|
|
368
|
-
mode?: RequestMode;
|
|
369
|
-
redirect?: RequestRedirect;
|
|
370
|
-
referrer?: string;
|
|
371
|
-
referrerPolicy?: string;
|
|
372
|
-
signal?: AbortSignal | null;
|
|
373
|
-
window?: null;
|
|
374
|
-
timeout?: number;
|
|
375
|
-
maxRedirects?: number;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
interface ResponseInit {
|
|
379
|
-
status?: number;
|
|
380
|
-
statusText?: string;
|
|
381
|
-
headers?: HeadersInit;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
interface Headers extends HeadersImpl {}
|
|
385
|
-
var Headers: typeof HeadersImpl
|
|
386
|
-
interface Response extends Omit<ResponseImpl, '_url' | '_redirected' | '_applyDecompressedBody'> {}
|
|
387
|
-
interface ResponseConstructor {
|
|
388
|
-
new(body?: BodyInit | null, init?: ResponseInit): Response
|
|
389
|
-
}
|
|
390
|
-
var Response: ResponseConstructor
|
|
391
|
-
interface Request extends RequestImpl {}
|
|
392
|
-
var Request: typeof RequestImpl
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// ── Register globals ──
|
|
396
|
-
|
|
397
|
-
globalThis.Headers = HeadersImpl
|
|
398
|
-
globalThis.Request = RequestImpl
|
|
399
|
-
globalThis.Response = ResponseImpl
|
|
1
|
+
import './polyfill.js'
|
|
2
|
+
import './stream.js'
|
|
3
|
+
|
|
4
|
+
// ── Byte helpers ──
|
|
5
|
+
|
|
6
|
+
export function _concat(parts: Uint8Array[]): Uint8Array<ArrayBuffer> {
|
|
7
|
+
if (parts.length === 0) return new Uint8Array(0)
|
|
8
|
+
let totalLen = 0
|
|
9
|
+
for (const p of parts) totalLen += p.length
|
|
10
|
+
const result = new Uint8Array(totalLen)
|
|
11
|
+
let offset = 0
|
|
12
|
+
for (const p of parts) { result.set(p, offset); offset += p.length }
|
|
13
|
+
return result
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function _toReadableStream(body: BodyInit): ReadableStream<Uint8Array> {
|
|
17
|
+
if (body instanceof ReadableStream) return body
|
|
18
|
+
const bytes = (typeof body === 'string')
|
|
19
|
+
? new TextEncoder().encode(body)
|
|
20
|
+
: (body instanceof ArrayBuffer)
|
|
21
|
+
? new Uint8Array(body)
|
|
22
|
+
: new Uint8Array(body.buffer, body.byteOffset, body.byteLength)
|
|
23
|
+
return new ReadableStream<Uint8Array>({
|
|
24
|
+
start(ctrl) {
|
|
25
|
+
ctrl.enqueue(bytes)
|
|
26
|
+
ctrl.close()
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function _readStream(stream: ReadableStream<Uint8Array>): Promise<Uint8Array<ArrayBuffer>> {
|
|
32
|
+
const reader = stream.getReader()
|
|
33
|
+
const chunks: Uint8Array[] = []
|
|
34
|
+
while (true) {
|
|
35
|
+
const { done, value } = await reader.read()
|
|
36
|
+
if (done) break
|
|
37
|
+
chunks.push(value)
|
|
38
|
+
}
|
|
39
|
+
return _concat(chunks)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function _hasChunkedEnd(data: Uint8Array): boolean {
|
|
43
|
+
if (data.length < 7) return false
|
|
44
|
+
let i = data.length - 7
|
|
45
|
+
return data[i] === 0x0D && data[i+1] === 0x0A && data[i+2] === 0x30 && data[i+3] === 0x0D && data[i+4] === 0x0A && data[i+5] === 0x0D && data[i+6] === 0x0A
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function decodeChunked(data: Uint8Array): Uint8Array {
|
|
49
|
+
const chunks: Uint8Array[] = []
|
|
50
|
+
let pos = 0
|
|
51
|
+
while (pos < data.length) {
|
|
52
|
+
let crlf = -1
|
|
53
|
+
for (let i = pos; i < data.length - 1; i++) {
|
|
54
|
+
if (data[i] === 0x0D && data[i + 1] === 0x0A) { crlf = i; break }
|
|
55
|
+
}
|
|
56
|
+
if (crlf < 0) break
|
|
57
|
+
|
|
58
|
+
let size = 0
|
|
59
|
+
for (let i = pos; i < crlf; i++) {
|
|
60
|
+
const b = data[i]!
|
|
61
|
+
if (b >= 0x30 && b <= 0x39) size = size * 16 + (b - 0x30)
|
|
62
|
+
else if (b >= 0x41 && b <= 0x46) size = size * 16 + (b - 0x37)
|
|
63
|
+
else if (b >= 0x61 && b <= 0x66) size = size * 16 + (b - 0x57)
|
|
64
|
+
else break
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const dataStart = crlf + 2
|
|
68
|
+
if (size === 0) {
|
|
69
|
+
for (let i = dataStart; i < data.length - 3; i++) {
|
|
70
|
+
if (data[i] === 0x0D && data[i+1] === 0x0A && data[i+2] === 0x0D && data[i+3] === 0x0A) {
|
|
71
|
+
pos = i + 4; break
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (pos === dataStart) break
|
|
75
|
+
break
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (dataStart + size > data.length) break
|
|
79
|
+
chunks.push(data.subarray(dataStart, dataStart + size))
|
|
80
|
+
pos = dataStart + size + 2
|
|
81
|
+
}
|
|
82
|
+
return _concat(chunks)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── Headers ──
|
|
86
|
+
|
|
87
|
+
// Flat array storage: [name0, value0, name1, value1, ...]
|
|
88
|
+
// Pairs at even/odd indices. Strings are immutable, so [...this._headers] is a correct deep copy.
|
|
89
|
+
|
|
90
|
+
export class HeadersImpl {
|
|
91
|
+
private _headers: string[] = []
|
|
92
|
+
|
|
93
|
+
constructor(init?: HeadersInit) {
|
|
94
|
+
if (init) {
|
|
95
|
+
if (init instanceof HeadersImpl) {
|
|
96
|
+
this._headers = [...init._headers]
|
|
97
|
+
} else if (Array.isArray(init)) {
|
|
98
|
+
this._headers = init.flatMap(([k, v]) => [k.toLowerCase(), v])
|
|
99
|
+
} else if (typeof init === 'object') {
|
|
100
|
+
this._headers = Object.entries(init).flatMap(([k, v]) => [k.toLowerCase(), v])
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
append(name: string, value: string): void {
|
|
106
|
+
this._headers.push(name.toLowerCase(), value)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
delete(name: string): void {
|
|
110
|
+
const key = name.toLowerCase()
|
|
111
|
+
const h = this._headers
|
|
112
|
+
let i = h.length - 2
|
|
113
|
+
while (i >= 0) {
|
|
114
|
+
if (h[i] === key) { h.splice(i, 2); i -= 2 } else i -= 2
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
get(name: string): string | null {
|
|
119
|
+
const key = name.toLowerCase()
|
|
120
|
+
const matches = [...this.entries()].filter(([k]) => k === key).map(([, v]) => v)
|
|
121
|
+
return matches.length ? matches.join(', ') : null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
has(name: string): boolean {
|
|
125
|
+
return [...this.keys()].includes(name.toLowerCase())
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
set(name: string, value: string): void {
|
|
129
|
+
const key = name.toLowerCase()
|
|
130
|
+
const h = this._headers
|
|
131
|
+
let found = false
|
|
132
|
+
let i = h.length - 2
|
|
133
|
+
while (i >= 0) {
|
|
134
|
+
if (h[i] === key) {
|
|
135
|
+
if (!found) {
|
|
136
|
+
h[i + 1] = value
|
|
137
|
+
found = true
|
|
138
|
+
} else {
|
|
139
|
+
h.splice(i, 2)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
i -= 2
|
|
143
|
+
}
|
|
144
|
+
if (!found) h.push(key, value)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
forEach(callback: (value: string, name: string, headers: HeadersImpl) => void): void {
|
|
148
|
+
for (const [name, value] of this) {
|
|
149
|
+
callback(value, name, this)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
*entries(): IterableIterator<[string, string]> {
|
|
154
|
+
const h = this._headers
|
|
155
|
+
for (let i = 0; i < h.length; i += 2) {
|
|
156
|
+
yield [h[i]!, h[i + 1]!]
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
*keys(): IterableIterator<string> {
|
|
161
|
+
for (const [k] of this) yield k
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
*values(): IterableIterator<string> {
|
|
165
|
+
for (const [, v] of this) yield v
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
[Symbol.iterator](): IterableIterator<[string, string]> {
|
|
169
|
+
return this.entries()
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── Request ──
|
|
174
|
+
|
|
175
|
+
export class RequestImpl {
|
|
176
|
+
readonly url: string
|
|
177
|
+
readonly method: string
|
|
178
|
+
readonly headers: HeadersImpl
|
|
179
|
+
private _body: ReadableStream<Uint8Array> | null
|
|
180
|
+
readonly redirect: RequestRedirect
|
|
181
|
+
readonly timeout: number
|
|
182
|
+
readonly maxRedirects: number
|
|
183
|
+
private _bodyConsumed: boolean = false
|
|
184
|
+
readonly integrity: string = ''
|
|
185
|
+
readonly keepalive: boolean = false
|
|
186
|
+
readonly mode: string = 'cors'
|
|
187
|
+
readonly cache: string = 'default'
|
|
188
|
+
readonly credentials: string = 'same-origin'
|
|
189
|
+
readonly referrer: string = 'about:client'
|
|
190
|
+
readonly referrerPolicy: string = ''
|
|
191
|
+
readonly signal: AbortSignal | null = null
|
|
192
|
+
readonly destination: string = ''
|
|
193
|
+
|
|
194
|
+
get body(): ReadableStream<Uint8Array> | null { return this._body }
|
|
195
|
+
|
|
196
|
+
get bodyUsed(): boolean {
|
|
197
|
+
return this._bodyConsumed || (this._body?.locked ?? false)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
constructor(input: string | RequestImpl, init: RequestInit = {}) {
|
|
201
|
+
const initBody = init.body
|
|
202
|
+
if (input instanceof RequestImpl) {
|
|
203
|
+
this.url = input.url
|
|
204
|
+
this.method = init.method || input.method
|
|
205
|
+
this.headers = new HeadersImpl(init.headers || input.headers)
|
|
206
|
+
if (initBody !== undefined && initBody !== null) {
|
|
207
|
+
this._body = _toReadableStream(initBody)
|
|
208
|
+
} else {
|
|
209
|
+
this._body = input._body
|
|
210
|
+
}
|
|
211
|
+
this.redirect = init.redirect || input.redirect
|
|
212
|
+
this.timeout = init.timeout || input.timeout
|
|
213
|
+
this.maxRedirects = init.maxRedirects || input.maxRedirects
|
|
214
|
+
} else {
|
|
215
|
+
this.url = input
|
|
216
|
+
this.method = init.method || 'GET'
|
|
217
|
+
this.headers = new HeadersImpl(init.headers)
|
|
218
|
+
this._body = initBody !== null && initBody !== undefined ? _toReadableStream(initBody) : null
|
|
219
|
+
this.redirect = init.redirect || 'follow'
|
|
220
|
+
this.timeout = init.timeout || 30000
|
|
221
|
+
this.maxRedirects = init.maxRedirects || 5
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async arrayBuffer(): Promise<ArrayBuffer> {
|
|
226
|
+
if (!this.body) return new ArrayBuffer(0)
|
|
227
|
+
if (this._bodyConsumed) throw new TypeError('Body already used')
|
|
228
|
+
this._bodyConsumed = true
|
|
229
|
+
const bytes = await _readStream(this.body)
|
|
230
|
+
return bytes.buffer
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async json<T = unknown>(): Promise<T> {
|
|
234
|
+
return JSON.parse(await this.text())
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async text(): Promise<string> {
|
|
238
|
+
if (!this.body) return ''
|
|
239
|
+
if (this._bodyConsumed) throw new TypeError('Body already used')
|
|
240
|
+
this._bodyConsumed = true
|
|
241
|
+
const bytes = await _readStream(this.body)
|
|
242
|
+
return new TextDecoder('utf-8').decode(bytes)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
clone(): RequestImpl {
|
|
246
|
+
if (this.bodyUsed) throw new TypeError('Body already used')
|
|
247
|
+
if (this._body) {
|
|
248
|
+
const [branch1, branch2] = this._body.tee()
|
|
249
|
+
this._body = branch1
|
|
250
|
+
const req = new RequestImpl(this.url, {
|
|
251
|
+
method: this.method,
|
|
252
|
+
headers: this.headers,
|
|
253
|
+
body: branch2,
|
|
254
|
+
redirect: this.redirect,
|
|
255
|
+
timeout: this.timeout,
|
|
256
|
+
maxRedirects: this.maxRedirects,
|
|
257
|
+
})
|
|
258
|
+
return req
|
|
259
|
+
}
|
|
260
|
+
return new RequestImpl(this.url, {
|
|
261
|
+
method: this.method,
|
|
262
|
+
headers: this.headers,
|
|
263
|
+
redirect: this.redirect,
|
|
264
|
+
timeout: this.timeout,
|
|
265
|
+
maxRedirects: this.maxRedirects,
|
|
266
|
+
})
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ── Response ──
|
|
271
|
+
|
|
272
|
+
export class ResponseImpl {
|
|
273
|
+
readonly status: number
|
|
274
|
+
readonly statusText: string
|
|
275
|
+
readonly ok: boolean
|
|
276
|
+
|
|
277
|
+
private _headers: HeadersImpl
|
|
278
|
+
_redirected: boolean = false
|
|
279
|
+
private _type: ResponseType = 'basic'
|
|
280
|
+
_url: string = ''
|
|
281
|
+
private _body: ReadableStream<Uint8Array>
|
|
282
|
+
private _bodyConsumed: boolean = false
|
|
283
|
+
|
|
284
|
+
get body(): ReadableStream<Uint8Array> { return this._body }
|
|
285
|
+
get bodyUsed(): boolean { return this._bodyConsumed || this._body.locked }
|
|
286
|
+
get headers(): HeadersImpl { return this._headers }
|
|
287
|
+
get redirected(): boolean { return this._redirected }
|
|
288
|
+
get type(): ResponseType { return this._type }
|
|
289
|
+
get url(): string { return this._url }
|
|
290
|
+
|
|
291
|
+
constructor(body?: BodyInit | null, init?: ResponseInit) {
|
|
292
|
+
const status = init?.status ?? 200
|
|
293
|
+
const statusText = init?.statusText ?? ''
|
|
294
|
+
const headers = new HeadersImpl(init?.headers)
|
|
295
|
+
this.status = status
|
|
296
|
+
this.statusText = statusText
|
|
297
|
+
this._headers = headers
|
|
298
|
+
this.ok = status >= 200 && status < 300
|
|
299
|
+
this._body = body != null ? _toReadableStream(body) : new ReadableStream<Uint8Array>({
|
|
300
|
+
start(ctrl) { ctrl.close() }
|
|
301
|
+
})
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async text(): Promise<string> {
|
|
305
|
+
if (this.bodyUsed) throw new TypeError('Body already used')
|
|
306
|
+
this._bodyConsumed = true
|
|
307
|
+
const bytes = await _readStream(this._body)
|
|
308
|
+
return new TextDecoder('utf-8').decode(bytes)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async json<T = unknown>(): Promise<T> {
|
|
312
|
+
return JSON.parse(await this.text())
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async arrayBuffer(): Promise<ArrayBuffer> {
|
|
316
|
+
if (this.bodyUsed) throw new TypeError('Body already used')
|
|
317
|
+
this._bodyConsumed = true
|
|
318
|
+
const bytes = await _readStream(this._body)
|
|
319
|
+
return bytes.buffer
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async bytes(): Promise<Uint8Array> {
|
|
323
|
+
if (this.bodyUsed) throw new TypeError('Body already used')
|
|
324
|
+
this._bodyConsumed = true
|
|
325
|
+
return await _readStream(this._body)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
clone(): Response {
|
|
329
|
+
if (this.bodyUsed) throw new TypeError('Body already used')
|
|
330
|
+
const [branch1, branch2] = this._body.tee()
|
|
331
|
+
this._body = branch1
|
|
332
|
+
return new ResponseImpl(branch2, { status: this.status, statusText: this.statusText, headers: this._headers })
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
_applyDecompressedBody(stream: ReadableStream<Uint8Array>, _body: ArrayBuffer, newHeaders: HeadersImpl): void {
|
|
336
|
+
this._bodyConsumed = false
|
|
337
|
+
this._body = stream
|
|
338
|
+
this._headers = newHeaders
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ── Global declarations ──
|
|
343
|
+
|
|
344
|
+
declare global {
|
|
345
|
+
type HeadersInit = [string, string][] | Record<string, string> | Headers
|
|
346
|
+
type BodyInit = ReadableStream<Uint8Array> | string | ArrayBuffer | ArrayBufferView
|
|
347
|
+
type RequestRedirect = 'error' | 'follow' | 'manual'
|
|
348
|
+
type RequestCache = 'default' | 'force-cache' | 'no-cache' | 'no-store' | 'only-if-cached' | 'reload'
|
|
349
|
+
type RequestCredentials = 'include' | 'omit' | 'same-origin'
|
|
350
|
+
type RequestMode = 'cors' | 'navigate' | 'no-cors' | 'same-origin'
|
|
351
|
+
type ResponseType = 'basic' | 'cors' | 'default' | 'error' | 'opaque' | 'opaqueredirect'
|
|
352
|
+
|
|
353
|
+
interface AbortSignal {
|
|
354
|
+
readonly aborted: boolean;
|
|
355
|
+
readonly reason: unknown;
|
|
356
|
+
onabort: ((event: Event) => void) | null;
|
|
357
|
+
throwIfAborted(): void;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
interface RequestInit {
|
|
361
|
+
body?: BodyInit | null;
|
|
362
|
+
cache?: RequestCache;
|
|
363
|
+
credentials?: RequestCredentials;
|
|
364
|
+
headers?: HeadersInit;
|
|
365
|
+
integrity?: string;
|
|
366
|
+
keepalive?: boolean;
|
|
367
|
+
method?: string;
|
|
368
|
+
mode?: RequestMode;
|
|
369
|
+
redirect?: RequestRedirect;
|
|
370
|
+
referrer?: string;
|
|
371
|
+
referrerPolicy?: string;
|
|
372
|
+
signal?: AbortSignal | null;
|
|
373
|
+
window?: null;
|
|
374
|
+
timeout?: number;
|
|
375
|
+
maxRedirects?: number;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
interface ResponseInit {
|
|
379
|
+
status?: number;
|
|
380
|
+
statusText?: string;
|
|
381
|
+
headers?: HeadersInit;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
interface Headers extends HeadersImpl {}
|
|
385
|
+
var Headers: typeof HeadersImpl
|
|
386
|
+
interface Response extends Omit<ResponseImpl, '_url' | '_redirected' | '_applyDecompressedBody'> {}
|
|
387
|
+
interface ResponseConstructor {
|
|
388
|
+
new(body?: BodyInit | null, init?: ResponseInit): Response
|
|
389
|
+
}
|
|
390
|
+
var Response: ResponseConstructor
|
|
391
|
+
interface Request extends RequestImpl {}
|
|
392
|
+
var Request: typeof RequestImpl
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ── Register globals ──
|
|
396
|
+
|
|
397
|
+
globalThis.Headers = HeadersImpl
|
|
398
|
+
globalThis.Request = RequestImpl
|
|
399
|
+
globalThis.Response = ResponseImpl
|