@expo/metro-runtime
Version:
Tools for making advanced Metro bundler features work
211 lines (201 loc) • 5.75 kB
text/typescript
// Copyright © 2023 650 Industries.
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
class DOMException extends Error {
constructor(message: string, name: string) {
super(message);
this.name = name;
}
}
// The differences between the definitions of `Location` and `WorkerLocation`
// are because of the `LegacyUnforgeable` attribute only specified upon
// `Location`'s properties. See:
// - https://html.spec.whatwg.org/multipage/history.html#the-location-interface
// - https://heycam.github.io/webidl/#LegacyUnforgeable
class Location {
constructor(href: string | null = null) {
const url = new URL(
// @ts-expect-error
href
);
try {
url.username = '';
} catch {
throw new Error(
'Attempting to use the window.location polyfill before the URL built-in has been polyfilled.'
);
}
url.password = '';
Object.defineProperties(this, {
hash: {
get() {
return url.hash;
},
set() {
throw new DOMException(`Cannot set "location.hash".`, 'NotSupportedError');
},
enumerable: true,
},
host: {
get() {
return url.host;
},
set() {
throw new DOMException(`Cannot set "location.host".`, 'NotSupportedError');
},
enumerable: true,
},
hostname: {
get() {
return url.hostname;
},
set() {
throw new DOMException(`Cannot set "location.hostname".`, 'NotSupportedError');
},
enumerable: true,
},
href: {
get() {
return url.href;
},
set() {
throw new DOMException(`Cannot set "location.href".`, 'NotSupportedError');
},
enumerable: true,
},
origin: {
get() {
return url.origin;
},
enumerable: true,
},
pathname: {
get() {
return url.pathname;
},
set() {
throw new DOMException(`Cannot set "location.pathname".`, 'NotSupportedError');
},
enumerable: true,
},
port: {
get() {
return url.port;
},
set() {
throw new DOMException(`Cannot set "location.port".`, 'NotSupportedError');
},
enumerable: true,
},
protocol: {
get() {
return url.protocol;
},
set() {
throw new DOMException(`Cannot set "location.protocol".`, 'NotSupportedError');
},
enumerable: true,
},
search: {
get() {
return url.search;
},
set() {
throw new DOMException(`Cannot set "location.search".`, 'NotSupportedError');
},
enumerable: true,
},
ancestorOrigins: {
get() {
return {
length: 0,
item: () => null,
contains: () => false,
};
},
enumerable: true,
},
assign: {
value: function assign() {
throw new DOMException(`Cannot call "location.assign()".`, 'NotSupportedError');
},
enumerable: true,
},
reload: {
value: function reload() {
if (process.env.NODE_ENV !== 'production') {
// NOTE: This does change how native fast refresh works. The upstream metro-runtime will check
// if `location.reload` exists before falling back on an implementation that is nearly identical to
// this. The main difference is that on iOS there is a "reason" message sent, but at the time of writing
// this, that message is unused (ref: `RCTTriggerReloadCommandNotification`).
const DevSettings = (require('react-native') as typeof import('react-native'))
.DevSettings;
return DevSettings.reload();
} else if (
typeof globalThis.expo !== 'undefined' &&
'reloadAppAsync' in globalThis.expo
) {
// Expo SDK 51 and above.
globalThis.expo.reloadAppAsync('');
} else {
throw new DOMException(`Cannot call "location.reload()".`, 'NotSupportedError');
}
},
enumerable: true,
},
replace: {
value: function replace() {
throw new DOMException(`Cannot call "location.replace()".`, 'NotSupportedError');
},
enumerable: true,
},
toString: {
value: function toString() {
return url.href;
},
enumerable: true,
},
[Symbol.for('Expo.privateCustomInspect')]: {
value(inspect: any) {
const object = {
hash: this.hash,
host: this.host,
hostname: this.hostname,
href: this.href,
origin: this.origin,
pathname: this.pathname,
port: this.port,
protocol: this.protocol,
search: this.search,
};
return `${this.constructor.name} ${inspect(object)}`;
},
},
});
}
}
Object.defineProperties(Location.prototype, {
[Symbol.toString()]: {
value: 'Location',
configurable: true,
},
});
let location: Location | undefined = undefined;
export function setLocationHref(href: string) {
location = new Location(href);
}
export function install() {
Object.defineProperty(global, 'Location', {
value: Location,
configurable: true,
writable: true,
});
Object.defineProperty(window, 'location', {
get() {
return location;
},
set() {
throw new DOMException(`Cannot set "location".`, 'NotSupportedError');
},
enumerable: true,
});
}