267 lines
9.0 KiB
Swift
267 lines
9.0 KiB
Swift
// Copyright 2015-present 650 Industries. All rights reserved.
|
|
|
|
// swiftlint:disable line_length
|
|
|
|
/**
|
|
* This file contains the browser scripts that are injected into the WebView.
|
|
* @generated by buildBrowserScripts.ts
|
|
*/
|
|
|
|
internal let INSTALL_GLOBALS_SCRIPT: String = """
|
|
// browserScripts/InstallGlobals/Deferred.ts
|
|
class Deferred {
|
|
promise;
|
|
resolveCallback;
|
|
rejectCallback;
|
|
constructor() {
|
|
this.promise = new Promise((resolve, reject) => {
|
|
this.resolveCallback = resolve;
|
|
this.rejectCallback = reject;
|
|
});
|
|
}
|
|
resolve(value) {
|
|
this.resolveCallback(value);
|
|
}
|
|
reject(reason) {
|
|
this.rejectCallback(reason);
|
|
}
|
|
getPromise() {
|
|
return this.promise;
|
|
}
|
|
}
|
|
|
|
// browserScripts/InstallGlobals/EventEmitterProxy.ts
|
|
class EventEmitterProxy {
|
|
moduleName;
|
|
listeners;
|
|
constructor(moduleName) {
|
|
this.moduleName = moduleName;
|
|
}
|
|
addListener = (eventName, listener) => {
|
|
if (!this.listeners) {
|
|
this.listeners = new Map;
|
|
}
|
|
if (!this.listeners.has(eventName)) {
|
|
this.listeners.set(eventName, new Set);
|
|
}
|
|
this.listeners.get(eventName)?.add(listener);
|
|
const nativeListenerId = window.ExpoDomWebView.nextEventListenerId++;
|
|
listener.$$nativeListenerId = nativeListenerId;
|
|
const source = `
|
|
globalThis.expo.$$DomWebViewEventListenerMap ||= {};
|
|
globalThis.expo.$$DomWebViewEventListenerMap['${eventName}'] ||= new Map();
|
|
const listener = (...args) => {
|
|
const serializeArgs = args.map((arg) => JSON.stringify(arg)).join(',');
|
|
const script = 'window.ExpoDomWebView.eventEmitterProxy.${this.moduleName}.emit("${eventName}", ' + serializeArgs + ')';
|
|
globalThis.expo.modules.ExpoDomWebViewModule.evalJsForWebViewAsync("%%WEBVIEW_ID%%", script);
|
|
};
|
|
globalThis.expo.$$DomWebViewEventListenerMap['${eventName}'].set(${nativeListenerId}, listener);
|
|
globalThis.expo.modules.${this.moduleName}.addListener('${eventName}', listener);
|
|
`;
|
|
window.ExpoDomWebView.eval(source);
|
|
return {
|
|
remove: () => {
|
|
this.removeListener(eventName, listener);
|
|
}
|
|
};
|
|
};
|
|
removeListener = (eventName, listener) => {
|
|
const nativeListenerId = listener.$$nativeListenerId;
|
|
if (nativeListenerId != null) {
|
|
const source = `(function() {
|
|
const nativeListener = globalThis.expo.$$DomWebViewEventListenerMap['${eventName}'].get(${nativeListenerId});
|
|
if (nativeListener != null) {
|
|
globalThis.expo.modules.${this.moduleName}.removeListener('${eventName}', nativeListener);
|
|
globalThis.expo.$$DomWebViewEventListenerMap['${eventName}'].delete(${nativeListenerId});
|
|
}
|
|
})();
|
|
true;
|
|
`;
|
|
window.ExpoDomWebView.eval(source);
|
|
}
|
|
this.listeners?.get(eventName)?.delete(listener);
|
|
};
|
|
removeAllListeners = (eventName) => {
|
|
const source = `
|
|
globalThis.expo.$$DomWebViewEventListenerMap['${eventName}'].clear();
|
|
globalThis.expo.modules.${this.moduleName}.removeAllListeners('${eventName}');
|
|
`;
|
|
window.ExpoDomWebView.eval(source);
|
|
this.listeners?.get(eventName)?.clear();
|
|
};
|
|
emit = (eventName, ...args) => {
|
|
const listeners = new Set(this.listeners?.get(eventName));
|
|
listeners.forEach((listener) => {
|
|
try {
|
|
listener(...args);
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
// browserScripts/InstallGlobals/utils.ts
|
|
function serializeArgs(args) {
|
|
return args.map((arg) => {
|
|
if (typeof arg === "object" && arg.sharedObjectId != null) {
|
|
return `globalThis.expo.sharedObjectRegistry.get(${arg.sharedObjectId})`;
|
|
}
|
|
return JSON.stringify(arg);
|
|
}).join(",");
|
|
}
|
|
|
|
// browserScripts/InstallGlobals/proxies.ts
|
|
function createSharedObjectProxy(sharedObjectId) {
|
|
return new Proxy({}, {
|
|
get: (target, prop) => {
|
|
const name = String(prop);
|
|
if (name === "sharedObjectId") {
|
|
return sharedObjectId;
|
|
}
|
|
return function(...args) {
|
|
const serializedArgs = serializeArgs(args);
|
|
const source = `globalThis.expo.sharedObjectRegistry.get(${sharedObjectId})?.${name}?.call(globalThis.expo.sharedObjectRegistry.get(${sharedObjectId}),${serializedArgs})`;
|
|
return window.ExpoDomWebView.eval(source);
|
|
};
|
|
}
|
|
});
|
|
}
|
|
function createConstructorProxy(moduleName, property, propertyName) {
|
|
return new Proxy(function() {
|
|
}, {
|
|
construct(target, args) {
|
|
const serializedArgs = serializeArgs(args);
|
|
const sharedObjectId = window.ExpoDomWebView.nextSharedObjectId++;
|
|
const sharedObjectProxy = createSharedObjectProxy(sharedObjectId);
|
|
window.ExpoDomWebView.sharedObjectFinalizationRegistry.register(sharedObjectProxy, sharedObjectId);
|
|
const source = `globalThis.expo.sharedObjectRegistry ||= new Map(); globalThis.expo.sharedObjectRegistry.set(${sharedObjectId}, new ${property}(${serializedArgs}));`;
|
|
window.ExpoDomWebView.eval(source);
|
|
return sharedObjectProxy;
|
|
}
|
|
});
|
|
}
|
|
function createPropertyProxy(propertyTypeCache, moduleName, propertyName) {
|
|
const property = `globalThis.expo.modules.${moduleName}.${propertyName}`;
|
|
let propertyType = propertyTypeCache[propertyName];
|
|
if (!propertyType) {
|
|
const typeCheck = `${property}?.prototype?.__expo_shared_object_id__ != null ? 'sharedObject' : typeof ${property}`;
|
|
propertyType = window.ExpoDomWebView.eval(typeCheck);
|
|
propertyTypeCache[propertyName] = propertyType;
|
|
}
|
|
if (propertyType === "sharedObject") {
|
|
return createConstructorProxy(moduleName, property, propertyName);
|
|
}
|
|
if (propertyType === "function") {
|
|
return function(...args) {
|
|
const serializedArgs = serializeArgs(args);
|
|
const source = `${property}(${serializedArgs})`;
|
|
return window.ExpoDomWebView.eval(source);
|
|
};
|
|
}
|
|
return window.ExpoDomWebView.eval(property);
|
|
}
|
|
function createExpoModuleProxy(moduleName) {
|
|
const propertyTypeCache = {};
|
|
return new Proxy({}, {
|
|
get: (target, prop) => {
|
|
const name = String(prop);
|
|
if (["addListener", "removeListener", "removeAllListeners"].includes(name)) {
|
|
return window.ExpoDomWebView.eventEmitterProxy[moduleName][name];
|
|
}
|
|
return createPropertyProxy(propertyTypeCache, moduleName, name);
|
|
}
|
|
});
|
|
}
|
|
|
|
// browserScripts/InstallGlobals/ExpoDomWebView.ts
|
|
class ExpoDomWebView {
|
|
nextDeferredId;
|
|
nextSharedObjectId;
|
|
nextEventListenerId;
|
|
deferredMap;
|
|
sharedObjectFinalizationRegistry;
|
|
expoModulesProxy;
|
|
eventEmitterProxy;
|
|
constructor() {
|
|
this.nextDeferredId = 0;
|
|
this.nextSharedObjectId = 0;
|
|
this.nextEventListenerId = 0;
|
|
this.deferredMap = new Map;
|
|
this.sharedObjectFinalizationRegistry = new FinalizationRegistry((sharedObjectId) => {
|
|
this.eval(`globalThis.expo.sharedObjectRegistry.delete(${sharedObjectId})`);
|
|
});
|
|
const expoModules = {};
|
|
const eventEmitterProxy = {};
|
|
this.eval("Object.keys(globalThis.expo.modules)").forEach((name) => {
|
|
expoModules[name] = createExpoModuleProxy(name);
|
|
eventEmitterProxy[name] = new EventEmitterProxy(name);
|
|
});
|
|
this.expoModulesProxy = expoModules;
|
|
this.eventEmitterProxy = eventEmitterProxy;
|
|
}
|
|
eval(source) {
|
|
const { deferredId, deferred } = this.createDeferred();
|
|
const args = JSON.stringify({ source, deferredId });
|
|
const result = JSON.parse(window.ExpoDomWebViewBridge.eval(args));
|
|
if (result.isPromise) {
|
|
return deferred.getPromise();
|
|
}
|
|
this.removeDeferred(deferredId);
|
|
return result.value;
|
|
}
|
|
createDeferred() {
|
|
const deferred = new Deferred;
|
|
const deferredId = this.nextDeferredId;
|
|
this.deferredMap.set(deferredId, deferred);
|
|
this.nextDeferredId += 1;
|
|
return { deferredId, deferred };
|
|
}
|
|
resolveDeferred(deferredId, value) {
|
|
const deferred = this.deferredMap.get(deferredId);
|
|
if (deferred) {
|
|
deferred.resolve(value);
|
|
this.deferredMap.delete(deferredId);
|
|
}
|
|
}
|
|
rejectDeferred(deferredId, reason) {
|
|
const deferred = this.deferredMap.get(deferredId);
|
|
if (deferred) {
|
|
deferred.reject(reason);
|
|
this.deferredMap.delete(deferredId);
|
|
}
|
|
}
|
|
removeDeferred(deferredId) {
|
|
this.deferredMap.delete(deferredId);
|
|
}
|
|
}
|
|
|
|
// browserScripts/InstallGlobals/index.ts
|
|
window.ExpoDomWebView = new ExpoDomWebView;
|
|
|
|
"""
|
|
|
|
internal let NATIVE_EVAL_WRAPPER_SCRIPT: String = """
|
|
// browserScripts/NativeEvalWrapper/index.ts
|
|
(function() {
|
|
const result = "%%SOURCE%%";
|
|
if (result instanceof Promise) {
|
|
result.then((resolved) => {
|
|
const resolvedString = JSON.stringify(resolved);
|
|
const script = 'window.ExpoDomWebView.resolveDeferred("%%DEFERRED_ID%%", ' + resolvedString + ")";
|
|
globalThis.expo.modules.ExpoDomWebViewModule.evalJsForWebViewAsync("%%WEBVIEW_ID%%", script);
|
|
}).catch((error) => {
|
|
const errorString = JSON.stringify(error);
|
|
const script = 'window.ExpoDomWebView.rejectDeferred("%%DEFERRED_ID%%", ' + errorString + ")";
|
|
globalThis.expo.modules.ExpoDomWebViewModule.evalJsForWebViewAsync("%%WEBVIEW_ID%%", script);
|
|
});
|
|
return JSON.stringify({ isPromise: true, value: null });
|
|
} else {
|
|
return JSON.stringify({ isPromise: false, value: result });
|
|
}
|
|
})();
|
|
|
|
"""
|
|
|
|
// swiftlint:enable line_length
|