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
Lines changed: 9 additions & 0 deletions
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

Lines changed: 3 additions & 2 deletions
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

Lines changed: 3 additions & 1 deletion
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

Lines changed: 88 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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
});
Lines changed: 27 additions & 0 deletions
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+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{@model}}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"response": "absolute-request"
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"response": "absolute-url"
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"response": "path-relative-request"
3+
}

0 commit comments

Comments
 (0)