From fe45dbd9906a491151538226ccaacb272507144b Mon Sep 17 00:00:00 2001 From: Jan Bobisud Date: Tue, 5 Jul 2022 12:54:00 +0200 Subject: [PATCH] Allow to use relative URLs for fetch --- .../fastboot/instance-initializers/fetch.js | 9 +++ packages/fastboot/src/sandbox.js | 79 ++++++++++++++++--- test-packages/basic-app/app/routes/fetch.js | 16 +++- .../basic-app/app/templates/fetch.hbs | 5 +- .../basic-app/public/absolute-request.json | 3 + .../basic-app/public/absolute-url.json | 3 + .../basic-app/public/relative-request.json | 3 + .../basic-app/public/relative-url.json | 3 + test-packages/basic-app/test/fetch-test.js | 5 +- 9 files changed, 112 insertions(+), 14 deletions(-) create mode 100644 packages/ember-cli-fastboot/fastboot/instance-initializers/fetch.js create mode 100644 test-packages/basic-app/public/absolute-request.json create mode 100644 test-packages/basic-app/public/absolute-url.json create mode 100644 test-packages/basic-app/public/relative-request.json create mode 100644 test-packages/basic-app/public/relative-url.json diff --git a/packages/ember-cli-fastboot/fastboot/instance-initializers/fetch.js b/packages/ember-cli-fastboot/fastboot/instance-initializers/fetch.js new file mode 100644 index 000000000..9fbcda4e5 --- /dev/null +++ b/packages/ember-cli-fastboot/fastboot/instance-initializers/fetch.js @@ -0,0 +1,9 @@ +function initialize(instance) { + let { request } = instance.lookup('service:fastboot'); + fetch.__fastbootRequest = request; +} + +export default { + name: 'fastboot:fetch', // `ember-fetch` addon registers as `fetch` + initialize, +}; diff --git a/packages/fastboot/src/sandbox.js b/packages/fastboot/src/sandbox.js index 75c413734..62f090918 100644 --- a/packages/fastboot/src/sandbox.js +++ b/packages/fastboot/src/sandbox.js @@ -4,6 +4,9 @@ const chalk = require('chalk'); const vm = require('vm'); const sourceMapSupport = require('source-map-support'); +const httpRegex = /^https?:\/\//; +const protocolRelativeRegex = /^\/\//; + module.exports = class Sandbox { constructor(globals) { this.globals = globals; @@ -56,27 +59,83 @@ module.exports = class Sandbox { } buildFetch() { + let globals; + if (globalThis.fetch) { - return { + globals = { fetch: globalThis.fetch, Request: globalThis.Request, Response: globalThis.Response, Headers: globalThis.Headers, AbortController: globalThis.AbortController, }; + } else { + let nodeFetch = require('node-fetch'); + let { + AbortController, + abortableFetch, + } = require('abortcontroller-polyfill/dist/cjs-ponyfill'); + let { fetch, Request } = abortableFetch({ + fetch: nodeFetch, + Request: nodeFetch.Request, + }); + + globals = { + fetch, + Request, + Response: nodeFetch.Response, + Headers: nodeFetch.Headers, + AbortController, + }; } - let nodeFetch = require('node-fetch'); - let { AbortController, abortableFetch } = require('abortcontroller-polyfill/dist/cjs-ponyfill'); - let { fetch, Request } = abortableFetch({ fetch: nodeFetch, Request: nodeFetch.Request }); + let originalFetch = globals.fetch; - return { - fetch, - Request, - Response: nodeFetch.Response, - Headers: nodeFetch.Headers, - AbortController, + globals.fetch = function __fastbootFetch(input, init) { + if (input && input.href) { + input.url = globals.fetch.__fastbootBuildAbsoluteURL(input.href); + } else if (typeof input === 'string') { + input = globals.fetch.__fastbootBuildAbsoluteURL(input); + } + return originalFetch(input, init); + }; + + globals.fetch.__fastbootBuildAbsoluteURL = function __fastbootBuildAbsoluteURL(url) { + if (protocolRelativeRegex.test(url)) { + let [host] = globals.fetch.__fastbootParseRequest(fetch.__fastbootRequest); + url = `${host}${url}`; + } else if (!httpRegex.test(url)) { + let [host, protocol] = globals.fetch.__fastbootParseRequest(fetch.__fastbootRequest); + url = `${protocol}//${host}${url}`; + } + return url; }; + + globals.fetch.__fastbootParseRequest = function __fastbootParseRequest(request) { + if (!request) { + throw new Error( + "Trying to fetch with relative url but ember-fetch hasn't finished loading FastBootInfo, see details at https://github.com/ember-cli/ember-fetch#relative-url" + ); + } + // Old Prember version is not sending protocol + const protocol = request.protocol === 'undefined:' ? 'http:' : request.protocol; + return [request.host, protocol]; + }; + + let OriginalRequest = globals.Request; + globals.Request = class __FastBootRequest extends OriginalRequest { + constructor(input, init) { + if (typeof input === 'string') { + input = globals.fetch.__fastbootBuildAbsoluteURL(input); + } else if (input && input.href) { + // WHATWG URL or Node.js Url Object + input = globals.fetch.__fastbootBuildAbsoluteURL(input.href); + } + super(input, init); + } + }; + + return globals; } runScript(script) { diff --git a/test-packages/basic-app/app/routes/fetch.js b/test-packages/basic-app/app/routes/fetch.js index 6c6c2bfb5..0470be362 100644 --- a/test-packages/basic-app/app/routes/fetch.js +++ b/test-packages/basic-app/app/routes/fetch.js @@ -1,5 +1,6 @@ import Route from '@ember/routing/route'; import { assert } from '@ember/debug'; +import { hash } from 'rsvp'; export default class FetchRoute extends Route { beforeModel() { @@ -11,7 +12,18 @@ export default class FetchRoute extends Route { } async model() { - let response = await fetch('https://api.github.com/users/tomster'); - return response.json(); + let [absoluteURL, absoluteRequest, relativeURL, relativeRequest] = await Promise.all([ + fetch('http://localhost:45678/absolute-url.json'), + fetch(new Request('http://localhost:45678/absolute-request.json')), + fetch('/assets/relative-url.json'), + fetch(new Request('/assets/relative-request.json')), + ]); + + return hash({ + absoluteURL: absoluteURL.json(), + absoluteRequest: absoluteRequest.json(), + relativeURL: relativeURL.json(), + relativeRequest: relativeRequest.json(), + }); } } diff --git a/test-packages/basic-app/app/templates/fetch.hbs b/test-packages/basic-app/app/templates/fetch.hbs index 465f670fb..9b6bef1bb 100644 --- a/test-packages/basic-app/app/templates/fetch.hbs +++ b/test-packages/basic-app/app/templates/fetch.hbs @@ -1 +1,4 @@ -{{@model.login}} \ No newline at end of file +{{@model.absoluteURL.login}} +{{@model.absoluteRequest.login}} +{{@model.relativeURL.login}} +{{@model.relativeRequest.login}} \ No newline at end of file diff --git a/test-packages/basic-app/public/absolute-request.json b/test-packages/basic-app/public/absolute-request.json new file mode 100644 index 000000000..358b38513 --- /dev/null +++ b/test-packages/basic-app/public/absolute-request.json @@ -0,0 +1,3 @@ +{ + "login": "absolute-request" +} \ No newline at end of file diff --git a/test-packages/basic-app/public/absolute-url.json b/test-packages/basic-app/public/absolute-url.json new file mode 100644 index 000000000..695526629 --- /dev/null +++ b/test-packages/basic-app/public/absolute-url.json @@ -0,0 +1,3 @@ +{ + "login": "absolute-url" +} \ No newline at end of file diff --git a/test-packages/basic-app/public/relative-request.json b/test-packages/basic-app/public/relative-request.json new file mode 100644 index 000000000..5e44c7649 --- /dev/null +++ b/test-packages/basic-app/public/relative-request.json @@ -0,0 +1,3 @@ +{ + "login": "relative-request" +} \ No newline at end of file diff --git a/test-packages/basic-app/public/relative-url.json b/test-packages/basic-app/public/relative-url.json new file mode 100644 index 000000000..26ab8be0f --- /dev/null +++ b/test-packages/basic-app/public/relative-url.json @@ -0,0 +1,3 @@ +{ + "login": "relative-url" +} \ No newline at end of file diff --git a/test-packages/basic-app/test/fetch-test.js b/test-packages/basic-app/test/fetch-test.js index b46029e40..f1f160da5 100644 --- a/test-packages/basic-app/test/fetch-test.js +++ b/test-packages/basic-app/test/fetch-test.js @@ -26,6 +26,9 @@ describe('fetch', function() { expect(response.statusCode).to.equal(200); expect(response.headers['content-type']).to.equalIgnoreCase('text/html; charset=utf-8'); - expect(response.body).to.contain('tomster'); + expect(response.body).to.contain('absolute-url'); + expect(response.body).to.contain('absolute-request'); + expect(response.body).to.contain('relative-url'); + expect(response.body).to.contain('relative-request'); }); });