Skip to content

Commit 840d95c

Browse files
matthargettfacebook-github-bot
authored andcommitted
Flesh out the URL polyfill a bit more (facebook#22901)
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: facebook#22901 Differential Revision: D13690954 Pulled By: cpojer fbshipit-source-id: 7966bc17be8af9bf656bffea5d530b1e626acfb3
1 parent 0a4f352 commit 840d95c

File tree

3 files changed

+189
-7
lines changed

3 files changed

+189
-7
lines changed

Libraries/Blob/URL.js

+152-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
* LICENSE file in the root directory of this source tree.
66
*
77
* @format
8-
* @flow strict-local
98
*/
109

1110
'use strict';
@@ -47,11 +46,71 @@ if (BlobModule && typeof BlobModule.BLOB_URI_SCHEME === 'string') {
4746
* </resources>
4847
* ```
4948
*/
50-
class URL {
51-
constructor() {
52-
throw new Error('Creating URL objects is not supported yet.');
49+
50+
// Small subset from whatwg-url: https://github.com/jsdom/whatwg-url/tree/master/lib
51+
// The reference code bloat comes from Unicode issues with URLs, so those won't work here.
52+
export class URLSearchParams {
53+
_searchParams = [];
54+
55+
constructor(params: any) {
56+
if (typeof params === 'object') {
57+
Object.keys(params).forEach(key => this.append(key, params[key]));
58+
}
59+
}
60+
61+
append(key: string, value: string) {
62+
this._searchParams.push([key, value]);
63+
}
64+
65+
delete(name) {
66+
throw new Error('not implemented');
67+
}
68+
69+
get(name) {
70+
throw new Error('not implemented');
71+
}
72+
73+
getAll(name) {
74+
throw new Error('not implemented');
75+
}
76+
77+
has(name) {
78+
throw new Error('not implemented');
79+
}
80+
81+
set(name, value) {
82+
throw new Error('not implemented');
5383
}
5484

85+
sort() {
86+
throw new Error('not implemented');
87+
}
88+
89+
[Symbol.iterator]() {
90+
return this._searchParams[Symbol.iterator]();
91+
}
92+
93+
toString() {
94+
if (this._searchParams.length === 0) {
95+
return '';
96+
}
97+
const last = this._searchParams.length - 1;
98+
return this._searchParams.reduce((acc, curr, index) => {
99+
return acc + curr.join('=') + (index === last ? '' : '&');
100+
}, '');
101+
}
102+
}
103+
104+
function validateBaseUrl(url: string) {
105+
// from this MIT-licensed gist: https://gist.github.com/dperini/729294
106+
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(
107+
url,
108+
);
109+
}
110+
111+
export class URL {
112+
_searchParamsInstance = null;
113+
55114
static createObjectURL(blob: Blob) {
56115
if (BLOB_URL_PREFIX === null) {
57116
throw new Error('Cannot create URL for blob!');
@@ -64,6 +123,93 @@ class URL {
64123
static revokeObjectURL(url: string) {
65124
// Do nothing.
66125
}
67-
}
68126

69-
module.exports = URL;
127+
constructor(url: string, base: string) {
128+
let baseUrl = null;
129+
if (base) {
130+
if (typeof base === 'string') {
131+
baseUrl = base;
132+
if (!validateBaseUrl(baseUrl)) {
133+
throw new TypeError(`Invalid base URL: ${baseUrl}`);
134+
}
135+
} else if (typeof base === 'object') {
136+
baseUrl = base.toString();
137+
}
138+
if (baseUrl.endsWith('/') && url.startsWith('/')) {
139+
baseUrl = baseUrl.slice(0, baseUrl.length - 1);
140+
}
141+
if (baseUrl.endsWith(url)) {
142+
url = '';
143+
}
144+
this._url = `${baseUrl}${url}`;
145+
} else {
146+
this._url = url;
147+
if (!this._url.endsWith('/')) {
148+
this._url += '/';
149+
}
150+
}
151+
}
152+
153+
get hash() {
154+
throw new Error('not implemented');
155+
}
156+
157+
get host() {
158+
throw new Error('not implemented');
159+
}
160+
161+
get hostname() {
162+
throw new Error('not implemented');
163+
}
164+
165+
get href(): string {
166+
return this.toString();
167+
}
168+
169+
get origin() {
170+
throw new Error('not implemented');
171+
}
172+
173+
get password() {
174+
throw new Error('not implemented');
175+
}
176+
177+
get pathname() {
178+
throw new Error('not implemented');
179+
}
180+
181+
get port() {
182+
throw new Error('not implemented');
183+
}
184+
185+
get protocol() {
186+
throw new Error('not implemented');
187+
}
188+
189+
get search() {
190+
throw new Error('not implemented');
191+
}
192+
193+
get searchParams(): URLSearchParams {
194+
if (this._searchParamsInstance == null) {
195+
this._searchParamsInstance = new URLSearchParams();
196+
}
197+
return this._searchParamsInstance;
198+
}
199+
200+
toJSON(): string {
201+
return this.toString();
202+
}
203+
204+
toString(): string {
205+
if (this._searchParamsInstance === null) {
206+
return this._url;
207+
}
208+
const separator = this._url.indexOf('?') > -1 ? '&' : '?';
209+
return this._url + separator + this._searchParamsInstance.toString();
210+
}
211+
212+
get username() {
213+
throw new Error('not implemented');
214+
}
215+
}

Libraries/Blob/__tests__/URL-test.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
* @emails oncall+react_native
9+
*/
10+
'use strict';
11+
12+
const URL = require('URL').URL;
13+
14+
describe('URL', function() {
15+
it('should pass Mozilla Dev Network examples', () => {
16+
const a = new URL('/', 'https://developer.mozilla.org');
17+
expect(a.href).toBe('https://developer.mozilla.org/');
18+
const b = new URL('https://developer.mozilla.org');
19+
expect(b.href).toBe('https://developer.mozilla.org/');
20+
const c = new URL('en-US/docs', b);
21+
expect(c.href).toBe('https://developer.mozilla.org/en-US/docs');
22+
const d = new URL('/en-US/docs', b);
23+
expect(d.href).toBe('https://developer.mozilla.org/en-US/docs');
24+
const f = new URL('/en-US/docs', d);
25+
expect(f.href).toBe('https://developer.mozilla.org/en-US/docs');
26+
// from original test suite, but requires complex implementation
27+
// const g = new URL(
28+
// '/en-US/docs',
29+
// 'https://developer.mozilla.org/fr-FR/toto',
30+
// );
31+
// expect(g.href).toBe('https://developer.mozilla.org/en-US/docs');
32+
const h = new URL('/en-US/docs', a);
33+
expect(h.href).toBe('https://developer.mozilla.org/en-US/docs');
34+
});
35+
});

Libraries/Core/setUpXHR.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ polyfillGlobal('WebSocket', () => require('WebSocket'));
2828
polyfillGlobal('Blob', () => require('Blob'));
2929
polyfillGlobal('File', () => require('File'));
3030
polyfillGlobal('FileReader', () => require('FileReader'));
31-
polyfillGlobal('URL', () => require('URL'));
31+
polyfillGlobal('URL', () => require('URL').URL); // flowlint-line untyped-import:off
32+
polyfillGlobal('URLSearchParams', () => require('URL').URLSearchParams); // flowlint-line untyped-import:off

0 commit comments

Comments
 (0)