187 lines
4.1 KiB
JavaScript
187 lines
4.1 KiB
JavaScript
/* eslint-env browser */
|
|
|
|
const { Buffer } = require('buffer/')
|
|
|
|
function syncFetch (...args) {
|
|
const [url, opts] = parseArgs(...args)
|
|
|
|
const xhr = new XMLHttpRequest()
|
|
xhr.withCredentials = opts.credentials === 'include'
|
|
xhr.timeout = opts.timeout
|
|
|
|
// Request
|
|
xhr.open(opts.method || 'GET', url, false)
|
|
|
|
try {
|
|
xhr.responseType = 'arraybuffer'
|
|
} catch (e) {
|
|
// not in Worker scope
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType#Synchronous_XHR_restrictions
|
|
}
|
|
|
|
for (const header of opts.headers) {
|
|
xhr.setRequestHeader(...header)
|
|
}
|
|
|
|
xhr.send(opts.body || null)
|
|
|
|
// Response
|
|
let headers = xhr.getAllResponseHeaders()
|
|
headers = headers && headers.split('\r\n').filter(Boolean).map(header => header.split(': ', 2))
|
|
|
|
return new syncFetch.Response(xhr.response, {
|
|
url: xhr.responseURL,
|
|
status: xhr.status,
|
|
statusText: xhr.statusText,
|
|
headers,
|
|
redirected: xhr.responseURL !== url
|
|
})
|
|
}
|
|
|
|
function parseArgs (resource, init) {
|
|
const request = []
|
|
|
|
if (resource instanceof syncFetch.Request) {
|
|
request.push(resource.url)
|
|
request.push({
|
|
method: resource.method,
|
|
headers: resource.headers,
|
|
body: resource.body
|
|
})
|
|
} else {
|
|
request.push(resource, {})
|
|
}
|
|
|
|
Object.assign(request[1], init)
|
|
|
|
request[1].headers = new syncFetch.Headers(request[1].headers || {})
|
|
|
|
return request
|
|
}
|
|
|
|
const INTERNALS = Symbol('SyncFetch Internals')
|
|
|
|
class SyncRequest extends Request {
|
|
constructor (resource, init = {}, body = init.body) {
|
|
super(resource, init)
|
|
this[INTERNALS] = {
|
|
body: body ? Buffer.from(body) : null
|
|
}
|
|
}
|
|
|
|
clone () {
|
|
checkBody(this)
|
|
return new SyncRequest(this.url, this)
|
|
}
|
|
}
|
|
|
|
class SyncResponse extends Response {
|
|
constructor (body, init = {}) {
|
|
body = body ? Buffer.from(body) : null
|
|
super(createStream(body), init)
|
|
this[INTERNALS] = {
|
|
url: init.url,
|
|
redirected: init.redirected,
|
|
body
|
|
}
|
|
}
|
|
|
|
get url () {
|
|
return this[INTERNALS].url
|
|
}
|
|
|
|
get redirected () {
|
|
return this[INTERNALS].redirected
|
|
}
|
|
|
|
clone () {
|
|
checkBody(this)
|
|
return new SyncResponse(this[INTERNALS].body, {
|
|
url: this.url,
|
|
headers: this.headers,
|
|
status: this.status,
|
|
statusText: this.statusText,
|
|
redirected: this.redirected
|
|
})
|
|
}
|
|
}
|
|
|
|
class Body {
|
|
constructor (body) {
|
|
this[INTERNALS] = {
|
|
body: Buffer.from(body)
|
|
}
|
|
}
|
|
|
|
static mixin (prototype) {
|
|
for (const name of Object.getOwnPropertyNames(Body.prototype)) {
|
|
if (name === 'constructor') { continue }
|
|
const desc = Object.getOwnPropertyDescriptor(Body.prototype, name)
|
|
Object.defineProperty(prototype, name, { ...desc, enumerable: true })
|
|
}
|
|
}
|
|
|
|
arrayBuffer () {
|
|
checkBody(this)
|
|
const buffer = consumeBody(this)
|
|
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)
|
|
}
|
|
|
|
blob () {
|
|
checkBody(this)
|
|
const type = this.headers && this.headers.get('content-type')
|
|
return new Blob([consumeBody(this)], type && { type })
|
|
}
|
|
|
|
text () {
|
|
checkBody(this)
|
|
return consumeBody(this).toString()
|
|
}
|
|
|
|
json () {
|
|
checkBody(this)
|
|
try {
|
|
return JSON.parse(consumeBody(this).toString())
|
|
} catch (err) {
|
|
throw new TypeError(`invalid json response body at ${this.url} reason: ${err.message}`, 'invalid-json')
|
|
}
|
|
}
|
|
|
|
buffer () {
|
|
checkBody(this)
|
|
return consumeBody(this).clone()
|
|
}
|
|
}
|
|
|
|
function checkBody (body) {
|
|
if (body.bodyUsed) {
|
|
throw new TypeError(`body used already for: ${body.url}`)
|
|
}
|
|
}
|
|
|
|
function consumeBody (body) {
|
|
_super(body, 'arrayBuffer')()
|
|
return body[INTERNALS].body || Buffer.alloc(0)
|
|
}
|
|
|
|
function _super (self, method) {
|
|
return Object.getPrototypeOf(Object.getPrototypeOf(self))[method].bind(self)
|
|
}
|
|
|
|
function createStream (body) {
|
|
return new ReadableStream({
|
|
start (controller) {
|
|
controller.enqueue(body)
|
|
controller.close()
|
|
}
|
|
})
|
|
}
|
|
|
|
Body.mixin(SyncRequest.prototype)
|
|
Body.mixin(SyncResponse.prototype)
|
|
|
|
syncFetch.Headers = self.Headers
|
|
syncFetch.Request = SyncRequest
|
|
syncFetch.Response = SyncResponse
|
|
module.exports = syncFetch
|