Skip to content

Commit 24aba7b

Browse files
committed
Add fetch API globals
1 parent 703e788 commit 24aba7b

File tree

15 files changed

+220
-3
lines changed

15 files changed

+220
-3
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+
};

packages/ember-cli-fastboot/test/request-details-test.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22

3+
const path = require('path');
34
const chai = require('chai');
45
const expect = chai.expect;
56
const RSVP = require('rsvp');
@@ -11,8 +12,8 @@ function injectMiddlewareAddon(app) {
1112
pkg.devDependencies['body-parser'] =
1213
process.env.npm_package_devDependencies_body_parser;
1314
pkg.dependencies = pkg.dependencies || {};
14-
pkg.dependencies['fastboot-express-middleware'] =
15-
process.env.npm_package_dependencies_fastboot_express_middleware;
15+
pkg.dependencies['fastboot'] = `file:${path.resolve(__dirname, '../../fastboot')}`
16+
pkg.dependencies['fastboot-express-middleware'] = `file:${path.resolve(__dirname, '../../fastboot-express-middleware')}`
1617
pkg['ember-addon'] = {
1718
paths: ['lib/post-middleware'],
1819
};

packages/fastboot/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@
2828
"postversion": "git push origin master --tags"
2929
},
3030
"dependencies": {
31+
"abortcontroller-polyfill": "^1.7.3",
3132
"chalk": "^4.1.2",
3233
"cookie": "^0.4.1",
3334
"debug": "^4.3.3",
3435
"jsdom": "^19.0.0",
36+
"node-fetch": "^2.6.7",
3537
"resolve": "^1.22.0",
3638
"simple-dom": "^1.4.0",
3739
"source-map-support": "^0.5.21"
@@ -79,4 +81,4 @@
7981
"tokenRef": "GITHUB_AUTH"
8082
}
8183
}
82-
}
84+
}

packages/fastboot/src/sandbox.js

+88
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;
@@ -14,6 +17,7 @@ module.exports = class Sandbox {
1417

1518
buildSandbox() {
1619
let console = this.buildWrappedConsole();
20+
let fetch = this.buildFetch();
1721
let URL = require('url');
1822
let globals = this.globals;
1923

@@ -28,6 +32,7 @@ module.exports = class Sandbox {
2832
// Convince jQuery not to assume it's in a browser
2933
module: { exports: {} },
3034
},
35+
fetch,
3136
globals
3237
);
3338

@@ -53,6 +58,89 @@ module.exports = class Sandbox {
5358
return wrappedConsole;
5459
}
5560

61+
buildFetch() {
62+
let globals;
63+
64+
if (globalThis.fetch) {
65+
globals = {
66+
fetch: globalThis.fetch,
67+
Request: globalThis.Request,
68+
Response: globalThis.Response,
69+
Headers: globalThis.Headers,
70+
AbortController: globalThis.AbortController,
71+
};
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+
};
90+
}
91+
92+
let originalFetch = globals.fetch;
93+
globals.fetch = function __fastbootFetch(input, init) {
94+
input = globals.fetch.__fastbootBuildAbsoluteURL(input);
95+
return originalFetch(input, init);
96+
};
97+
98+
globals.fetch.__fastbootBuildAbsoluteURL = function __fastbootBuildAbsoluteURL(input) {
99+
if (input && input.href) {
100+
// WHATWG URL or Node.js Url Object
101+
input = input.href;
102+
}
103+
104+
if (typeof input !== 'string') {
105+
return input;
106+
}
107+
108+
if (protocolRelativeRegex.test(input)) {
109+
let request = globals.fetch.__fastbootRequest;
110+
let [protocol] = globals.fetch.__fastbootParseRequest(input, request);
111+
input = `${protocol}//${input}`;
112+
} else if (!httpRegex.test(input)) {
113+
let request = globals.fetch.__fastbootRequest;
114+
let [protocol, host] = globals.fetch.__fastbootParseRequest(input, request);
115+
input = `${protocol}//${host}${input}`;
116+
}
117+
118+
return input;
119+
};
120+
121+
globals.fetch.__fastbootParseRequest = function __fastbootParseRequest(url, request) {
122+
if (!request) {
123+
throw new Error(
124+
`Using fetch with relative URL ${url}, but application instance has not been initialized yet.`
125+
);
126+
}
127+
128+
// Old Prember version is not sending protocol
129+
const protocol = request.protocol === 'undefined:' ? 'http:' : request.protocol;
130+
return [protocol, request.host];
131+
};
132+
133+
let OriginalRequest = globals.Request;
134+
globals.Request = class __FastBootRequest extends OriginalRequest {
135+
constructor(input, init) {
136+
input = globals.fetch.__fastbootBuildAbsoluteURL(input);
137+
super(input, init);
138+
}
139+
};
140+
141+
return globals;
142+
}
143+
56144
runScript(script) {
57145
script.runInContext(this.context);
58146
}

test-packages/basic-app/app/router.js

+1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ Router.map(function() {
1616
this.route('echo-request-headers');
1717
this.route('return-status-code-418');
1818
this.route('metadata');
19+
this.route('fetch');
1920
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Route from '@ember/routing/route';
2+
import { assert } from '@ember/debug';
3+
4+
export default class FetchRoute extends Route {
5+
beforeModel() {
6+
assert('fetch is available', fetch);
7+
assert('Request is available', Request);
8+
assert('Response is available', Response);
9+
assert('Headers is available', Headers);
10+
assert('AbortController is available', AbortController);
11+
}
12+
13+
async model() {
14+
let responses = await Promise.all([
15+
fetch('http://localhost:45678/absolute-url.json'),
16+
fetch(new Request('http://localhost:45678/absolute-request.json')),
17+
fetch('//localhost:45678/protocol-relative-url.json'),
18+
fetch(new Request('//localhost:45678/protocol-relative-request.json')),
19+
fetch('/path-relative-url.json'),
20+
fetch(new Request('/path-relative-request.json')),
21+
]);
22+
23+
responses = await Promise.all(responses.map((response) => response.json()));
24+
25+
return responses.map((response) => response.response).join('|');
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{@model}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"response": "absolute-request"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"response": "absolute-url"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"response": "path-relative-request"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"response": "path-relative-url"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"response": "protocol-relative-request"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"response": "protocol-relative-url"
3+
}
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use strict';
2+
3+
const RSVP = require('rsvp');
4+
const request = RSVP.denodeify(require('request'));
5+
const expect = require('chai').use(require('chai-string')).expect;
6+
const { startServer, stopServer } = require('../../test-libs');
7+
8+
describe('fetch', function () {
9+
this.timeout(120000);
10+
11+
before(function () {
12+
return startServer();
13+
});
14+
15+
after(function () {
16+
return stopServer();
17+
});
18+
19+
it('uses fetch', async () => {
20+
const response = await request({
21+
url: 'http://localhost:45678/fetch',
22+
headers: {
23+
Accept: 'text/html',
24+
},
25+
});
26+
27+
expect(response.statusCode).to.equal(200);
28+
expect(response.headers['content-type']).to.equalIgnoreCase('text/html; charset=utf-8');
29+
expect(response.body).to.contain(
30+
[
31+
'absolute-url',
32+
'absolute-request',
33+
'protocol-relative-url',
34+
'protocol-relative-request',
35+
'path-relative-url',
36+
'path-relative-request',
37+
].join('|')
38+
);
39+
});
40+
});

yarn.lock

+30
Original file line numberDiff line numberDiff line change
@@ -3621,6 +3621,11 @@ abortcontroller-polyfill@^1.4.0:
36213621
resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.4.0.tgz#0d5eb58e522a461774af8086414f68e1dda7a6c4"
36223622
integrity sha512-3ZFfCRfDzx3GFjO6RAkYx81lPGpUS20ISxux9gLxuKnqafNcFQo59+IoZqpO2WvQlyc287B62HDnDdNYRmlvWA==
36233623

3624+
abortcontroller-polyfill@^1.7.3:
3625+
version "1.7.3"
3626+
resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz#1b5b487bd6436b5b764fd52a612509702c3144b5"
3627+
integrity sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q==
3628+
36243629
accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
36253630
version "1.3.7"
36263631
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
@@ -13595,6 +13600,13 @@ node-fetch@^2.3.0, node-fetch@^2.6.0:
1359513600
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
1359613601
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
1359713602

13603+
node-fetch@^2.6.7:
13604+
version "2.6.7"
13605+
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
13606+
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
13607+
dependencies:
13608+
whatwg-url "^5.0.0"
13609+
1359813610
node-int64@^0.4.0:
1359913611
version "0.4.0"
1360013612
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@@ -17074,6 +17086,11 @@ tr46@^3.0.0:
1707417086
dependencies:
1707517087
punycode "^2.1.1"
1707617088

17089+
tr46@~0.0.3:
17090+
version "0.0.3"
17091+
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
17092+
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
17093+
1707717094
tree-sync@^1.2.2:
1707817095
version "1.4.0"
1707917096
resolved "https://registry.yarnpkg.com/tree-sync/-/tree-sync-1.4.0.tgz#314598d13abaf752547d9335b8f95d9a137100d6"
@@ -17653,6 +17670,11 @@ wcwidth@^1.0.1:
1765317670
dependencies:
1765417671
defaults "^1.0.3"
1765517672

17673+
webidl-conversions@^3.0.0:
17674+
version "3.0.1"
17675+
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
17676+
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
17677+
1765617678
webidl-conversions@^4.0.2:
1765717679
version "4.0.2"
1765817680
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
@@ -17826,6 +17848,14 @@ whatwg-url@^10.0.0:
1782617848
tr46 "^3.0.0"
1782717849
webidl-conversions "^7.0.0"
1782817850

17851+
whatwg-url@^5.0.0:
17852+
version "5.0.0"
17853+
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
17854+
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
17855+
dependencies:
17856+
tr46 "~0.0.3"
17857+
webidl-conversions "^3.0.0"
17858+
1782917859
whatwg-url@^7.0.0:
1783017860
version "7.1.0"
1783117861
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"

0 commit comments

Comments
 (0)