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..e462a0693 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,86 @@ 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; + globals.fetch = function __fastbootFetch(input, init) { + input = globals.fetch.__fastbootBuildAbsoluteURL(input); + return originalFetch(input, init); + }; - return { - fetch, - Request, - Response: nodeFetch.Response, - Headers: nodeFetch.Headers, - AbortController, + globals.fetch.__fastbootBuildAbsoluteURL = function __fastbootBuildAbsoluteURL(input) { + if (input && input.href) { + // WHATWG URL or Node.js Url Object + input = input.href; + } + + if (typeof input !== 'string') { + return input; + } + + if (protocolRelativeRegex.test(input)) { + let request = globals.fetch.__fastbootRequest; + let [protocol] = globals.fetch.__fastbootParseRequest(input, request); + input = `${protocol}//${input}`; + } else if (!httpRegex.test(input)) { + let request = globals.fetch.__fastbootRequest; + let [protocol, host] = globals.fetch.__fastbootParseRequest(input, request); + input = `${protocol}//${host}${input}`; + } + + return input; }; + + globals.fetch.__fastbootParseRequest = function __fastbootParseRequest(url, request) { + if (!request) { + throw new Error( + `Using fetch with relative URL ${url}, but application instance has not been initialized yet.` + ); + } + + // Old Prember version is not sending protocol + const protocol = request.protocol === 'undefined:' ? 'http:' : request.protocol; + return [protocol, request.host]; + }; + + let OriginalRequest = globals.Request; + globals.Request = class __FastBootRequest extends OriginalRequest { + constructor(input, init) { + input = globals.fetch.__fastbootBuildAbsoluteURL(input); + 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..475238b15 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,29 @@ export default class FetchRoute extends Route { } async model() { - let response = await fetch('https://api.github.com/users/tomster'); - return response.json(); + let [ + absoluteURL, + absoluteRequest, + protocolRelativeURL, + protocolRelativeRequest, + pathRelativeURL, + pathRelativeRequest, + ] = await Promise.all([ + fetch('http://localhost:45678/absolute-url.json'), + fetch(new Request('http://localhost:45678/absolute-request.json')), + fetch('//localhost:45678/protocol-relative-url.json'), + fetch(new Request('//localhost:45678/protocol-relative-request.json')), + fetch('/path-relative-url.json'), + fetch(new Request('/path-relative-request.json')), + ]); + + return hash({ + absoluteURL: absoluteURL.json(), + absoluteRequest: absoluteRequest.json(), + protocolRelativeURL: protocolRelativeURL.json(), + protocolRelativeRequest: protocolRelativeRequest.json(), + pathRelativeURL: pathRelativeURL.json(), + pathRelativeRequest: pathRelativeRequest.json(), + }); } } diff --git a/test-packages/basic-app/app/templates/fetch.hbs b/test-packages/basic-app/app/templates/fetch.hbs index 465f670fb..be5717f19 100644 --- a/test-packages/basic-app/app/templates/fetch.hbs +++ b/test-packages/basic-app/app/templates/fetch.hbs @@ -1 +1,6 @@ -{{@model.login}} \ No newline at end of file +{{@model.absoluteURL.response}} +{{@model.absoluteRequest.response}} +{{@model.protocolRelativeURL.response}} +{{@model.protocolRelativeRequest.response}} +{{@model.pathRelativeURL.response}} +{{@model.pathRelativeRequest.response}} \ 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..9e22df1aa --- /dev/null +++ b/test-packages/basic-app/public/absolute-request.json @@ -0,0 +1,3 @@ +{ + "response": "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..5be641f21 --- /dev/null +++ b/test-packages/basic-app/public/absolute-url.json @@ -0,0 +1,3 @@ +{ + "response": "absolute-url" +} \ No newline at end of file diff --git a/test-packages/basic-app/public/path-relative-request.json b/test-packages/basic-app/public/path-relative-request.json new file mode 100644 index 000000000..56537c3f6 --- /dev/null +++ b/test-packages/basic-app/public/path-relative-request.json @@ -0,0 +1,3 @@ +{ + "response": "path-relative-request" +} \ No newline at end of file diff --git a/test-packages/basic-app/public/path-relative-url.json b/test-packages/basic-app/public/path-relative-url.json new file mode 100644 index 000000000..bd94d3de6 --- /dev/null +++ b/test-packages/basic-app/public/path-relative-url.json @@ -0,0 +1,3 @@ +{ + "response": "path-relative-url" +} \ No newline at end of file diff --git a/test-packages/basic-app/public/protocol-relative-request.json b/test-packages/basic-app/public/protocol-relative-request.json new file mode 100644 index 000000000..aeb56f25d --- /dev/null +++ b/test-packages/basic-app/public/protocol-relative-request.json @@ -0,0 +1,3 @@ +{ + "response": "protocol-relative-request" +} \ No newline at end of file diff --git a/test-packages/basic-app/public/protocol-relative-url.json b/test-packages/basic-app/public/protocol-relative-url.json new file mode 100644 index 000000000..ad4fd3664 --- /dev/null +++ b/test-packages/basic-app/public/protocol-relative-url.json @@ -0,0 +1,3 @@ +{ + "response": "protocol-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..4a68026fa 100644 --- a/test-packages/basic-app/test/fetch-test.js +++ b/test-packages/basic-app/test/fetch-test.js @@ -26,6 +26,11 @@ 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('protocol-relative-url'); + expect(response.body).to.contain('protocol-relative-request'); + expect(response.body).to.contain('path-relative-url'); + expect(response.body).to.contain('path-relative-request'); }); });