Skip to content

Commit

Permalink
Flesh out the URL polyfill a bit more (#22901)
Browse files Browse the repository at this point in the history
Summary:
This expands functionality of URL minimally so Apollo Server can run in React Native contexts. Add explicit-fail getters so undefined values won't get generated from the otherwise missing implemenation.

Use of URL in apollo-server here: https://github.com/apollographql/apollo-server/blob/458bc71eadde52483ccaef209df3eb1f1bcb4424/packages/apollo-datasource-rest/src/RESTDataSource.ts#L79

Credit to my colleague dysonpro for debugging the issue and providing the initial working stub implementation.

Changelog:
----------

Help reviewers and the release process by writing your own changelog entry. See http://facebook.github.io/react-native/docs/contributing#changelog for an example.

[INTERNAL] [ENHANCEMENT] - Support construction, toString(), and href() of URL objects.
Pull Request resolved: #22901

Differential Revision: D13690954

Pulled By: cpojer

fbshipit-source-id: 7966bc17be8af9bf656bffea5d530b1e626acfb3
  • Loading branch information
matthargett authored and facebook-github-bot committed Jan 16, 2019
1 parent e3ff150 commit 6303850
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 7 deletions.
158 changes: 152 additions & 6 deletions Libraries/Blob/URL.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/

'use strict';
Expand Down Expand Up @@ -47,11 +46,71 @@ if (BlobModule && typeof BlobModule.BLOB_URI_SCHEME === 'string') {
* </resources>
* ```
*/
class URL {
constructor() {
throw new Error('Creating URL objects is not supported yet.');

// Small subset from whatwg-url: https://github.com/jsdom/whatwg-url/tree/master/lib
// The reference code bloat comes from Unicode issues with URLs, so those won't work here.
export class URLSearchParams {
_searchParams = [];

constructor(params: any) {
if (typeof params === 'object') {
Object.keys(params).forEach(key => this.append(key, params[key]));
}
}

append(key: string, value: string) {
this._searchParams.push([key, value]);
}

delete(name) {
throw new Error('not implemented');
}

get(name) {
throw new Error('not implemented');
}

getAll(name) {
throw new Error('not implemented');
}

has(name) {
throw new Error('not implemented');
}

set(name, value) {
throw new Error('not implemented');
}

sort() {
throw new Error('not implemented');
}

[Symbol.iterator]() {
return this._searchParams[Symbol.iterator]();
}

toString() {
if (this._searchParams.length === 0) {
return '';
}
const last = this._searchParams.length - 1;
return this._searchParams.reduce((acc, curr, index) => {
return acc + curr.join('=') + (index === last ? '' : '&');
}, '');
}
}

function validateBaseUrl(url: string) {
// from this MIT-licensed gist: https://gist.github.com/dperini/729294
return /^(?:(?:(?:https?|ftp):)?\/\/)(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(
url,
);
}

export class URL {
_searchParamsInstance = null;

static createObjectURL(blob: Blob) {
if (BLOB_URL_PREFIX === null) {
throw new Error('Cannot create URL for blob!');
Expand All @@ -64,6 +123,93 @@ class URL {
static revokeObjectURL(url: string) {
// Do nothing.
}
}

module.exports = URL;
constructor(url: string, base: string) {
let baseUrl = null;
if (base) {
if (typeof base === 'string') {
baseUrl = base;
if (!validateBaseUrl(baseUrl)) {
throw new TypeError(`Invalid base URL: ${baseUrl}`);
}
} else if (typeof base === 'object') {
baseUrl = base.toString();
}
if (baseUrl.endsWith('/') && url.startsWith('/')) {
baseUrl = baseUrl.slice(0, baseUrl.length - 1);
}
if (baseUrl.endsWith(url)) {
url = '';
}
this._url = `${baseUrl}${url}`;
} else {
this._url = url;
if (!this._url.endsWith('/')) {
this._url += '/';
}
}
}

get hash() {
throw new Error('not implemented');
}

get host() {
throw new Error('not implemented');
}

get hostname() {
throw new Error('not implemented');
}

get href(): string {
return this.toString();
}

get origin() {
throw new Error('not implemented');
}

get password() {
throw new Error('not implemented');
}

get pathname() {
throw new Error('not implemented');
}

get port() {
throw new Error('not implemented');
}

get protocol() {
throw new Error('not implemented');
}

get search() {
throw new Error('not implemented');
}

get searchParams(): URLSearchParams {
if (this._searchParamsInstance == null) {
this._searchParamsInstance = new URLSearchParams();
}
return this._searchParamsInstance;
}

toJSON(): string {
return this.toString();
}

toString(): string {
if (this._searchParamsInstance === null) {
return this._url;
}
const separator = this._url.indexOf('?') > -1 ? '&' : '?';
return this._url + separator + this._searchParamsInstance.toString();
}

get username() {
throw new Error('not implemented');
}
}
35 changes: 35 additions & 0 deletions Libraries/Blob/__tests__/URL-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @emails oncall+react_native
*/
'use strict';

const URL = require('URL').URL;

describe('URL', function() {
it('should pass Mozilla Dev Network examples', () => {
const a = new URL('/', 'https://developer.mozilla.org');
expect(a.href).toBe('https://developer.mozilla.org/');
const b = new URL('https://developer.mozilla.org');
expect(b.href).toBe('https://developer.mozilla.org/');
const c = new URL('en-US/docs', b);
expect(c.href).toBe('https://developer.mozilla.org/en-US/docs');
const d = new URL('/en-US/docs', b);
expect(d.href).toBe('https://developer.mozilla.org/en-US/docs');
const f = new URL('/en-US/docs', d);
expect(f.href).toBe('https://developer.mozilla.org/en-US/docs');
// from original test suite, but requires complex implementation
// const g = new URL(
// '/en-US/docs',
// 'https://developer.mozilla.org/fr-FR/toto',
// );
// expect(g.href).toBe('https://developer.mozilla.org/en-US/docs');
const h = new URL('/en-US/docs', a);
expect(h.href).toBe('https://developer.mozilla.org/en-US/docs');
});
});
3 changes: 2 additions & 1 deletion Libraries/Core/setUpXHR.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ polyfillGlobal('WebSocket', () => require('WebSocket'));
polyfillGlobal('Blob', () => require('Blob'));
polyfillGlobal('File', () => require('File'));
polyfillGlobal('FileReader', () => require('FileReader'));
polyfillGlobal('URL', () => require('URL'));
polyfillGlobal('URL', () => require('URL').URL); // flowlint-line untyped-import:off
polyfillGlobal('URLSearchParams', () => require('URL').URLSearchParams); // flowlint-line untyped-import:off

0 comments on commit 6303850

Please sign in to comment.