Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fetch API globals #898

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
function initialize(instance) {
let { request } = instance.lookup('service:fastboot');
fetch.__fastbootRequest = request;
}

export default {
initialize,
};
11 changes: 9 additions & 2 deletions packages/ember-cli-fastboot/test/request-details-test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

const path = require('path');
const chai = require('chai');
const expect = chai.expect;
const RSVP = require('rsvp');
Expand All @@ -11,8 +12,14 @@ function injectMiddlewareAddon(app) {
pkg.devDependencies['body-parser'] =
process.env.npm_package_devDependencies_body_parser;
pkg.dependencies = pkg.dependencies || {};
pkg.dependencies['fastboot-express-middleware'] =
process.env.npm_package_dependencies_fastboot_express_middleware;
pkg.dependencies['fastboot'] = `file:${path.resolve(
__dirname,
'../../fastboot'
)}`;
pkg.dependencies['fastboot-express-middleware'] = `file:${path.resolve(
__dirname,
'../../fastboot-express-middleware'
)}`;
pkg['ember-addon'] = {
paths: ['lib/post-middleware'],
};
Expand Down
2 changes: 2 additions & 0 deletions packages/fastboot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@
"postversion": "git push origin master --tags"
},
"dependencies": {
"abortcontroller-polyfill": "^1.7.3",
"chalk": "^4.1.2",
"cookie": "^0.4.1",
"debug": "^4.3.3",
"jsdom": "^19.0.0",
"node-fetch": "^2.6.7",
"resolve": "^1.22.0",
"simple-dom": "^1.4.0",
"source-map-support": "^0.5.21"
Expand Down
88 changes: 88 additions & 0 deletions packages/fastboot/src/sandbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -14,6 +17,7 @@ module.exports = class Sandbox {

buildSandbox() {
let console = this.buildWrappedConsole();
let fetch = this.buildFetch();
let URL = require('url');
let globals = this.globals;

Expand All @@ -28,6 +32,7 @@ module.exports = class Sandbox {
// Convince jQuery not to assume it's in a browser
module: { exports: {} },
},
fetch,
globals
);

Expand All @@ -53,6 +58,89 @@ module.exports = class Sandbox {
return wrappedConsole;
}

buildFetch() {
let globals;

if (globalThis.fetch) {
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 originalFetch = globals.fetch;
globals.fetch = function __fastbootFetch(input, init) {
input = globals.fetch.__fastbootBuildAbsoluteURL(input);
return originalFetch(input, init);
};

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
Copy link
Contributor Author

@bobisjan bobisjan Jul 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gilest, should be old versions of prember still supported? Based on the changelog the 1.0.2+ send protocol correctly https://github.com/ef4/prember/blob/master/CHANGELOG.md#102---2019-01-06

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Is that patch incompatible with the changes you are making?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, this workaround should still work for prember < 1.0.2, hence it can stay here if it make sense 🙌

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would guess most prember users are on the latest version but if the workaround can stay that is ideal

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) {
script.runInContext(this.context);
}
Expand Down
1 change: 1 addition & 0 deletions test-packages/basic-app/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ Router.map(function() {
this.route('echo-request-headers');
this.route('return-status-code-418');
this.route('metadata');
this.route('fetch');
});
27 changes: 27 additions & 0 deletions test-packages/basic-app/app/routes/fetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Route from '@ember/routing/route';
import { assert } from '@ember/debug';

export default class FetchRoute extends Route {
beforeModel() {
assert('fetch is available', fetch);
assert('Request is available', Request);
assert('Response is available', Response);
assert('Headers is available', Headers);
assert('AbortController is available', AbortController);
}

async model() {
let responses = 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')),
]);

responses = await Promise.all(responses.map((response) => response.json()));

return responses.map((response) => response.response).join('|');
}
}
1 change: 1 addition & 0 deletions test-packages/basic-app/app/templates/fetch.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{@model}}
1 change: 0 additions & 1 deletion test-packages/basic-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
"ember-cli-uglify": "^3.0.0",
"ember-data": "~3.19.0",
"ember-export-application-global": "^2.0.1",
"ember-fetch": "^8.0.1",
"ember-load-initializers": "^2.1.1",
"ember-maybe-import-regenerator": "^0.1.6",
"ember-qunit": "^4.6.0",
Expand Down
3 changes: 3 additions & 0 deletions test-packages/basic-app/public/absolute-request.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"response": "absolute-request"
}
3 changes: 3 additions & 0 deletions test-packages/basic-app/public/absolute-url.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"response": "absolute-url"
}
3 changes: 3 additions & 0 deletions test-packages/basic-app/public/path-relative-request.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"response": "path-relative-request"
}
3 changes: 3 additions & 0 deletions test-packages/basic-app/public/path-relative-url.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"response": "path-relative-url"
}
3 changes: 3 additions & 0 deletions test-packages/basic-app/public/protocol-relative-request.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"response": "protocol-relative-request"
}
3 changes: 3 additions & 0 deletions test-packages/basic-app/public/protocol-relative-url.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"response": "protocol-relative-url"
}
40 changes: 40 additions & 0 deletions test-packages/basic-app/test/fetch-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict';

const RSVP = require('rsvp');
const request = RSVP.denodeify(require('request'));
const expect = require('chai').use(require('chai-string')).expect;
const { startServer, stopServer } = require('../../test-libs');

describe('fetch', function () {
this.timeout(120000);

before(function () {
return startServer();
});

after(function () {
return stopServer();
});

it('uses fetch', async () => {
const response = await request({
url: 'http://localhost:45678/fetch',
headers: {
Accept: 'text/html',
},
});

expect(response.statusCode).to.equal(200);
expect(response.headers['content-type']).to.equalIgnoreCase('text/html; charset=utf-8');
expect(response.body).to.contain(
[
'absolute-url',
'absolute-request',
'protocol-relative-url',
'protocol-relative-request',
'path-relative-url',
'path-relative-request',
].join('|')
);
});
});