76 lines
2.7 KiB
TypeScript
76 lines
2.7 KiB
TypeScript
import { net } from 'electron'
|
|
import { Readable } from 'node:stream'
|
|
import { byteStringResponseHeaders } from './response-headers.js'
|
|
|
|
export const fetchNetwork: typeof fetch = async (input, init) => {
|
|
const request = new Request(input, init)
|
|
if (request.redirect !== 'manual') return net.fetch(request)
|
|
if (request.method !== 'GET') throw new Error('Ручные переходы доступны только для загрузки файлов')
|
|
// net.fetch rejects manual redirects before it can return their status and headers.
|
|
return new Promise<Response>((resolve, reject) => {
|
|
const client = net.request({
|
|
url: request.url,
|
|
method: 'GET',
|
|
redirect: 'manual',
|
|
credentials: request.credentials,
|
|
})
|
|
let body: Readable | null = null
|
|
const cleanup = () => request.signal.removeEventListener('abort', abort)
|
|
const fail = (error: unknown) => {
|
|
cleanup()
|
|
reject(error)
|
|
client.abort()
|
|
}
|
|
const abort = () => {
|
|
const error = request.signal.reason
|
|
body?.destroy(error instanceof Error ? error : new Error('Загрузка отменена'))
|
|
fail(error)
|
|
}
|
|
const headersOf = (values: Record<string, string | string[]>) => {
|
|
const headers = new Headers()
|
|
for (const [name, entries] of Object.entries(byteStringResponseHeaders(values)))
|
|
for (const value of entries) headers.append(name, value)
|
|
return headers
|
|
}
|
|
client.on('error', fail)
|
|
client.on('redirect', (status, _method, url, values) => {
|
|
try {
|
|
const headers = headersOf(values)
|
|
headers.set('Location', new URL(url).href)
|
|
resolve(new Response(null, { status, headers }))
|
|
cleanup()
|
|
client.abort()
|
|
} catch (error) {
|
|
fail(error)
|
|
}
|
|
})
|
|
client.on('response', (response) => {
|
|
try {
|
|
body = response as unknown as Readable
|
|
body.once('end', cleanup)
|
|
body.once('close', () => {
|
|
cleanup()
|
|
if (!body!.readableEnded) client.abort()
|
|
})
|
|
const empty = [204, 205, 304].includes(response.statusCode)
|
|
const stream = empty ? null : Readable.toWeb(body) as ReadableStream<Uint8Array>
|
|
resolve(new Response(stream, {
|
|
status: response.statusCode,
|
|
headers: headersOf(response.headers),
|
|
}))
|
|
if (empty) body.resume()
|
|
} catch (error) {
|
|
fail(error)
|
|
}
|
|
})
|
|
try {
|
|
for (const [name, value] of request.headers) client.setHeader(name, value)
|
|
request.signal.addEventListener('abort', abort, { once: true })
|
|
if (request.signal.aborted) abort()
|
|
else client.end()
|
|
} catch (error) {
|
|
fail(error)
|
|
}
|
|
})
|
|
}
|