From fd6ed8722e5954d5240acdfbfcb3a3780de078cc Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 28 Sep 2022 23:05:09 -0600 Subject: [PATCH] feat(browser): make script-tag friendly --- urequest.js | 394 ++++++++++++++++++++++++++-------------------------- 1 file changed, 200 insertions(+), 194 deletions(-) diff --git a/urequest.js b/urequest.js index ec48572..99f18fc 100644 --- a/urequest.js +++ b/urequest.js @@ -1,223 +1,229 @@ -'use strict'; +(function (exports) { + 'use strict'; -/** - * @typedef {import('./').Request} Request - * @typedef {import('./').RequestOptions} RequestOptions - * @typedef {import('./').Response} Response - * @typedef {import('./').Headers} Headers - */ - -// `fetch` will be available for node and browsers as a global -//var fetch = window.fetch; - -// https://developer.mozilla.org/en-US/docs/Web/API/fetch -// https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch -let _fetchDefaults = { - method: 'GET', // *GET, POST, PATCH, PUT, DELETE, etc - headers: {}, - body: undefined, // String, ArrayBuffer, FormData, etc - mode: 'cors', // no-cors, *cors, same-origin - credentials: 'same-origin', // omit, *same-origin, include - cache: 'default', // *default, no-store, reload, no-cache, force-cache, only-if-cached - redirect: 'follow', // *follow, error, manual, - referrer: undefined, - referrerPolicy: 'no-referrer-when-downgrade', // no-referrer, *no-referrer-when-downgrade, same-origin, origin, strict-origin, origin-when-cross-origin, strict-origin-when-cross-origin, unsafe-url - integrity: '', - keepalive: false, - signal: null // -}; - -let _optionKeys = Object.keys(_fetchDefaults).concat([ - //'encoding', // N/A - //'stream', // TODO via getReader - //'json' // handled manually - //'form', // TODO - //'auth' // handled manually - //'formData', // TODO - //'FormData', // TODO - //'userAgent' // not allowed, non-standard for request.js -]); - -/** - * @returns {Request>} - */ -function setDefaults(_defs) { /** - * @param {RequestOptions} opts - * @returns {Promise} - **/ - async function request(opts) { - if ('string' === typeof opts) { - opts = { url: opts }; - } - let reqOpts = { headers: {} }; + * @typedef {import('./').Request} Request + * @typedef {import('./').RequestOptions} RequestOptions + * @typedef {import('./').Response} Response + * @typedef {import('./').Headers} Headers + */ - if ( - opts.body || - (opts.json && true !== opts.json) || - opts.form || - opts.formData - ) { - // TODO this is probably a deviation from request's API - // need to check and probably eliminate it - reqOpts.method = (reqOpts.method || 'POST').toUpperCase(); - } else { - reqOpts.method = (reqOpts.method || 'GET').toUpperCase(); - } + // `fetch` will be available for node and browsers as a global + //var fetch = window.fetch; - _optionKeys.forEach(function (key) { - if (key in opts) { - if ('undefined' !== typeof opts[key]) { - reqOpts[key] = opts[key]; + // https://developer.mozilla.org/en-US/docs/Web/API/fetch + // https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch + let _fetchDefaults = { + method: 'GET', // *GET, POST, PATCH, PUT, DELETE, etc + headers: {}, + body: undefined, // String, ArrayBuffer, FormData, etc + mode: 'cors', // no-cors, *cors, same-origin + credentials: 'same-origin', // omit, *same-origin, include + cache: 'default', // *default, no-store, reload, no-cache, force-cache, only-if-cached + redirect: 'follow', // *follow, error, manual, + referrer: undefined, + referrerPolicy: 'no-referrer-when-downgrade', // no-referrer, *no-referrer-when-downgrade, same-origin, origin, strict-origin, origin-when-cross-origin, strict-origin-when-cross-origin, unsafe-url + integrity: '', + keepalive: false, + signal: null // + }; + + let _optionKeys = Object.keys(_fetchDefaults).concat([ + //'encoding', // N/A + //'stream', // TODO via getReader + //'json' // handled manually + //'form', // TODO + //'auth' // handled manually + //'formData', // TODO + //'FormData', // TODO + //'userAgent' // not allowed, non-standard for request.js + ]); + + /** + * @returns {Request>} + */ + function setDefaults(_defs) { + /** + * @param {RequestOptions} opts + * @returns {Promise} + **/ + async function request(opts) { + if ('string' === typeof opts) { + opts = { url: opts }; + } + let reqOpts = { headers: {} }; + + if ( + opts.body || + (opts.json && true !== opts.json) || + opts.form || + opts.formData + ) { + // TODO this is probably a deviation from request's API + // need to check and probably eliminate it + reqOpts.method = (reqOpts.method || 'POST').toUpperCase(); + } else { + reqOpts.method = (reqOpts.method || 'GET').toUpperCase(); + } + + _optionKeys.forEach(function (key) { + if (key in opts) { + if ('undefined' !== typeof opts[key]) { + reqOpts[key] = opts[key]; + } + } else if (key in _defs) { + reqOpts[key] = _defs[key]; } - } else if (key in _defs) { - reqOpts[key] = _defs[key]; - } - }); + }); - if (opts.auth) { - // if opts.uri specifies auth it will be parsed by url.parse and passed directly to the http module - if ('string' !== typeof opts.auth) { - let u = opts.auth.user || opts.auth.username || ''; - let p = opts.auth.pass || opts.auth.password || ''; - reqOpts.headers.Authorization = encodeBasicAuth(`${u}:${p}`); - } else if ('string' === typeof opts.auth) { - reqOpts.headers.Authorization = encodeBasicAuth(`${opts.auth}`); + if (opts.auth) { + // if opts.uri specifies auth it will be parsed by url.parse and passed directly to the http module + if ('string' !== typeof opts.auth) { + let u = opts.auth.user || opts.auth.username || ''; + let p = opts.auth.pass || opts.auth.password || ''; + reqOpts.headers.Authorization = encodeBasicAuth( + `${u}:${p}` + ); + } else if ('string' === typeof opts.auth) { + reqOpts.headers.Authorization = encodeBasicAuth( + `${opts.auth}` + ); + } + + // [request-compat] + if (opts.auth.bearer) { + // having a shortcut for base64 encoding makes sense, + // but this? Eh, whatevs... + reqOpts.header.Authorization = `Bearer ${opts.auth.bearer}`; + } } - // [request-compat] - if (opts.auth.bearer) { - // having a shortcut for base64 encoding makes sense, - // but this? Eh, whatevs... - reqOpts.header.Authorization = `Bearer ${opts.auth.bearer}`; + let body; + if (opts.json && true !== opts.json) { + if (!opts.headers['content-type']) { + opts.headers['content-type'] = 'application/json'; + } + body = JSON.stringify(opts.json); + if (!opts.method) { + opts.method = 'POST'; + } } - } - let body; - if (opts.json && true !== opts.json) { - if (!opts.headers['content-type']) { - opts.headers['content-type'] = 'application/json'; + // The node version will send HTTP Auth by default, but not Cookies. + // We don't have an equivalent option for `fetch`. Furthermore, + // `fetch` caches HTTP Auth Basic across browser refreshes, + // which is not analogous to the node behavior. + // + // "In the face of ambiguity, refuse the temptation to guess" + // + //if (!('credentials' in opts)) { + // opts.credentials = 'include'; + //} + + if (!('mode' in opts)) { + reqOpts.mode = 'cors'; } - body = JSON.stringify(opts.json); - if (!opts.method) { - opts.method = 'POST'; + if (!('body' in opts)) { + if (body) { + reqOpts.body = body; + } } - } - // The node version will send HTTP Auth by default, but not Cookies. - // We don't have an equivalent option for `fetch`. Furthermore, - // `fetch` caches HTTP Auth Basic across browser refreshes, - // which is not analogous to the node behavior. - // - // "In the face of ambiguity, refuse the temptation to guess" - // - //if (!('credentials' in opts)) { - // opts.credentials = 'include'; - //} + let resp = await fetch(opts.url, reqOpts); - if (!('mode' in opts)) { - reqOpts.mode = 'cors'; - } - if (!('body' in opts)) { - if (body) { - reqOpts.body = body; - } - } - - let resp = await fetch(opts.url, reqOpts); - - let result = { - ok: resp.ok, - headers: headersToObj(resp.headers), - body: undefined, - // swapped to match request.js - statusCode: resp.status, - status: resp.statusText, - request: reqOpts, - response: resp - }; - result.toJSON = function () { - return { - ok: result.ok, - headers: result.headers, - body: result.body, - statusCode: result.statusCode, - status: result.status + let result = { + ok: resp.ok, + headers: headersToObj(resp.headers), + body: undefined, + // swapped to match request.js + statusCode: resp.status, + status: resp.statusText, + request: reqOpts, + response: resp + }; + result.toJSON = function () { + return { + ok: result.ok, + headers: result.headers, + body: result.body, + statusCode: result.statusCode, + status: result.status + }; }; - }; - // return early if there's no body - if (!result.headers['content-type']) { + // return early if there's no body + if (!result.headers['content-type']) { + return result; + } + + // TODO blob, formData ? + if (null === opts.encoding) { + return await resp.arrayBuffer(); + } + + if (!opts.json) { + result.body = await resp.text(); + } else { + result.body = await resp.json().catch(async function () { + return await resp.text(); + }); + } return result; } - // TODO blob, formData ? - if (null === opts.encoding) { - return await resp.arrayBuffer(); - } - - if (!opts.json) { - result.body = await resp.text(); - } else { - result.body = await resp.json().catch(async function () { - return await resp.text(); - }); - } - return result; + return request; } - return request; -} - -/** - * @param {Iterable.<*>} rheaders - * @returns {Object.} - */ -function headersToObj(rheaders) { - /* + /** + * @param {Iterable.<*>} rheaders + * @returns {Object.} + */ + function headersToObj(rheaders) { + /* Array.from(resp.headers.entries()).forEach(function (h) { headers[h[0]] = h[1]; }); */ - let headerNames = Array.from(rheaders.keys()); - let resHeaders = {}; - headerNames.forEach(function (k) { - resHeaders[k] = rheaders.get(k); - }); - return resHeaders; -} + let headerNames = Array.from(rheaders.keys()); + let resHeaders = {}; + headerNames.forEach(function (k) { + resHeaders[k] = rheaders.get(k); + }); + return resHeaders; + } -/** - * @param {String} utf8 - * @returns {String} - */ -function encodeBasicAuth(utf8) { - let b64 = unicodeToBase64(utf8); - return `Basic ${b64}`; -} + /** + * @param {String} utf8 + * @returns {String} + */ + function encodeBasicAuth(utf8) { + let b64 = unicodeToBase64(utf8); + return `Basic ${b64}`; + } -/** - * @param {String} utf8 - * @returns {String} - */ -function unicodeToBase64(utf8) { - let str = ''; - let uint8 = new TextEncoder().encode(utf8); - uint8.forEach(function (b) { - str += String.fromCharCode(b); - }); - let b64 = btoa(str); - return b64; -} + /** + * @param {String} utf8 + * @returns {String} + */ + function unicodeToBase64(utf8) { + let str = ''; + let uint8 = new TextEncoder().encode(utf8); + uint8.forEach(function (b) { + str += String.fromCharCode(b); + }); + let b64 = btoa(str); + return b64; + } -let defaultRequest = setDefaults({ mode: 'cors' }); -//@ts-ignore -exports.urequest = defaultRequest; -//@ts-ignore -exports.urequest.defaults = setDefaults; + let defaultRequest = setDefaults({ mode: 'cors' }); + //@ts-ignore + exports.urequest = defaultRequest; + //@ts-ignore + exports.urequest.defaults = setDefaults; -// for backwards compat -if ('undefined' !== typeof module) { - module.exports = defaultRequest; - module.exports.defaults = setDefaults; -} + // for backwards compat + if ('undefined' !== typeof module) { + module.exports = defaultRequest; + module.exports.defaults = setDefaults; + } +})(('undefined' !== typeof module && module.exports) || window);