const exec = require('child_process').execFileSync const path = require('path') const { URL } = require('url') const _fetch = require('node-fetch') const shared = require('./shared') function fetch (resource, init) { const request = [] if (resource instanceof fetch.Request) { request.push(...shared.serializeRequest(resource)) } else if (resource instanceof URL) { request.push(resource.href, {}) } else { request.push(resource, {}) } Object.assign(request[1], init) request[1].headers = new _fetch.Headers(request[1].headers) if (request[1].body) { const contentType = extractContentType(request) if (contentType && !request[1].headers.get('content-type')) { request[1].headers.append('content-type', contentType) } request[1].body = shared.parseBody(init.body).toString('base64') } request[1].headers = shared.serializeHeaders(request[1].headers) // TODO credentials const response = JSON.parse(sendMessage(request)) if ('headers' in response[1]) { return shared.deserializeResponse(fetch, ...response) } else { throw shared.deserializeError(fetch, ...response) } } function sendMessage (message) { return exec(process.execPath, [path.join(__dirname, 'worker.js')], { windowsHide: true, maxBuffer: Infinity, input: JSON.stringify(message), shell: false }).toString() } function extractContentType (input) { const request = new _fetch.Request(...input) return request.headers.get('content-type') || undefined } const _body = Symbol('bodyBuffer') const _bodyError = Symbol('bodyError') class SyncRequest extends _fetch.Request { constructor (resource, init = {}) { const buffer = shared.parseBody(init.body) super(resource, init) defineBuffer(this, buffer) } clone () { checkBody(this) return new SyncRequest(...shared.serializeRequest(this)) } } class SyncResponse extends _fetch.Response { constructor (body, init, options = {}) { const { buffer = shared.parseBody(body), bodyError } = options super(body, init) defineBuffer(this, buffer) if (bodyError) defineBodyError(this, bodyError) } clone () { checkBody(this) const buffer = Buffer.from(this[_body]) return new SyncResponse( shared.createStream(buffer), shared.serializeResponse(this), { buffer, bodyError: this[_bodyError] } ) } } class Body { static mixin (proto) { for (const name of Object.getOwnPropertyNames(Body.prototype)) { if (name === 'constructor') { continue } const desc = Object.getOwnPropertyDescriptor(Body.prototype, name) Object.defineProperty(proto, name, { ...desc, enumerable: true }) } } arrayBuffer () { checkBody(this) const buf = consumeBody(this) return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) } text () { checkBody(this) return consumeBody(this).toString() } json () { checkBody(this) try { return JSON.parse(consumeBody(this).toString()) } catch (err) { throw new fetch.FetchError(`invalid json response body at ${this.url} reason: ${err.message}`, 'invalid-json') } } buffer () { checkBody(this) return Buffer.from(consumeBody(this)) } textConverted () { throw new fetch.FetchError('textConverted not implemented') } } function _super (self, method) { return Object.getPrototypeOf(Object.getPrototypeOf(self))[method].bind(self) } function checkBody (body) { if (body[_bodyError]) { throw body[_bodyError] } if (body.bodyUsed) { throw new TypeError(`body used already for: ${body.url}`) } } function consumeBody (body) { _super(body, 'buffer')().catch(error => console.error(error)) return body[_body] || Buffer.alloc(0) } function defineBuffer (body, buffer) { Object.defineProperty(body, _body, { value: buffer, enumerable: false }) } function defineBodyError (body, error) { Object.defineProperty(body, _bodyError, { value: shared.deserializeError(fetch, ...error), enumerable: false }) } Body.mixin(SyncRequest.prototype) Body.mixin(SyncResponse.prototype) Object.defineProperties(SyncRequest.prototype, { clone: { enumerable: true } }) Object.defineProperties(SyncResponse.prototype, { clone: { enumerable: true } }) fetch.Headers = _fetch.Headers fetch.FetchError = _fetch.FetchError fetch.Request = SyncRequest fetch.Response = SyncResponse module.exports = fetch