|
| 1 | +import base64 from "./base64"; |
| 2 | +import channel from "./channel"; |
| 3 | + |
| 4 | +// Only third-party plugins consume this facade. App code uses Bridge/imports. |
| 5 | +export default function installPluginCompatibility(host = window) { |
| 6 | + if (host.cordova) return; |
| 7 | + const bridge = host.Bridge; |
| 8 | + const modules = new Map(); |
| 9 | + const legacyChannel = { |
| 10 | + ...channel, |
| 11 | + onCordovaReady: channel.onBridgeReady, |
| 12 | + onPluginsReady: channel.onServicesReady, |
| 13 | + waitForInitialization: (name) => |
| 14 | + channel.waitForInitialization(readinessName(name)), |
| 15 | + initializationComplete: (name) => |
| 16 | + channel.initializationComplete(readinessName(name)), |
| 17 | + }; |
| 18 | + const cordova = { |
| 19 | + plugin: { http: bridge.http }, |
| 20 | + plugins: { clipboard: bridge.clipboard }, |
| 21 | + file: bridge.file, |
| 22 | + websocket: bridge.websocket, |
| 23 | + platformId: bridge.platformId, |
| 24 | + version: bridge.version, |
| 25 | + platformVersion: bridge.version, |
| 26 | + require: requireModule, |
| 27 | + define: defineModule, |
| 28 | + }; |
| 29 | + Object.defineProperty(cordova, "exec", { enumerable: true, value: exec }); |
| 30 | + for (const name of [ |
| 31 | + "addConstructor", |
| 32 | + "addDocumentEventHandler", |
| 33 | + "addStickyDocumentEventHandler", |
| 34 | + "removeDocumentEventHandler", |
| 35 | + "fireDocumentEvent", |
| 36 | + "fireWindowEvent", |
| 37 | + ]) |
| 38 | + cordova[name] = (...args) => bridge[name](...args); |
| 39 | + |
| 40 | + for (const name of ["pause", "resume"]) |
| 41 | + host.document.addEventListener(name, (event) => |
| 42 | + channel[name === "pause" ? "onPause" : "onResume"].fire(event), |
| 43 | + ); |
| 44 | + Object.defineProperty(host.device, "cordova", { |
| 45 | + enumerable: true, |
| 46 | + get: () => bridge.version, |
| 47 | + }); |
| 48 | + const app = host.navigator.app; |
| 49 | + let pendingNavigation; |
| 50 | + Object.assign(app, { |
| 51 | + loadUrl(url, options = {}) { |
| 52 | + options ??= {}; |
| 53 | + pendingNavigation = host.setTimeout(() => { |
| 54 | + if (options.clearHistory) app.clearHistory(); |
| 55 | + if (options.openExternal) host.system.openInBrowser(url); |
| 56 | + else host.location.href = url; |
| 57 | + }, options.wait || 0); |
| 58 | + }, |
| 59 | + cancelLoadUrl: () => host.clearTimeout(pendingNavigation), |
| 60 | + }); |
| 61 | + // Proteus dismisses the Android splash before any plugin can load. |
| 62 | + host.navigator.splashscreen = { show() {}, hide() {} }; |
| 63 | + register("cordova", cordova); |
| 64 | + register("cordova/exec", exec); |
| 65 | + register("cordova/channel", legacyChannel); |
| 66 | + register("cordova/base64", base64); |
| 67 | + register("cordova/plugin/android/app", app); |
| 68 | + register("cordova/plugin/android/splashscreen", host.navigator.splashscreen); |
| 69 | + register("cordova/plugin/android/statusbar", host.statusbar); |
| 70 | + |
| 71 | + for (const [id, value] of Object.entries({ |
| 72 | + "cordova-clipboard.Clipboard": bridge.clipboard, |
| 73 | + "cordova-plugin-device.device": host.device, |
| 74 | + "cordova-plugin-server.CreateServer": host.CreateServer, |
| 75 | + "cordova-plugin-ftp.ftp": host.ftp, |
| 76 | + "cordova-plugin-sdcard.sdcard": host.sdcard, |
| 77 | + "cordova-plugin-websocket.WebSocket": bridge.websocket, |
| 78 | + "cordova-plugin-buildinfo.BuildInfo": host.BuildInfo, |
| 79 | + "cordova-plugin-sftp.sftp": host.sftp, |
| 80 | + "com.foxdebug.acode.rk.exec.terminal.Terminal": host.Terminal, |
| 81 | + "com.foxdebug.acode.rk.exec.terminal.Executor": host.Executor, |
| 82 | + "cordova-plugin-iap.iap": host.iap, |
| 83 | + "com.foxdebug.acode.rk.customtabs.CustomTabs": host.CustomTabs, |
| 84 | + "cordova-plugin-advanced-http.http": bridge.http, |
| 85 | + "cordova-plugin-system.system": host.system, |
| 86 | + "admob-plus-cordova.AdMob": host.admob, |
| 87 | + })) |
| 88 | + if (value !== undefined) register(id, value); |
| 89 | + |
| 90 | + for (const name of [ |
| 91 | + "DirectoryEntry", |
| 92 | + "DirectoryReader", |
| 93 | + "Entry", |
| 94 | + "File", |
| 95 | + "FileEntry", |
| 96 | + "FileError", |
| 97 | + "FileReader", |
| 98 | + "FileSystem", |
| 99 | + "FileUploadOptions", |
| 100 | + "FileUploadResult", |
| 101 | + "FileWriter", |
| 102 | + "Flags", |
| 103 | + "LocalFileSystem", |
| 104 | + "Metadata", |
| 105 | + "ProgressEvent", |
| 106 | + "requestFileSystem", |
| 107 | + ]) |
| 108 | + register(`cordova-plugin-file.${name}`, host[name]); |
| 109 | + register("cordova-plugin-file.fileSystemPaths", { file: bridge.file }); |
| 110 | + register("cordova-plugin-file.resolveLocalFileSystemURI", { |
| 111 | + resolveLocalFileSystemURL: host.resolveLocalFileSystemURL, |
| 112 | + resolveLocalFileSystemURI: host.resolveLocalFileSystemURI, |
| 113 | + }); |
| 114 | + host.cordova = cordova; |
| 115 | + |
| 116 | + function exec(success, failure, service, action, args = []) { |
| 117 | + args ||= []; |
| 118 | + if (service === "CordovaHttpPlugin") service = "NativeHttpPlugin"; |
| 119 | + if (service === "CoreAndroid" || service === "App") { |
| 120 | + service = "App"; |
| 121 | + if (action === "overrideBackbutton") { |
| 122 | + action = "overrideButton"; |
| 123 | + args = ["backbutton", args[0]]; |
| 124 | + } else if (action === "overrideButton") { |
| 125 | + args = [args[0].replace(/button$/, "") + "button", args[1]]; |
| 126 | + } else if (action === "loadUrl" || action === "cancelLoadUrl") { |
| 127 | + app[action](...args); |
| 128 | + success?.(); |
| 129 | + return; |
| 130 | + } |
| 131 | + } |
| 132 | + return bridge.exec(success, failure, service, action, args); |
| 133 | + } |
| 134 | + function register(id, exports) { |
| 135 | + modules.set(id, { exports }); |
| 136 | + } |
| 137 | + function defineModule(id, factory) { |
| 138 | + if (modules.has(id)) throw new Error(`Module ${id} already defined`); |
| 139 | + modules.set(id, { factory, exports: {} }); |
| 140 | + } |
| 141 | + function requireModule(id) { |
| 142 | + const module = modules.get(id); |
| 143 | + if (!module) throw new Error(`Module ${id} not found`); |
| 144 | + if (module.factory) { |
| 145 | + const factory = module.factory; |
| 146 | + delete module.factory; |
| 147 | + factory( |
| 148 | + (name) => |
| 149 | + requireModule( |
| 150 | + name.startsWith("./") |
| 151 | + ? `${id.slice(0, id.lastIndexOf("."))}.${name.slice(2)}` |
| 152 | + : name, |
| 153 | + ), |
| 154 | + module.exports, |
| 155 | + module, |
| 156 | + ); |
| 157 | + } |
| 158 | + return module.exports; |
| 159 | + } |
| 160 | +} |
| 161 | + |
| 162 | +function readinessName(name) { |
| 163 | + if (name === "onCordovaReady") return "onBridgeReady"; |
| 164 | + if (name === "onPluginsReady") return "onServicesReady"; |
| 165 | + return name; |
| 166 | +} |
0 commit comments