Skip to content

Commit 30d6ac7

Browse files
committed
Allow to use relative URLs for fetch
1 parent 9cb1268 commit 30d6ac7

File tree

9 files changed

+112
-14
lines changed

9 files changed

+112
-14
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
function initialize(instance) {
2+
let { request } = instance.lookup('service:fastboot');
3+
fetch.__fastbootRequest = request;
4+
}
5+
6+
export default {
7+
name: 'fastboot:fetch', // `ember-fetch` addon registers as `fetch`
8+
initialize,
9+
};

Diff for: packages/fastboot/src/sandbox.js

+69-10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ const chalk = require('chalk');
44
const vm = require('vm');
55
const sourceMapSupport = require('source-map-support');
66

7+
const httpRegex = /^https?:\/\//;
8+
const protocolRelativeRegex = /^\/\//;
9+
710
module.exports = class Sandbox {
811
constructor(globals) {
912
this.globals = globals;
@@ -56,27 +59,83 @@ module.exports = class Sandbox {
5659
}
5760

5861
buildFetch() {
62+
let globals;
63+
5964
if (globalThis.fetch) {
60-
return {
65+
globals = {
6166
fetch: globalThis.fetch,
6267
Request: globalThis.Request,
6368
Response: globalThis.Response,
6469
Headers: globalThis.Headers,
6570
AbortController: globalThis.AbortController,
6671
};
72+
} else {
73+
let nodeFetch = require('node-fetch');
74+
let {
75+
AbortController,
76+
abortableFetch,
77+
} = require('abortcontroller-polyfill/dist/cjs-ponyfill');
78+
let { fetch, Request } = abortableFetch({
79+
fetch: nodeFetch,
80+
Request: nodeFetch.Request,
81+
});
82+
83+
globals = {
84+
fetch,
85+
Request,
86+
Response: nodeFetch.Response,
87+
Headers: nodeFetch.Headers,
88+
AbortController,
89+
};
6790
}
6891

69-
let nodeFetch = require('node-fetch');
70-
let { AbortController, abortableFetch } = require('abortcontroller-polyfill/dist/cjs-ponyfill');
71-
let { fetch, Request } = abortableFetch({ fetch: nodeFetch, Request: nodeFetch.Request });
92+
let originalFetch = globals.fetch;
7293

73-
return {
74-
fetch,
75-
Request,
76-
Response: nodeFetch.Response,
77-
Headers: nodeFetch.Headers,
78-
AbortController,
94+
globals.fetch = function __fastbootFetch(input, init) {
95+
if (input && input.href) {
96+
input.url = globals.fetch.__fastbootBuildAbsoluteURL(input.href);
97+
} else if (typeof input === 'string') {
98+
input = globals.fetch.__fastbootBuildAbsoluteURL(input);
99+
}
100+
return originalFetch(input, init);
101+
};
102+
103+
globals.fetch.__fastbootBuildAbsoluteURL = function __fastbootBuildAbsoluteURL(url) {
104+
if (protocolRelativeRegex.test(url)) {
105+
let [host] = globals.fetch.__fastbootParseRequest(fetch.__fastbootRequest);
106+
url = `${host}${url}`;
107+
} else if (!httpRegex.test(url)) {
108+
let [host, protocol] = globals.fetch.__fastbootParseRequest(fetch.__fastbootRequest);
109+
url = `${protocol}//${host}${url}`;
110+
}
111+
return url;
79112
};
113+
114+
globals.fetch.__fastbootParseRequest = function __fastbootParseRequest(request) {
115+
if (!request) {
116+
throw new Error(
117+
"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"
118+
);
119+
}
120+
// Old Prember version is not sending protocol
121+
const protocol = request.protocol === 'undefined:' ? 'http:' : request.protocol;
122+
return [request.host, protocol];
123+
};
124+
125+
let OriginalRequest = globals.Request;
126+
globals.Request = class __FastBootRequest extends OriginalRequest {
127+
constructor(input, init) {
128+
if (typeof input === 'string') {
129+
input = globals.fetch.__fastbootBuildAbsoluteURL(input);
130+
} else if (input && input.href) {
131+
// WHATWG URL or Node.js Url Object
132+
input = globals.fetch.__fastbootBuildAbsoluteURL(input.href);
133+
}
134+
super(input, init);
135+
}
136+
};
137+
138+
return globals;
80139
}
81140

82141
runScript(script) {

Diff for: test-packages/basic-app/app/routes/fetch.js

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Route from '@ember/routing/route';
22
import { assert } from '@ember/debug';
3+
import { hash } from 'rsvp';
34

45
export default class FetchRoute extends Route {
56
beforeModel() {
@@ -11,7 +12,18 @@ export default class FetchRoute extends Route {
1112
}
1213

1314
async model() {
14-
let response = await fetch('https://api.github.com/users/tomster');
15-
return response.json();
15+
let [absoluteURL, absoluteRequest, relativeURL, relativeRequest] = await Promise.all([
16+
fetch('http://localhost:45678/absolute-url.json'),
17+
fetch('http://localhost:45678/absolute-request.json'),
18+
fetch('/assets/relative-url.json'),
19+
fetch(new Request('/assets/relative-request.json')),
20+
]);
21+
22+
return hash({
23+
absoluteURL: absoluteURL.json(),
24+
absoluteRequest: absoluteRequest.json(),
25+
relativeURL: relativeURL.json(),
26+
relativeRequest: relativeRequest.json(),
27+
});
1628
}
1729
}

Diff for: test-packages/basic-app/app/templates/fetch.hbs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
{{@model.login}}
1+
{{@model.absoluteURL.login}}
2+
{{@model.absoluteRequest.login}}
3+
{{@model.relativeURL.login}}
4+
{{@model.relativeRequest.login}}

Diff for: test-packages/basic-app/public/absolute-request.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"login": "absolute-request"
3+
}

Diff for: test-packages/basic-app/public/absolute-url.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"login": "absolute-url"
3+
}

Diff for: test-packages/basic-app/public/relative-request.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"login": "relative-request"
3+
}

Diff for: test-packages/basic-app/public/relative-url.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"login": "relative-url"
3+
}

Diff for: test-packages/basic-app/test/fetch-test.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ describe('fetch', function() {
2626

2727
expect(response.statusCode).to.equal(200);
2828
expect(response.headers['content-type']).to.equalIgnoreCase('text/html; charset=utf-8');
29-
expect(response.body).to.contain('tomster');
29+
expect(response.body).to.contain('absolute-url');
30+
expect(response.body).to.contain('absolute-request');
31+
expect(response.body).to.contain('relative-url');
32+
expect(response.body).to.contain('relative-request');
3033
});
3134
});

0 commit comments

Comments
 (0)