├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── add-to-project.yml │ ├── mocha-reporter-config.json │ ├── release.yml │ ├── semantic.yml │ └── test.yml ├── .gitignore ├── .releaserc.json ├── LICENSE ├── README.md ├── docs └── migration-2.md ├── index.d.ts ├── main ├── index.d.ts └── index.js ├── package.json ├── renderer ├── index.d.ts └── index.js ├── src ├── common │ ├── get-electron-binding.ts │ ├── ipc-messages.ts │ ├── module-names.ts │ ├── type-utils.ts │ └── types.d.ts ├── internal-ambient.d.ts ├── main │ ├── index.ts │ ├── objects-registry.ts │ └── server.ts └── renderer │ ├── callbacks-registry.ts │ ├── index.ts │ └── remote.ts ├── test ├── all.ts ├── events-helpers.ts ├── fixtures │ ├── call.js │ ├── circular.js │ ├── class.js │ ├── error-properties.js │ ├── exception.js │ ├── export-function-with-properties.js │ ├── function-with-args.js │ ├── function-with-missing-properties.js │ ├── function-with-properties.js │ ├── function.js │ ├── id.js │ ├── no-prototype.js │ ├── preload-remote-function.js │ ├── print_name.js │ ├── promise.js │ ├── property.js │ ├── rejected-promise.js │ ├── remote-event-handler.html │ ├── remote-object-set.js │ ├── remote-static.js │ ├── render-view-deleted.html │ ├── send-on-exit.html │ ├── to-string-non-function.js │ └── unhandled-rejection.js ├── index.js └── window-helpers.ts ├── tsconfig.json └── yarn.lock /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @electron/wg-ecosystem @electron/wg-security @electron/wg-upgrades 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | -------------------------------------------------------------------------------- /.github/workflows/add-to-project.yml: -------------------------------------------------------------------------------- 1 | name: Add to Ecosystem WG Project 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | pull_request_target: 8 | types: 9 | - opened 10 | 11 | permissions: {} 12 | 13 | jobs: 14 | add-to-project: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Generate GitHub App token 18 | uses: electron/github-app-auth-action@384fd19694fe7b6dcc9a684746c6976ad78228ae # v1.1.1 19 | id: generate-token 20 | with: 21 | creds: ${{ secrets.ECOSYSTEM_ISSUE_TRIAGE_GH_APP_CREDS }} 22 | org: electron 23 | - name: Add to Project 24 | uses: dsanders11/project-actions/add-item@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0 25 | with: 26 | field: Opened 27 | field-value: ${{ github.event.pull_request.created_at || github.event.issue.created_at }} 28 | project-number: 89 29 | token: ${{ steps.generate-token.outputs.token }} 30 | -------------------------------------------------------------------------------- /.github/workflows/mocha-reporter-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "reporterEnabled": "spec, mocha-junit-reporter", 3 | "mochaJunitReporterReporterOptions": { 4 | "mochaFile": "test-results/junit.xml" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | test: 10 | uses: ./.github/workflows/test.yml 11 | 12 | release: 13 | name: Release 14 | runs-on: ubuntu-latest 15 | needs: test 16 | environment: npm 17 | permissions: 18 | id-token: write # for CFA and npm provenance 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 22 | with: 23 | persist-credentials: false 24 | - name: Setup Node.js 25 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 26 | with: 27 | node-version: 20.x 28 | cache: 'yarn' 29 | - name: Install 30 | run: yarn install --frozen-lockfile 31 | - uses: continuousauth/action@4e8a2573eeb706f6d7300d6a9f3ca6322740b72d # v1.0.5 32 | timeout-minutes: 60 33 | with: 34 | project-id: ${{ secrets.CFA_PROJECT_ID }} 35 | secret: ${{ secrets.CFA_SECRET }} 36 | npm-token: ${{ secrets.NPM_TOKEN }} 37 | -------------------------------------------------------------------------------- /.github/workflows/semantic.yml: -------------------------------------------------------------------------------- 1 | name: "Check Semantic Commit" 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | main: 15 | permissions: 16 | pull-requests: read # for amannn/action-semantic-pull-request to analyze PRs 17 | statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR 18 | name: Validate PR Title 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: semantic-pull-request 22 | uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | with: 26 | validateSingleCommit: false 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | schedule: 8 | - cron: '0 22 * * 3' 9 | workflow_call: 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | test: 16 | name: Test 17 | strategy: 18 | matrix: 19 | electron-version: 20 | - 12 21 | - 13 22 | - 14 23 | - 15 24 | - 16 25 | - 17 26 | - 18 27 | - 19 28 | - 20 29 | - 21 30 | - 22 31 | - 23 32 | - 24 33 | # Electron v25 is consistently hanging 34 | # when tests run on GHA so skip for now 35 | # - 25 36 | - 26 37 | - 27 38 | - 28 39 | - 29 40 | - 30 41 | - 31 42 | - 32 43 | - 33 44 | runs-on: ubuntu-22.04 45 | steps: 46 | - name: Checkout 47 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 48 | - name: Setup Node.js 49 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 50 | with: 51 | node-version: '18.20' 52 | cache: 'yarn' 53 | - name: Install Dependencies 54 | run: yarn install --frozen-lockfile 55 | - name: Install Electron 56 | run: yarn add "electron@${{ matrix.electron-version }}" 57 | - name: Build 58 | run: yarn tsc 59 | - name: Test (Electron 12) 60 | if : ${{ matrix.electron-version == 12 }} 61 | run: xvfb-run yarn test:ci --in-process-gpu 62 | - name: Test (Electron 13+) 63 | if : ${{ matrix.electron-version != 12 }} 64 | run: xvfb-run yarn test:ci 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | test-results/ 4 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@semantic-release/commit-analyzer", 4 | "@semantic-release/release-notes-generator", 5 | "@continuous-auth/semantic-release-npm", 6 | "@semantic-release/github" 7 | ], 8 | "branches": [ "main" ] 9 | } 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2022 Electron contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @electron/remote 2 | 3 | [![Test](https://github.com/electron/remote/actions/workflows/test.yml/badge.svg)](https://github.com/electron/remote/actions/workflows/test.yml) 4 | [![npm version](http://img.shields.io/npm/v/@electron/remote.svg)](https://npmjs.org/package/@electron/remote) 5 | 6 | `@electron/remote` is an [Electron](https://electronjs.org) module that bridges 7 | JavaScript objects from the main process to the renderer process. This lets you 8 | access main-process-only objects as if they were available in the renderer 9 | process. 10 | 11 | > ⚠️ **Warning!** This module has [many subtle 12 | > pitfalls][remote-considered-harmful]. There is almost always a better way to 13 | > accomplish your task than using this module. For example, [`ipcRenderer.invoke`](https://www.electronjs.org/docs/latest/api/ipc-renderer#ipcrendererinvokechannel-args) can serve many common use cases. 14 | 15 | `@electron/remote` is a replacement for the built-in `remote` module in 16 | Electron, which is deprecated and will eventually be removed. 17 | 18 | ## Migrating from `remote` 19 | 20 | > **NOTE:** `@electron/remote` requires Electron 13 or higher. 21 | 22 | There are three things you need to do to migrate from the built-in `remote` 23 | module to `@electron/remote`. 24 | 25 | First, you need to install it from NPM: 26 | 27 | ```shell 28 | $ npm install --save @electron/remote 29 | ``` 30 | 31 | Second, `@electron/remote/main` must be initialized in the main 32 | process before it can be used from the renderer: 33 | 34 | ```javascript 35 | // in the main process: 36 | require('@electron/remote/main').initialize() 37 | ``` 38 | 39 | Third, `require('electron').remote` in the renderer process must be 40 | replaced with `require('@electron/remote')`. 41 | 42 | ```javascript 43 | // in the renderer process: 44 | 45 | // Before 46 | const { BrowserWindow } = require('electron').remote 47 | 48 | // After 49 | const { BrowserWindow } = require('@electron/remote') 50 | ``` 51 | 52 | **Note:** Since this is requiring a module through npm rather than a built-in 53 | module, if you're using `remote` from a sandboxed process, you'll need to 54 | configure your bundler appropriately to package the code of `@electron/remote` 55 | in the preload script. Of course, [using `@electron/remote` makes the sandbox 56 | much less effective][remote-considered-harmful]. 57 | 58 | **Note:** In `electron >= 14.0.0`, you must use the new `enable` API to enable the remote module for each desired `WebContents` separately: `require("@electron/remote/main").enable(webContents)`. 59 | 60 | In `electron < 14.0.0`, `@electron/remote` respects the `enableRemoteModule` WebPreferences 61 | value. You must pass `{ webPreferences: { enableRemoteModule: true } }` to 62 | the constructor of `BrowserWindow`s that should be granted permission to use 63 | `@electron/remote`. 64 | 65 | # API Reference 66 | 67 | The `remote` module provides a simple way to do inter-process communication 68 | (IPC) between the renderer process (web page) and the main process. 69 | 70 | In Electron, GUI-related modules (such as `dialog`, `menu` etc.) are only 71 | available in the main process, not in the renderer process. In order to use them 72 | from the renderer process, the `ipc` module is necessary to send inter-process 73 | messages to the main process. With the `remote` module, you can invoke methods 74 | of the main process object without explicitly sending inter-process messages, 75 | similar to Java's [RMI][rmi]. An example of creating a browser window from a 76 | renderer process: 77 | 78 | ```javascript 79 | const { BrowserWindow } = require('@electron/remote') 80 | let win = new BrowserWindow({ width: 800, height: 600 }) 81 | win.loadURL('https://github.com') 82 | ``` 83 | 84 | In order for this to work, you first need to initialize the main-process side 85 | of the remote module: 86 | 87 | ```javascript 88 | // in the main process: 89 | require('@electron/remote/main').initialize() 90 | ``` 91 | 92 | **Note:** In `electron >= 14.0.0` the remote module is disabled by default for any `WebContents` instance and is only enabled for specified `WebContents` after explicitly calling `require("@electron/remote/main").enable(webContents)`. 93 | 94 | In `electron < 14.0.0` the remote module can be disabled for security reasons in the following contexts: 95 | - [`BrowserWindow`](https://www.electronjs.org/docs/latest/api/browser-window) - by setting the `enableRemoteModule` option to `false`. 96 | - [``](https://www.electronjs.org/docs/latest/api/webview-tag) - by setting the `enableremotemodule` attribute to `false`. 97 | 98 | ## Remote Objects 99 | 100 | Each object (including functions) returned by the `remote` module represents an 101 | object in the main process (we call it a remote object or remote function). 102 | When you invoke methods of a remote object, call a remote function, or create 103 | a new object with the remote constructor (function), you are actually sending 104 | synchronous inter-process messages. 105 | 106 | In the example above, both `BrowserWindow` and `win` were remote objects and 107 | `new BrowserWindow` didn't create a `BrowserWindow` object in the renderer 108 | process. Instead, it created a `BrowserWindow` object in the main process and 109 | returned the corresponding remote object in the renderer process, namely the 110 | `win` object. 111 | 112 | **Note:** Only [enumerable properties][enumerable-properties] which are present 113 | when the remote object is first referenced are accessible via remote. 114 | 115 | **Note:** Arrays and Buffers are copied over IPC when accessed via the `remote` 116 | module. Modifying them in the renderer process does not modify them in the main 117 | process and vice versa. 118 | 119 | ## Lifetime of Remote Objects 120 | 121 | Electron makes sure that as long as the remote object in the renderer process 122 | lives (in other words, has not been garbage collected), the corresponding object 123 | in the main process will not be released. When the remote object has been 124 | garbage collected, the corresponding object in the main process will be 125 | dereferenced. 126 | 127 | If the remote object is leaked in the renderer process (e.g. stored in a map but 128 | never freed), the corresponding object in the main process will also be leaked, 129 | so you should be very careful not to leak remote objects. 130 | 131 | Primary value types like strings and numbers, however, are sent by copy. 132 | 133 | ## Passing callbacks to the main process 134 | 135 | Code in the main process can accept callbacks from the renderer - for instance 136 | the `remote` module - but you should be extremely careful when using this 137 | feature. 138 | 139 | First, in order to avoid deadlocks, the callbacks passed to the main process 140 | are called asynchronously. You should not expect the main process to 141 | get the return value of the passed callbacks. 142 | 143 | For instance you can't use a function from the renderer process in an 144 | `Array.map` called in the main process: 145 | 146 | ```javascript 147 | // main process mapNumbers.js 148 | exports.withRendererCallback = (mapper) => { 149 | return [1, 2, 3].map(mapper) 150 | } 151 | 152 | exports.withLocalCallback = () => { 153 | return [1, 2, 3].map(x => x + 1) 154 | } 155 | ``` 156 | 157 | ```javascript 158 | // renderer process 159 | const mapNumbers = require('@electron/remote').require('./mapNumbers') 160 | const withRendererCb = mapNumbers.withRendererCallback(x => x + 1) 161 | const withLocalCb = mapNumbers.withLocalCallback() 162 | 163 | console.log(withRendererCb, withLocalCb) 164 | // [undefined, undefined, undefined], [2, 3, 4] 165 | ``` 166 | 167 | As you can see, the renderer callback's synchronous return value was not as 168 | expected, and didn't match the return value of an identical callback that lives 169 | in the main process. 170 | 171 | Second, the callbacks passed to the main process will persist until the 172 | main process garbage-collects them. 173 | 174 | For example, the following code seems innocent at first glance. It installs a 175 | callback for the `close` event on a remote object: 176 | 177 | ```javascript 178 | require('@electron/remote').getCurrentWindow().on('close', () => { 179 | // window was closed... 180 | }) 181 | ``` 182 | 183 | But remember the callback is referenced by the main process until you 184 | explicitly uninstall it. If you do not, each time you reload your window the 185 | callback will be installed again, leaking one callback for each restart. 186 | 187 | To make things worse, since the context of previously installed callbacks has 188 | been released, exceptions will be raised in the main process when the `close` 189 | event is emitted. 190 | 191 | To avoid this problem, ensure you clean up any references to renderer callbacks 192 | passed to the main process. This involves cleaning up event handlers, or 193 | ensuring the main process is explicitly told to dereference callbacks that came 194 | from a renderer process that is exiting. 195 | 196 | ## Accessing built-in modules in the main process 197 | 198 | The built-in modules in the main process are added as getters in the `remote` 199 | module, so you can use them directly like the `electron` module. 200 | 201 | ```javascript 202 | const app = require('@electron/remote').app 203 | console.log(app) 204 | ``` 205 | 206 | ## Methods 207 | 208 | The `remote` module has the following methods: 209 | 210 | ### `remote.require(module)` 211 | 212 | * `module` String 213 | 214 | Returns `any` - The object returned by `require(module)` in the main process. 215 | Modules specified by their relative path will resolve relative to the entrypoint 216 | of the main process. 217 | 218 | e.g. 219 | 220 | ```sh 221 | project/ 222 | ├── main 223 | │ ├── foo.js 224 | │ └── index.js 225 | ├── package.json 226 | └── renderer 227 | └── index.js 228 | ``` 229 | 230 | ```js 231 | // main process: main/index.js 232 | const { app } = require('@electron/remote') 233 | app.whenReady().then(() => { /* ... */ }) 234 | ``` 235 | 236 | ```js 237 | // some relative module: main/foo.js 238 | module.exports = 'bar' 239 | ``` 240 | 241 | ```js 242 | // renderer process: renderer/index.js 243 | const foo = require('@electron/remote').require('./foo') // bar 244 | ``` 245 | 246 | ### `remote.getCurrentWindow()` 247 | 248 | Returns `BrowserWindow` - The window to which this web page belongs. 249 | 250 | **Note:** Do not use `removeAllListeners` on `BrowserWindow`. Use of this can 251 | remove all [`blur`](https://developer.mozilla.org/en-US/docs/Web/Events/blur) 252 | listeners, disable click events on touch bar buttons, and other unintended 253 | consequences. 254 | 255 | ### `remote.getCurrentWebContents()` 256 | 257 | Returns `WebContents` - The web contents of this web page. 258 | 259 | ### `remote.getGlobal(name)` 260 | 261 | * `name` String 262 | 263 | Returns `any` - The global variable of `name` (e.g. `global[name]`) in the main 264 | process. 265 | 266 | ## Properties 267 | 268 | ### `remote.process` _Readonly_ 269 | 270 | A `NodeJS.Process` object. The `process` object in the main process. This is the same as 271 | `remote.getGlobal('process')` but is cached. 272 | 273 | # Overriding exposed objects 274 | 275 | Without filtering, `@electron/remote` will provide access to any JavaScript 276 | object that any renderer requests. In order to control what can be accessed, 277 | `@electron/remote` provides an opportunity to the app to return a custom result 278 | for any of `getGlobal`, `require`, `getCurrentWindow`, `getCurrentWebContents`, 279 | or any of the builtin module properties. 280 | 281 | The following events will be emitted first on the `app` Electron module, and 282 | then on the specific `WebContents` which requested the object. When emitted on 283 | the `app` module, the first parameter after the `Event` object will be the 284 | `WebContents` which originated the request. If any handler calls 285 | `preventDefault`, the request will be denied. If a `returnValue` parameter is 286 | set on the result, then that value will be returned to the renderer instead of 287 | the default. 288 | 289 | ## Events 290 | 291 | ### Event: 'remote-require' 292 | 293 | Returns: 294 | 295 | * `event` Event 296 | * `moduleName` String 297 | 298 | Emitted when `remote.require()` is called in the renderer process of `webContents`. 299 | Calling `event.preventDefault()` will prevent the module from being returned. 300 | Custom value can be returned by setting `event.returnValue`. 301 | 302 | ### Event: 'remote-get-global' 303 | 304 | Returns: 305 | 306 | * `event` Event 307 | * `globalName` String 308 | 309 | Emitted when `remote.getGlobal()` is called in the renderer process of `webContents`. 310 | Calling `event.preventDefault()` will prevent the global from being returned. 311 | Custom value can be returned by setting `event.returnValue`. 312 | 313 | ### Event: 'remote-get-builtin' 314 | 315 | Returns: 316 | 317 | * `event` Event 318 | * `moduleName` String 319 | 320 | Emitted when `remote.getBuiltin()` is called in the renderer process of 321 | `webContents`, including when a builtin module is accessed as a property (e.g. 322 | `require("@electron/remote").BrowserWindow`). 323 | Calling `event.preventDefault()` will prevent the module from being returned. 324 | Custom value can be returned by setting `event.returnValue`. 325 | 326 | ### Event: 'remote-get-current-window' 327 | 328 | Returns: 329 | 330 | * `event` Event 331 | 332 | Emitted when `remote.getCurrentWindow()` is called in the renderer process of `webContents`. 333 | Calling `event.preventDefault()` will prevent the object from being returned. 334 | Custom value can be returned by setting `event.returnValue`. 335 | 336 | ### Event: 'remote-get-current-web-contents' 337 | 338 | Returns: 339 | 340 | * `event` Event 341 | 342 | Emitted when `remote.getCurrentWebContents()` is called in the renderer process of `webContents`. 343 | Calling `event.preventDefault()` will prevent the object from being returned. 344 | Custom value can be returned by setting `event.returnValue`. 345 | 346 | [rmi]: https://en.wikipedia.org/wiki/Java_remote_method_invocation 347 | [enumerable-properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties 348 | [remote-considered-harmful]: https://medium.com/@nornagon/electrons-remote-module-considered-harmful-70d69500f31 349 | -------------------------------------------------------------------------------- /docs/migration-2.md: -------------------------------------------------------------------------------- 1 | # Migrating to `@electron/remote@2.x` 2 | 3 | In `@electron/remote@2.x`, the method of enabling the `remote` module for a 4 | WebContents has changed. Previously, the deprecated `enableRemoteModule` 5 | preference was respected. Beginning with `@electron/remote@2.0.0`, there is a 6 | new API for enabling the `remote` module, called `enable()`. 7 | 8 | ## Usage 9 | 10 | After creating a `WebContents`, and before the `remote` module is first used in 11 | that contents, you must call `enable()`: 12 | 13 | ```js 14 | // Main process 15 | const remoteMain = require("@electron/remote/main") 16 | remoteMain.initialize() 17 | 18 | const win = new BrowserWindow(/* ... */) 19 | remoteMain.enable(win.webContents) 20 | win.loadURL(/* ... */) 21 | ``` 22 | 23 | ## Migration 24 | 25 | To migrate from `@electron/remote@1.x` to `@electron/remote@2.x`, replace all 26 | usages of the `enableRemoteModule: true` WebPreference by a call to `enable()`. 27 | 28 | ```js 29 | // Before (@electron/remote@1.x) 30 | const win = new BrowserWindow({ 31 | webPreferences: { 32 | enableRemoteModule: true 33 | } 34 | }) 35 | 36 | // After (@electron/remote@2.x) 37 | const remoteMain = require("@electron/remote/main") 38 | remoteMain.initialize() 39 | const win = new BrowserWindow() 40 | remoteMain.enable(win.webContents) 41 | ``` 42 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import * as Electron from 'electron'; 2 | 3 | export { 4 | ClientRequest, 5 | CommandLine, 6 | Cookies, 7 | Debugger, 8 | Dock, 9 | DownloadItem, 10 | IncomingMessage, 11 | MessagePortMain, 12 | ServiceWorkers, 13 | TouchBarButton, 14 | TouchBarColorPicker, 15 | TouchBarGroup, 16 | TouchBarLabel, 17 | TouchBarOtherItemsProxy, 18 | TouchBarPopover, 19 | TouchBarScrubber, 20 | TouchBarSegmentedControl, 21 | TouchBarSlider, 22 | TouchBarSpacer, 23 | WebRequest, 24 | } from 'electron/main'; 25 | 26 | // Taken from `RemoteMainInterface` 27 | export var app: Electron.App; 28 | export var autoUpdater: Electron.AutoUpdater; 29 | export var BrowserView: typeof Electron.BrowserView; 30 | export var BrowserWindow: typeof Electron.BrowserWindow; 31 | export var clipboard: Electron.Clipboard; 32 | export var contentTracing: Electron.ContentTracing; 33 | export var crashReporter: Electron.CrashReporter; 34 | export var desktopCapturer: Electron.DesktopCapturer; 35 | export var dialog: Electron.Dialog; 36 | export var globalShortcut: Electron.GlobalShortcut; 37 | export var inAppPurchase: Electron.InAppPurchase; 38 | export var ipcMain: Electron.IpcMain; 39 | export var Menu: typeof Electron.Menu; 40 | export var MenuItem: typeof Electron.MenuItem; 41 | export var MessageChannelMain: typeof Electron.MessageChannelMain; 42 | export var nativeImage: typeof Electron.nativeImage; 43 | export var nativeTheme: Electron.NativeTheme; 44 | export var net: Electron.Net; 45 | export var netLog: Electron.NetLog; 46 | export var Notification: typeof Electron.Notification; 47 | export var powerMonitor: Electron.PowerMonitor; 48 | export var powerSaveBlocker: Electron.PowerSaveBlocker; 49 | export var protocol: Electron.Protocol; 50 | export var screen: Electron.Screen; 51 | export var session: typeof Electron.session; 52 | export var ShareMenu: typeof Electron.ShareMenu; 53 | export var shell: Electron.Shell; 54 | export var systemPreferences: Electron.SystemPreferences; 55 | export var TouchBar: typeof Electron.TouchBar; 56 | export var Tray: typeof Electron.Tray; 57 | export var webContents: typeof Electron.webContents; 58 | export var webFrameMain: typeof Electron.webFrameMain; 59 | 60 | // Taken from `Remote` 61 | export function getCurrentWebContents(): Electron.WebContents; 62 | export function getCurrentWindow(): Electron.BrowserWindow; 63 | export function getGlobal(name: string): any; 64 | export var process: NodeJS.Process; 65 | export var require: NodeJS.Require; 66 | -------------------------------------------------------------------------------- /main/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from '../dist/src/main'; 2 | -------------------------------------------------------------------------------- /main/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../dist/src/main') 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@electron/remote", 3 | "version": "0.0.0-development", 4 | "main": "renderer/index.js", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/electron/remote.git" 9 | }, 10 | "peerDependencies": { 11 | "electron": ">= 13.0.0" 12 | }, 13 | "devDependencies": { 14 | "@types/chai": "^4.2.11", 15 | "@types/chai-as-promised": "^7.1.2", 16 | "@types/dirty-chai": "^2.0.2", 17 | "@types/mocha": "^10.0.10", 18 | "@types/node": "^14.17.0", 19 | "chai": "^4.2.0", 20 | "chai-as-promised": "^7.1.1", 21 | "dirty-chai": "^2.0.1", 22 | "electron": "22.x", 23 | "mocha": "^10.8.2", 24 | "mocha-junit-reporter": "^1.23.3", 25 | "mocha-multi-reporters": "^1.1.7", 26 | "ts-node": "^8.10.2", 27 | "typescript": "^4.1.3", 28 | "walkdir": "^0.4.1", 29 | "yargs": "^15.3.1" 30 | }, 31 | "scripts": { 32 | "prepare": "tsc", 33 | "test": "electron test --extension=ts --require=ts-node/register --exit --js-flags=--expose_gc", 34 | "test:ci": "yarn test --reporter=mocha-multi-reporters --reporter-options=configFile=.github/workflows/mocha-reporter-config.json" 35 | }, 36 | "files": [ 37 | "README.md", 38 | "package.json", 39 | "main", 40 | "renderer", 41 | "dist/src", 42 | "index.d.ts" 43 | ], 44 | "types": "index.d.ts", 45 | "publishConfig": { 46 | "provenance": true 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /renderer/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from '../dist/src/renderer'; 2 | -------------------------------------------------------------------------------- /renderer/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../dist/src/renderer') 2 | -------------------------------------------------------------------------------- /src/common/get-electron-binding.ts: -------------------------------------------------------------------------------- 1 | export const getElectronBinding: typeof process.electronBinding = (name: string) => { 2 | if ((process as any)._linkedBinding) { 3 | return (process as any)._linkedBinding('electron_common_' + name) 4 | } else if (process.electronBinding) { 5 | return process.electronBinding(name as any) 6 | } else { 7 | return null 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/common/ipc-messages.ts: -------------------------------------------------------------------------------- 1 | export const enum IPC_MESSAGES { 2 | BROWSER_REQUIRE = 'REMOTE_BROWSER_REQUIRE', 3 | BROWSER_GET_BUILTIN = 'REMOTE_BROWSER_GET_BUILTIN', 4 | BROWSER_GET_GLOBAL = 'REMOTE_BROWSER_GET_GLOBAL', 5 | BROWSER_GET_CURRENT_WINDOW = 'REMOTE_BROWSER_GET_CURRENT_WINDOW', 6 | BROWSER_GET_CURRENT_WEB_CONTENTS = 'REMOTE_BROWSER_GET_CURRENT_WEB_CONTENTS', 7 | BROWSER_CONSTRUCTOR = 'REMOTE_BROWSER_CONSTRUCTOR', 8 | BROWSER_FUNCTION_CALL = 'REMOTE_BROWSER_FUNCTION_CALL', 9 | BROWSER_MEMBER_CONSTRUCTOR = 'REMOTE_BROWSER_MEMBER_CONSTRUCTOR', 10 | BROWSER_MEMBER_CALL = 'REMOTE_BROWSER_MEMBER_CALL', 11 | BROWSER_MEMBER_GET = 'REMOTE_BROWSER_MEMBER_GET', 12 | BROWSER_MEMBER_SET = 'REMOTE_BROWSER_MEMBER_SET', 13 | BROWSER_DEREFERENCE = 'REMOTE_BROWSER_DEREFERENCE', 14 | BROWSER_CONTEXT_RELEASE = 'REMOTE_BROWSER_CONTEXT_RELEASE', 15 | BROWSER_WRONG_CONTEXT_ERROR = 'REMOTE_BROWSER_WRONG_CONTEXT_ERROR', 16 | 17 | RENDERER_CALLBACK = 'REMOTE_RENDERER_CALLBACK', 18 | RENDERER_RELEASE_CALLBACK = 'REMOTE_RENDERER_RELEASE_CALLBACK', 19 | } 20 | -------------------------------------------------------------------------------- /src/common/module-names.ts: -------------------------------------------------------------------------------- 1 | import { getElectronBinding } from './get-electron-binding' 2 | 3 | export const commonModuleNames = [ 4 | 'clipboard', 5 | 'nativeImage', 6 | 'shell', 7 | ]; 8 | 9 | export const browserModuleNames = [ 10 | 'app', 11 | 'autoUpdater', 12 | 'BaseWindow', 13 | 'BrowserView', 14 | 'BrowserWindow', 15 | 'contentTracing', 16 | 'crashReporter', 17 | 'dialog', 18 | 'globalShortcut', 19 | 'ipcMain', 20 | 'inAppPurchase', 21 | 'Menu', 22 | 'MenuItem', 23 | 'nativeTheme', 24 | 'net', 25 | 'netLog', 26 | 'MessageChannelMain', 27 | 'Notification', 28 | 'powerMonitor', 29 | 'powerSaveBlocker', 30 | 'protocol', 31 | 'pushNotifications', 32 | 'safeStorage', 33 | 'screen', 34 | 'session', 35 | 'ShareMenu', 36 | 'systemPreferences', 37 | 'TopLevelWindow', 38 | 'TouchBar', 39 | 'Tray', 40 | 'utilityProcess', 41 | 'View', 42 | 'webContents', 43 | 'WebContentsView', 44 | 'webFrameMain', 45 | ].concat(commonModuleNames); 46 | 47 | const features = getElectronBinding('features'); 48 | 49 | if (features?.isDesktopCapturerEnabled?.() !== false) { 50 | browserModuleNames.push('desktopCapturer'); 51 | } 52 | 53 | if (features?.isViewApiEnabled?.() !== false) { 54 | browserModuleNames.push('ImageView'); 55 | } 56 | -------------------------------------------------------------------------------- /src/common/type-utils.ts: -------------------------------------------------------------------------------- 1 | import { nativeImage } from 'electron'; 2 | 3 | export function isPromise (val: any) { 4 | return ( 5 | val && 6 | val.then && 7 | val.then instanceof Function && 8 | val.constructor && 9 | val.constructor.reject && 10 | val.constructor.reject instanceof Function && 11 | val.constructor.resolve && 12 | val.constructor.resolve instanceof Function 13 | ); 14 | } 15 | 16 | const serializableTypes = [ 17 | Boolean, 18 | Number, 19 | String, 20 | Date, 21 | Error, 22 | RegExp, 23 | ArrayBuffer 24 | ]; 25 | 26 | // https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#Supported_types 27 | export function isSerializableObject (value: any) { 28 | return value === null || ArrayBuffer.isView(value) || serializableTypes.some(type => value instanceof type); 29 | } 30 | 31 | const objectMap = function (source: Object, mapper: (value: any) => any) { 32 | const sourceEntries = Object.entries(source); 33 | const targetEntries = sourceEntries.map(([key, val]) => [key, mapper(val)]); 34 | return Object.fromEntries(targetEntries); 35 | }; 36 | 37 | function serializeNativeImage (image: Electron.NativeImage) { 38 | const representations = []; 39 | const scaleFactors = image.getScaleFactors(); 40 | 41 | // Use Buffer when there's only one representation for better perf. 42 | // This avoids compressing to/from PNG where it's not necessary to 43 | // ensure uniqueness of dataURLs (since there's only one). 44 | if (scaleFactors.length === 1) { 45 | const scaleFactor = scaleFactors[0]; 46 | const size = image.getSize(scaleFactor); 47 | const buffer = image.toBitmap({ scaleFactor }); 48 | representations.push({ scaleFactor, size, buffer }); 49 | } else { 50 | // Construct from dataURLs to ensure that they are not lost in creation. 51 | for (const scaleFactor of scaleFactors) { 52 | const size = image.getSize(scaleFactor); 53 | const dataURL = image.toDataURL({ scaleFactor }); 54 | representations.push({ scaleFactor, size, dataURL }); 55 | } 56 | } 57 | return { __ELECTRON_SERIALIZED_NativeImage__: true, representations }; 58 | } 59 | 60 | function deserializeNativeImage (value: any) { 61 | const image = nativeImage.createEmpty(); 62 | 63 | // Use Buffer when there's only one representation for better perf. 64 | // This avoids compressing to/from PNG where it's not necessary to 65 | // ensure uniqueness of dataURLs (since there's only one). 66 | if (value.representations.length === 1) { 67 | const { buffer, size, scaleFactor } = value.representations[0]; 68 | const { width, height } = size; 69 | image.addRepresentation({ buffer, scaleFactor, width, height }); 70 | } else { 71 | // Construct from dataURLs to ensure that they are not lost in creation. 72 | for (const rep of value.representations) { 73 | const { dataURL, size, scaleFactor } = rep; 74 | const { width, height } = size; 75 | image.addRepresentation({ dataURL, scaleFactor, width, height }); 76 | } 77 | } 78 | 79 | return image; 80 | } 81 | 82 | export function serialize (value: any): any { 83 | if (value && value.constructor && value.constructor.name === 'NativeImage') { 84 | return serializeNativeImage(value); 85 | } if (Array.isArray(value)) { 86 | return value.map(serialize); 87 | } else if (isSerializableObject(value)) { 88 | return value; 89 | } else if (value instanceof Object) { 90 | return objectMap(value, serialize); 91 | } else { 92 | return value; 93 | } 94 | } 95 | 96 | export function deserialize (value: any): any { 97 | if (value && value.__ELECTRON_SERIALIZED_NativeImage__) { 98 | return deserializeNativeImage(value); 99 | } else if (Array.isArray(value)) { 100 | return value.map(deserialize); 101 | } else if (isSerializableObject(value)) { 102 | return value; 103 | } else if (value instanceof Object) { 104 | return objectMap(value, deserialize); 105 | } else { 106 | return value; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/common/types.d.ts: -------------------------------------------------------------------------------- 1 | import type { Size } from 'electron'; 2 | import type { NativeImage } from 'electron'; 3 | 4 | export type ObjectMember = { 5 | name: string, 6 | value?: any, 7 | enumerable?: boolean, 8 | writable?: boolean, 9 | type?: 'method' | 'get' 10 | } 11 | 12 | export type ObjProtoDescriptor = { 13 | members: ObjectMember[], 14 | proto: ObjProtoDescriptor 15 | } | null 16 | 17 | export type MetaType = { 18 | type: 'object' | 'function', 19 | name: string, 20 | members: ObjectMember[], 21 | proto: ObjProtoDescriptor, 22 | id: number, 23 | } | { 24 | type: 'value', 25 | value: any, 26 | } | { 27 | type: 'buffer', 28 | value: Uint8Array, 29 | } | { 30 | type: 'array', 31 | members: MetaType[] 32 | } | { 33 | type: 'error', 34 | value: Error, 35 | members: ObjectMember[] 36 | } | { 37 | type: 'exception', 38 | value: MetaType, 39 | } | { 40 | type: 'promise', 41 | then: MetaType 42 | } | { 43 | type: 'nativeimage' 44 | value: NativeImage 45 | } 46 | 47 | export type MetaTypeFromRenderer = { 48 | type: 'value', 49 | value: any 50 | } | { 51 | type: 'remote-object', 52 | id: number 53 | } | { 54 | type: 'array', 55 | value: MetaTypeFromRenderer[] 56 | } | { 57 | type: 'buffer', 58 | value: Uint8Array 59 | } | { 60 | type: 'promise', 61 | then: MetaTypeFromRenderer 62 | } | { 63 | type: 'object', 64 | name: string, 65 | members: { 66 | name: string, 67 | value: MetaTypeFromRenderer 68 | }[] 69 | } | { 70 | type: 'function-with-return-value', 71 | value: MetaTypeFromRenderer 72 | } | { 73 | type: 'function', 74 | id: number, 75 | location: string, 76 | length: number 77 | } | { 78 | type: 'nativeimage', 79 | value: { 80 | size: Size, 81 | buffer: Buffer, 82 | scaleFactor: number, 83 | dataURL: string 84 | }[] 85 | } 86 | -------------------------------------------------------------------------------- /src/internal-ambient.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace Electron { 2 | interface WebContents { 3 | getLastWebPreferences(): WebPreferences; 4 | getOwnerBrowserWindow(): BrowserWindow; 5 | } 6 | } 7 | 8 | declare namespace NodeJS { 9 | interface V8UtilBinding { 10 | getHiddenValue(obj: Object, key: string): T; 11 | } 12 | 13 | interface EventBinding { 14 | createWithSender(contents: Electron.WebContents): Electron.Event & { returnValue: any } 15 | } 16 | 17 | interface FeaturesBinding { 18 | isDesktopCapturerEnabled(): boolean; 19 | isViewApiEnabled(): boolean; 20 | } 21 | 22 | interface Process { 23 | electronBinding(name: 'event'): EventBinding; 24 | electronBinding(name: 'v8_util'): V8UtilBinding; 25 | electronBinding(name: 'features'): FeaturesBinding; 26 | readonly contextId?: string; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/index.ts: -------------------------------------------------------------------------------- 1 | export { initialize, isInitialized, enable } from "./server"; 2 | -------------------------------------------------------------------------------- /src/main/objects-registry.ts: -------------------------------------------------------------------------------- 1 | import { WebContents } from 'electron' 2 | 3 | const getOwnerKey = (webContents: WebContents, contextId: string) => { 4 | return `${webContents.id}-${contextId}` 5 | } 6 | 7 | class ObjectsRegistry { 8 | private nextId: number = 0 9 | 10 | // Stores all objects by ref-counting. 11 | // (id) => {object, count} 12 | private storage: Record = {} 13 | 14 | // Stores the IDs + refCounts of objects referenced by WebContents. 15 | // (ownerKey) => { id: refCount } 16 | private owners: Record> = {} 17 | 18 | private electronIds = new WeakMap(); 19 | 20 | // Register a new object and return its assigned ID. If the object is already 21 | // registered then the already assigned ID would be returned. 22 | add (webContents: WebContents, contextId: string, obj: any): number { 23 | // Get or assign an ID to the object. 24 | const id = this.saveToStorage(obj) 25 | 26 | // Add object to the set of referenced objects. 27 | const ownerKey = getOwnerKey(webContents, contextId) 28 | let owner = this.owners[ownerKey] 29 | if (!owner) { 30 | owner = this.owners[ownerKey] = new Map() 31 | this.registerDeleteListener(webContents, contextId) 32 | } 33 | if (!owner.has(id)) { 34 | owner.set(id, 0) 35 | // Increase reference count if not referenced before. 36 | this.storage[id].count++ 37 | } 38 | 39 | owner.set(id, owner.get(id)! + 1) 40 | return id 41 | } 42 | 43 | // Get an object according to its ID. 44 | get (id: number): any { 45 | const pointer = this.storage[id] 46 | if (pointer != null) return pointer.object 47 | } 48 | 49 | // Dereference an object according to its ID. 50 | // Note that an object may be double-freed (cleared when page is reloaded, and 51 | // then garbage collected in old page). 52 | remove (webContents: WebContents, contextId: string, id: number): void { 53 | const ownerKey = getOwnerKey(webContents, contextId) 54 | const owner = this.owners[ownerKey] 55 | if (owner && owner.has(id)) { 56 | const newRefCount = owner.get(id)! - 1 57 | 58 | // Only completely remove if the number of references GCed in the 59 | // renderer is the same as the number of references we sent them 60 | if (newRefCount <= 0) { 61 | // Remove the reference in owner. 62 | owner.delete(id) 63 | // Dereference from the storage. 64 | this.dereference(id) 65 | } else { 66 | owner.set(id, newRefCount) 67 | } 68 | } 69 | } 70 | 71 | // Clear all references to objects refrenced by the WebContents. 72 | clear (webContents: WebContents, contextId: string): void { 73 | const ownerKey = getOwnerKey(webContents, contextId) 74 | const owner = this.owners[ownerKey] 75 | if (!owner) return 76 | 77 | for (const id of owner.keys()) this.dereference(id) 78 | 79 | delete this.owners[ownerKey] 80 | } 81 | 82 | // Saves the object into storage and assigns an ID for it. 83 | private saveToStorage (object: any): number { 84 | let id = this.electronIds.get(object) 85 | if (!id) { 86 | id = ++this.nextId 87 | this.storage[id] = { 88 | count: 0, 89 | object: object 90 | } 91 | this.electronIds.set(object, id); 92 | } 93 | return id 94 | } 95 | 96 | // Dereference the object from store. 97 | private dereference (id: number): void { 98 | const pointer = this.storage[id] 99 | if (pointer == null) { 100 | return 101 | } 102 | pointer.count -= 1 103 | if (pointer.count === 0) { 104 | this.electronIds.delete(pointer.object); 105 | delete this.storage[id] 106 | } 107 | } 108 | 109 | // Clear the storage when renderer process is destroyed. 110 | private registerDeleteListener (webContents: WebContents, contextId: string): void { 111 | // contextId => ${processHostId}-${contextCount} 112 | const processHostId = contextId.split('-')[0] 113 | const listener = (_: any, deletedProcessHostId: string) => { 114 | if (deletedProcessHostId && 115 | deletedProcessHostId.toString() === processHostId) { 116 | webContents.removeListener('render-view-deleted' as any, listener) 117 | this.clear(webContents, contextId) 118 | } 119 | } 120 | // Note that the "render-view-deleted" event may not be emitted on time when 121 | // the renderer process get destroyed because of navigation, we rely on the 122 | // renderer process to send "ELECTRON_BROWSER_CONTEXT_RELEASE" message to 123 | // guard this situation. 124 | webContents.on('render-view-deleted' as any, listener) 125 | } 126 | } 127 | 128 | export default new ObjectsRegistry() 129 | -------------------------------------------------------------------------------- /src/main/server.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events' 2 | import objectsRegistry from './objects-registry' 3 | import { isPromise, isSerializableObject, deserialize, serialize } from '../common/type-utils' 4 | import type { MetaTypeFromRenderer, ObjectMember, MetaType, ObjProtoDescriptor } from '../common/types' 5 | import { ipcMain, WebContents, IpcMainEvent, app } from 'electron' 6 | import { IPC_MESSAGES } from '../common/ipc-messages'; 7 | import { getElectronBinding } from '../common/get-electron-binding' 8 | 9 | const { Promise } = global 10 | 11 | const v8Util = getElectronBinding('v8_util') 12 | 13 | const hasWebPrefsRemoteModuleAPI = (() => { 14 | const electronVersion = Number(process.versions.electron?.split(".")?.[0]); 15 | return Number.isNaN(electronVersion) || electronVersion < 14 16 | })() 17 | 18 | // The internal properties of Function. 19 | const FUNCTION_PROPERTIES = [ 20 | 'length', 'name', 'arguments', 'caller', 'prototype' 21 | ] 22 | 23 | type RendererFunctionId = [string, number] // [contextId, funcId] 24 | type FinalizerInfo = { id: RendererFunctionId, webContents: WebContents, frameId: number } 25 | type CallIntoRenderer = (...args: any[]) => void 26 | 27 | // The remote functions in renderer processes. 28 | const rendererFunctionCache = new Map>() 29 | // eslint-disable-next-line no-undef 30 | const finalizationRegistry = new FinalizationRegistry((fi: FinalizerInfo) => { 31 | const mapKey = fi.id[0] + '~' + fi.id[1] 32 | const ref = rendererFunctionCache.get(mapKey) 33 | if (ref !== undefined && ref.deref() === undefined) { 34 | rendererFunctionCache.delete(mapKey) 35 | if (!fi.webContents.isDestroyed()) { 36 | try { 37 | fi.webContents.sendToFrame(fi.frameId, IPC_MESSAGES.RENDERER_RELEASE_CALLBACK, fi.id[0], fi.id[1]); 38 | } catch (error) { 39 | console.warn(`sendToFrame() failed: ${error}`); 40 | } 41 | } 42 | } 43 | }) 44 | 45 | function getCachedRendererFunction (id: RendererFunctionId): CallIntoRenderer | undefined { 46 | const mapKey = id[0] + '~' + id[1] 47 | const ref = rendererFunctionCache.get(mapKey) 48 | if (ref !== undefined) { 49 | const deref = ref.deref() 50 | if (deref !== undefined) return deref 51 | } 52 | } 53 | function setCachedRendererFunction (id: RendererFunctionId, wc: WebContents, frameId: number, value: CallIntoRenderer) { 54 | // eslint-disable-next-line no-undef 55 | const wr = new WeakRef(value) 56 | const mapKey = id[0] + '~' + id[1] 57 | rendererFunctionCache.set(mapKey, wr) 58 | finalizationRegistry.register(value, { 59 | id, 60 | webContents: wc, 61 | frameId 62 | } as FinalizerInfo) 63 | return value 64 | } 65 | 66 | const locationInfo = new WeakMap(); 67 | 68 | // Return the description of object's members: 69 | const getObjectMembers = function (object: any): ObjectMember[] { 70 | let names = Object.getOwnPropertyNames(object) 71 | // For Function, we should not override following properties even though they 72 | // are "own" properties. 73 | if (typeof object === 'function') { 74 | names = names.filter((name) => { 75 | return !FUNCTION_PROPERTIES.includes(name) 76 | }) 77 | } 78 | // Map properties to descriptors. 79 | return names.map((name) => { 80 | const descriptor = Object.getOwnPropertyDescriptor(object, name)! 81 | let type: ObjectMember['type'] 82 | let writable = false 83 | if (descriptor.get === undefined && typeof object[name] === 'function') { 84 | type = 'method' 85 | } else { 86 | if (descriptor.set || descriptor.writable) writable = true 87 | type = 'get' 88 | } 89 | return { name, enumerable: descriptor.enumerable, writable, type } 90 | }) 91 | } 92 | 93 | // Return the description of object's prototype. 94 | const getObjectPrototype = function (object: any): ObjProtoDescriptor { 95 | const proto = Object.getPrototypeOf(object) 96 | if (proto === null || proto === Object.prototype) return null 97 | return { 98 | members: getObjectMembers(proto), 99 | proto: getObjectPrototype(proto) 100 | } 101 | } 102 | 103 | // Convert a real value into meta data. 104 | const valueToMeta = function (sender: WebContents, contextId: string, value: any, optimizeSimpleObject = false): MetaType { 105 | // Determine the type of value. 106 | let type: MetaType['type'] 107 | switch (typeof value) { 108 | case 'object': 109 | // Recognize certain types of objects. 110 | if (value instanceof Buffer) { 111 | type = 'buffer' 112 | } else if (value && value.constructor && value.constructor.name === 'NativeImage') { 113 | type = 'nativeimage' 114 | } else if (Array.isArray(value)) { 115 | type = 'array' 116 | } else if (value instanceof Error) { 117 | type = 'error' 118 | } else if (isSerializableObject(value)) { 119 | type = 'value' 120 | } else if (isPromise(value)) { 121 | type = 'promise' 122 | } else if (Object.prototype.hasOwnProperty.call(value, 'callee') && value.length != null) { 123 | // Treat the arguments object as array. 124 | type = 'array' 125 | } else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) { 126 | // Treat simple objects as value. 127 | type = 'value' 128 | } else { 129 | type = 'object' 130 | } 131 | break 132 | case 'function': 133 | type = 'function' 134 | break 135 | default: 136 | type = 'value' 137 | break 138 | } 139 | 140 | // Fill the meta object according to value's type. 141 | if (type === 'array') { 142 | return { 143 | type, 144 | members: value.map((el: any) => valueToMeta(sender, contextId, el, optimizeSimpleObject)) 145 | } 146 | } else if (type === 'nativeimage') { 147 | return { type, value: serialize(value) } 148 | } else if (type === 'object' || type === 'function') { 149 | return { 150 | type, 151 | name: value.constructor ? value.constructor.name : '', 152 | // Reference the original value if it's an object, because when it's 153 | // passed to renderer we would assume the renderer keeps a reference of 154 | // it. 155 | id: objectsRegistry.add(sender, contextId, value), 156 | members: getObjectMembers(value), 157 | proto: getObjectPrototype(value) 158 | } 159 | } else if (type === 'buffer') { 160 | return { type, value } 161 | } else if (type === 'promise') { 162 | // Add default handler to prevent unhandled rejections in main process 163 | // Instead they should appear in the renderer process 164 | value.then(function () {}, function () {}) 165 | 166 | return { 167 | type, 168 | then: valueToMeta(sender, contextId, function (onFulfilled: Function, onRejected: Function) { 169 | value.then(onFulfilled, onRejected) 170 | }) 171 | } 172 | } else if (type === 'error') { 173 | return { 174 | type, 175 | value, 176 | members: Object.keys(value).map(name => ({ 177 | name, 178 | value: valueToMeta(sender, contextId, value[name]) 179 | })) 180 | } 181 | } else { 182 | return { 183 | type: 'value', 184 | value 185 | } 186 | } 187 | } 188 | 189 | const throwRPCError = function (message: string) { 190 | const error = new Error(message) as Error & {code: string, errno: number} 191 | error.code = 'EBADRPC' 192 | error.errno = -72 193 | throw error 194 | } 195 | 196 | const removeRemoteListenersAndLogWarning = (sender: any, callIntoRenderer: (...args: any[]) => void) => { 197 | const location = locationInfo.get(callIntoRenderer); 198 | let message = 'Attempting to call a function in a renderer window that has been closed or released.' + 199 | `\nFunction provided here: ${location}` 200 | 201 | if (sender instanceof EventEmitter) { 202 | const remoteEvents = sender.eventNames().filter((eventName) => { 203 | return sender.listeners(eventName).includes(callIntoRenderer) 204 | }) 205 | 206 | if (remoteEvents.length > 0) { 207 | message += `\nRemote event names: ${remoteEvents.join(', ')}` 208 | remoteEvents.forEach((eventName) => { 209 | sender.removeListener(eventName, callIntoRenderer) 210 | }) 211 | } 212 | } 213 | 214 | console.warn(message) 215 | } 216 | 217 | const fakeConstructor = (constructor: Function, name: string) => 218 | new Proxy(Object, { 219 | get (target, prop, receiver) { 220 | if (prop === 'name') { 221 | return name 222 | } else { 223 | return Reflect.get(target, prop, receiver) 224 | } 225 | } 226 | }) 227 | 228 | // Convert array of meta data from renderer into array of real values. 229 | const unwrapArgs = function (sender: WebContents, frameId: number, contextId: string, args: any[]) { 230 | const metaToValue = function (meta: MetaTypeFromRenderer): any { 231 | switch (meta.type) { 232 | case 'nativeimage': 233 | return deserialize(meta.value) 234 | case 'value': 235 | return meta.value 236 | case 'remote-object': 237 | return objectsRegistry.get(meta.id) 238 | case 'array': 239 | return unwrapArgs(sender, frameId, contextId, meta.value) 240 | case 'buffer': 241 | return Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength) 242 | case 'promise': 243 | return Promise.resolve({ 244 | then: metaToValue(meta.then) 245 | }) 246 | case 'object': { 247 | const ret: any = meta.name !== 'Object' ? Object.create({ 248 | constructor: fakeConstructor(Object, meta.name) 249 | }) : {} 250 | 251 | for (const { name, value } of meta.members) { 252 | ret[name] = metaToValue(value) 253 | } 254 | return ret 255 | } 256 | case 'function-with-return-value': { 257 | const returnValue = metaToValue(meta.value) 258 | return function () { 259 | return returnValue 260 | } 261 | } 262 | case 'function': { 263 | // Merge contextId and meta.id, since meta.id can be the same in 264 | // different webContents. 265 | const objectId: [string, number] = [contextId, meta.id] 266 | 267 | // Cache the callbacks in renderer. 268 | const cachedFunction = getCachedRendererFunction(objectId) 269 | if (cachedFunction !== undefined) { return cachedFunction; } 270 | 271 | const callIntoRenderer = function (this: any, ...args: any[]) { 272 | let succeed = false 273 | if (!sender.isDestroyed()) { 274 | try { 275 | succeed = sender.sendToFrame(frameId, IPC_MESSAGES.RENDERER_CALLBACK, contextId, meta.id, valueToMeta(sender, contextId, args)) as any as boolean !== false; 276 | } catch (error) { 277 | console.warn(`sendToFrame() failed: ${error}`); 278 | } 279 | } 280 | if (!succeed) { 281 | removeRemoteListenersAndLogWarning(this, callIntoRenderer) 282 | } 283 | } 284 | locationInfo.set(callIntoRenderer, meta.location); 285 | Object.defineProperty(callIntoRenderer, 'length', { value: meta.length }) 286 | 287 | setCachedRendererFunction(objectId, sender, frameId, callIntoRenderer) 288 | return callIntoRenderer 289 | } 290 | default: 291 | throw new TypeError(`Unknown type: ${(meta as any).type}`) 292 | } 293 | } 294 | return args.map(metaToValue) 295 | } 296 | 297 | const isRemoteModuleEnabledImpl = function (contents: WebContents) { 298 | const webPreferences = contents.getLastWebPreferences() || {} 299 | return (webPreferences as any).enableRemoteModule != null ? !!(webPreferences as any).enableRemoteModule : false 300 | } 301 | 302 | const isRemoteModuleEnabledCache = new WeakMap() 303 | 304 | export const isRemoteModuleEnabled = function (contents: WebContents) { 305 | if (hasWebPrefsRemoteModuleAPI && !isRemoteModuleEnabledCache.has(contents)) { 306 | isRemoteModuleEnabledCache.set(contents, isRemoteModuleEnabledImpl(contents)) 307 | } 308 | 309 | return isRemoteModuleEnabledCache.get(contents) 310 | } 311 | 312 | export function enable(contents: WebContents) { 313 | isRemoteModuleEnabledCache.set(contents, true) 314 | } 315 | 316 | const handleRemoteCommand = function (channel: string, handler: (event: IpcMainEvent, contextId: string, ...args: any[]) => void) { 317 | ipcMain.on(channel, (event, contextId: string, ...args: any[]) => { 318 | let returnValue: MetaType | null | void 319 | 320 | if (!isRemoteModuleEnabled(event.sender)) { 321 | event.returnValue = { 322 | type: 'exception', 323 | value: valueToMeta(event.sender, contextId, new Error( 324 | '@electron/remote is disabled for this WebContents. Call require("@electron/remote/main").enable(webContents) to enable it.' 325 | )) 326 | } 327 | return 328 | } 329 | 330 | try { 331 | returnValue = handler(event, contextId, ...args) 332 | } catch (error) { 333 | returnValue = { 334 | type: 'exception', 335 | value: valueToMeta(event.sender, contextId, error), 336 | } 337 | } 338 | 339 | if (returnValue !== undefined) { 340 | event.returnValue = returnValue 341 | } 342 | }) 343 | } 344 | 345 | const emitCustomEvent = function (contents: WebContents, eventName: string, ...args: any[]) { 346 | const event = { sender: contents, returnValue: undefined as any, defaultPrevented: false } 347 | 348 | app.emit(eventName, event, contents, ...args) 349 | contents.emit(eventName, event, ...args) 350 | 351 | return event 352 | } 353 | 354 | const logStack = function (contents: WebContents, code: string, stack: string | undefined) { 355 | if (stack) { 356 | console.warn(`WebContents (${contents.id}): ${code}`, stack) 357 | } 358 | } 359 | 360 | let initialized = false 361 | 362 | export function isInitialized() { 363 | return initialized 364 | } 365 | 366 | export function initialize() { 367 | if (initialized) 368 | throw new Error('@electron/remote has already been initialized') 369 | initialized = true 370 | handleRemoteCommand(IPC_MESSAGES.BROWSER_WRONG_CONTEXT_ERROR, function (event, contextId, passedContextId, id) { 371 | const objectId: [string, number] = [passedContextId, id] 372 | const cachedFunction = getCachedRendererFunction(objectId) 373 | if (cachedFunction === undefined) { 374 | // Do nothing if the error has already been reported before. 375 | return 376 | } 377 | removeRemoteListenersAndLogWarning(event.sender, cachedFunction) 378 | }) 379 | 380 | handleRemoteCommand(IPC_MESSAGES.BROWSER_REQUIRE, function (event, contextId, moduleName, stack) { 381 | logStack(event.sender, `remote.require('${moduleName}')`, stack) 382 | const customEvent = emitCustomEvent(event.sender, 'remote-require', moduleName) 383 | 384 | if (customEvent.returnValue === undefined) { 385 | if (customEvent.defaultPrevented) { 386 | throw new Error(`Blocked remote.require('${moduleName}')`) 387 | } else { 388 | // electron < 28 389 | if (process.mainModule) { 390 | customEvent.returnValue = process.mainModule!.require(moduleName) 391 | } else { 392 | // electron >= 28 393 | let mainModule = module; 394 | while (mainModule.parent) { 395 | mainModule = mainModule.parent; 396 | } 397 | customEvent.returnValue = mainModule.require(moduleName) 398 | } 399 | } 400 | } 401 | 402 | return valueToMeta(event.sender, contextId, customEvent.returnValue) 403 | }) 404 | 405 | handleRemoteCommand(IPC_MESSAGES.BROWSER_GET_BUILTIN, function (event, contextId, moduleName, stack) { 406 | logStack(event.sender, `remote.getBuiltin('${moduleName}')`, stack) 407 | const customEvent = emitCustomEvent(event.sender, 'remote-get-builtin', moduleName) 408 | 409 | if (customEvent.returnValue === undefined) { 410 | if (customEvent.defaultPrevented) { 411 | throw new Error(`Blocked remote.getBuiltin('${moduleName}')`) 412 | } else { 413 | customEvent.returnValue = (require('electron') as any)[moduleName] 414 | } 415 | } 416 | 417 | return valueToMeta(event.sender, contextId, customEvent.returnValue) 418 | }) 419 | 420 | handleRemoteCommand(IPC_MESSAGES.BROWSER_GET_GLOBAL, function (event, contextId, globalName, stack) { 421 | logStack(event.sender, `remote.getGlobal('${globalName}')`, stack) 422 | const customEvent = emitCustomEvent(event.sender, 'remote-get-global', globalName) 423 | 424 | if (customEvent.returnValue === undefined) { 425 | if (customEvent.defaultPrevented) { 426 | throw new Error(`Blocked remote.getGlobal('${globalName}')`) 427 | } else { 428 | customEvent.returnValue = (global as any)[globalName] 429 | } 430 | } 431 | 432 | return valueToMeta(event.sender, contextId, customEvent.returnValue) 433 | }) 434 | 435 | handleRemoteCommand(IPC_MESSAGES.BROWSER_GET_CURRENT_WINDOW, function (event, contextId, stack) { 436 | logStack(event.sender, 'remote.getCurrentWindow()', stack) 437 | const customEvent = emitCustomEvent(event.sender, 'remote-get-current-window') 438 | 439 | if (customEvent.returnValue === undefined) { 440 | if (customEvent.defaultPrevented) { 441 | throw new Error('Blocked remote.getCurrentWindow()') 442 | } else { 443 | customEvent.returnValue = event.sender.getOwnerBrowserWindow() 444 | } 445 | } 446 | 447 | return valueToMeta(event.sender, contextId, customEvent.returnValue) 448 | }) 449 | 450 | handleRemoteCommand(IPC_MESSAGES.BROWSER_GET_CURRENT_WEB_CONTENTS, function (event, contextId, stack) { 451 | logStack(event.sender, 'remote.getCurrentWebContents()', stack) 452 | const customEvent = emitCustomEvent(event.sender, 'remote-get-current-web-contents') 453 | 454 | if (customEvent.returnValue === undefined) { 455 | if (customEvent.defaultPrevented) { 456 | throw new Error('Blocked remote.getCurrentWebContents()') 457 | } else { 458 | customEvent.returnValue = event.sender 459 | } 460 | } 461 | 462 | return valueToMeta(event.sender, contextId, customEvent.returnValue) 463 | }) 464 | 465 | handleRemoteCommand(IPC_MESSAGES.BROWSER_CONSTRUCTOR, function (event, contextId, id, args) { 466 | args = unwrapArgs(event.sender, event.frameId, contextId, args) 467 | const constructor = objectsRegistry.get(id) 468 | 469 | if (constructor == null) { 470 | throwRPCError(`Cannot call constructor on missing remote object ${id}`) 471 | } 472 | 473 | return valueToMeta(event.sender, contextId, new constructor(...args)) 474 | }) 475 | 476 | handleRemoteCommand(IPC_MESSAGES.BROWSER_FUNCTION_CALL, function (event, contextId, id, args) { 477 | args = unwrapArgs(event.sender, event.frameId, contextId, args) 478 | const func = objectsRegistry.get(id) 479 | 480 | if (func == null) { 481 | throwRPCError(`Cannot call function on missing remote object ${id}`) 482 | } 483 | 484 | try { 485 | return valueToMeta(event.sender, contextId, func(...args), true) 486 | } catch (error) { 487 | const err = new Error( 488 | `Could not call remote function '${func.name || "anonymous"}'. Check that the function signature is correct. Underlying error: ${error}\n` + 489 | (error instanceof Error ? `Underlying stack: ${error.stack}\n` : "") 490 | ); 491 | (err as any).cause = error 492 | throw err 493 | } 494 | }) 495 | 496 | handleRemoteCommand(IPC_MESSAGES.BROWSER_MEMBER_CONSTRUCTOR, function (event, contextId, id, method, args) { 497 | args = unwrapArgs(event.sender, event.frameId, contextId, args) 498 | const object = objectsRegistry.get(id) 499 | 500 | if (object == null) { 501 | throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`) 502 | } 503 | 504 | return valueToMeta(event.sender, contextId, new object[method](...args)) 505 | }) 506 | 507 | handleRemoteCommand(IPC_MESSAGES.BROWSER_MEMBER_CALL, function (event, contextId, id, method, args) { 508 | args = unwrapArgs(event.sender, event.frameId, contextId, args) 509 | const object = objectsRegistry.get(id) 510 | 511 | if (object == null) { 512 | throwRPCError(`Cannot call method '${method}' on missing remote object ${id}`) 513 | } 514 | 515 | try { 516 | return valueToMeta(event.sender, contextId, object[method](...args), true) 517 | } catch (error) { 518 | const err = new Error( 519 | `Could not call remote method '${method}'. Check that the method signature is correct. Underlying error: ${error}` + 520 | (error instanceof Error ? `Underlying stack: ${error.stack}\n` : "") 521 | ); 522 | (err as any).cause = error 523 | throw err 524 | } 525 | }) 526 | 527 | handleRemoteCommand(IPC_MESSAGES.BROWSER_MEMBER_SET, function (event, contextId, id, name, args) { 528 | args = unwrapArgs(event.sender, event.frameId, contextId, args) 529 | const obj = objectsRegistry.get(id) 530 | 531 | if (obj == null) { 532 | throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`) 533 | } 534 | 535 | obj[name] = args[0] 536 | return null 537 | }) 538 | 539 | handleRemoteCommand(IPC_MESSAGES.BROWSER_MEMBER_GET, function (event, contextId, id, name) { 540 | const obj = objectsRegistry.get(id) 541 | 542 | if (obj == null) { 543 | throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`) 544 | } 545 | 546 | return valueToMeta(event.sender, contextId, obj[name]) 547 | }) 548 | 549 | handleRemoteCommand(IPC_MESSAGES.BROWSER_DEREFERENCE, function (event, contextId, id) { 550 | objectsRegistry.remove(event.sender, contextId, id) 551 | }) 552 | 553 | handleRemoteCommand(IPC_MESSAGES.BROWSER_CONTEXT_RELEASE, (event, contextId) => { 554 | objectsRegistry.clear(event.sender, contextId) 555 | return null 556 | }) 557 | } 558 | -------------------------------------------------------------------------------- /src/renderer/callbacks-registry.ts: -------------------------------------------------------------------------------- 1 | 2 | export class CallbacksRegistry { 3 | private nextId: number = 0 4 | private callbacks: Record = {} 5 | private callbackIds = new WeakMap(); 6 | private locationInfo = new WeakMap(); 7 | 8 | add (callback: Function): number { 9 | // The callback is already added. 10 | let id = this.callbackIds.get(callback); 11 | if (id != null) return id 12 | 13 | id = this.nextId += 1 14 | this.callbacks[id] = callback 15 | this.callbackIds.set(callback, id); 16 | 17 | // Capture the location of the function and put it in the ID string, 18 | // so that release errors can be tracked down easily. 19 | const regexp = /at (.*)/gi 20 | const stackString = (new Error()).stack 21 | if (!stackString) return id; 22 | 23 | let filenameAndLine: string; 24 | let match 25 | 26 | while ((match = regexp.exec(stackString)) !== null) { 27 | const location = match[1] 28 | if (location.includes('(native)')) continue 29 | if (location.includes('()')) continue 30 | if (location.includes('callbacks-registry.js')) continue 31 | if (location.includes('remote.js')) continue 32 | if (location.includes('@electron/remote/dist')) continue 33 | 34 | const ref = /([^/^)]*)\)?$/gi.exec(location) 35 | if (ref) filenameAndLine = ref![1] 36 | break 37 | } 38 | 39 | this.locationInfo.set(callback, filenameAndLine!); 40 | return id 41 | } 42 | 43 | get (id: number): Function { 44 | return this.callbacks[id] || function () {} 45 | } 46 | 47 | getLocation (callback: Function): string | undefined { 48 | return this.locationInfo.get(callback); 49 | } 50 | 51 | apply (id: number, ...args: any[]): any { 52 | return this.get(id).apply(global, ...args) 53 | } 54 | 55 | remove (id: number): void { 56 | const callback = this.callbacks[id] 57 | if (callback) { 58 | this.callbackIds.delete(callback); 59 | delete this.callbacks[id] 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/renderer/index.ts: -------------------------------------------------------------------------------- 1 | if (process.type === 'browser') 2 | throw new Error(`"@electron/remote" cannot be required in the browser process. Instead require("@electron/remote/main").`) 3 | export * from './remote' 4 | -------------------------------------------------------------------------------- /src/renderer/remote.ts: -------------------------------------------------------------------------------- 1 | import { CallbacksRegistry } from './callbacks-registry' 2 | import { isPromise, isSerializableObject, serialize, deserialize } from '../common/type-utils' 3 | import { MetaTypeFromRenderer, ObjectMember, ObjProtoDescriptor, MetaType } from '../common/types' 4 | import { BrowserWindow, WebContents, ipcRenderer, IpcRendererEvent } from 'electron' 5 | import { browserModuleNames } from '../common/module-names' 6 | import { getElectronBinding } from '../common/get-electron-binding' 7 | import { IPC_MESSAGES } from '../common/ipc-messages'; 8 | 9 | const { Promise } = global 10 | 11 | const callbacksRegistry = new CallbacksRegistry() 12 | const remoteObjectCache = new Map() 13 | const finalizationRegistry = new FinalizationRegistry((id: number) => { 14 | const ref = remoteObjectCache.get(id) 15 | if (ref !== undefined && ref.deref() === undefined) { 16 | remoteObjectCache.delete(id) 17 | ipcRenderer.send(IPC_MESSAGES.BROWSER_DEREFERENCE, contextId, id, 0) 18 | } 19 | }) 20 | 21 | const electronIds = new WeakMap(); 22 | const isReturnValue = new WeakSet(); 23 | 24 | function getCachedRemoteObject (id: number) { 25 | const ref = remoteObjectCache.get(id) 26 | if (ref !== undefined) { 27 | const deref = ref.deref() 28 | if (deref !== undefined) return deref 29 | } 30 | } 31 | function setCachedRemoteObject (id: number, value: any) { 32 | const wr = new WeakRef(value) 33 | remoteObjectCache.set(id, wr) 34 | finalizationRegistry.register(value, id) 35 | return value 36 | } 37 | 38 | function getContextId() { 39 | const v8Util = getElectronBinding('v8_util') 40 | if (v8Util) { 41 | return v8Util.getHiddenValue(global, 'contextId') 42 | } else { 43 | throw new Error('Electron >=v13.0.0-beta.6 required to support sandboxed renderers') 44 | } 45 | } 46 | 47 | // An unique ID that can represent current context. 48 | const contextId = process.contextId || getContextId() 49 | 50 | // Notify the main process when current context is going to be released. 51 | // Note that when the renderer process is destroyed, the message may not be 52 | // sent, we also listen to the "render-view-deleted" event in the main process 53 | // to guard that situation. 54 | process.on('exit', () => { 55 | const command = IPC_MESSAGES.BROWSER_CONTEXT_RELEASE 56 | ipcRenderer.send(command, contextId) 57 | }) 58 | 59 | const IS_REMOTE_PROXY = Symbol('is-remote-proxy') 60 | 61 | // Convert the arguments object into an array of meta data. 62 | function wrapArgs (args: any[], visited = new Set()): MetaTypeFromRenderer[] { 63 | const valueToMeta = (value: any): MetaTypeFromRenderer => { 64 | // Check for circular reference. 65 | if (visited.has(value)) { 66 | return { 67 | type: 'value', 68 | value: null 69 | } 70 | } 71 | 72 | if (value && value.constructor && value.constructor.name === 'NativeImage') { 73 | return { type: 'nativeimage', value: serialize(value) } 74 | } else if (Array.isArray(value)) { 75 | visited.add(value) 76 | const meta: MetaTypeFromRenderer = { 77 | type: 'array', 78 | value: wrapArgs(value, visited) 79 | } 80 | visited.delete(value) 81 | return meta 82 | } else if (value instanceof Buffer) { 83 | return { 84 | type: 'buffer', 85 | value 86 | } 87 | } else if (isSerializableObject(value)) { 88 | return { 89 | type: 'value', 90 | value 91 | } 92 | } else if (typeof value === 'object') { 93 | if (isPromise(value)) { 94 | return { 95 | type: 'promise', 96 | then: valueToMeta(function (onFulfilled: Function, onRejected: Function) { 97 | value.then(onFulfilled, onRejected) 98 | }) 99 | } 100 | } else if (electronIds.has(value)) { 101 | return { 102 | type: 'remote-object', 103 | id: electronIds.get(value)! 104 | } 105 | } 106 | 107 | const meta: MetaTypeFromRenderer = { 108 | type: 'object', 109 | name: value.constructor ? value.constructor.name : '', 110 | members: [] 111 | } 112 | visited.add(value) 113 | for (const prop in value) { // eslint-disable-line guard-for-in 114 | meta.members.push({ 115 | name: prop, 116 | value: valueToMeta(value[prop]) 117 | }) 118 | } 119 | visited.delete(value) 120 | return meta 121 | } else if (typeof value === 'function' && isReturnValue.has(value)) { 122 | return { 123 | type: 'function-with-return-value', 124 | value: valueToMeta(value()) 125 | } 126 | } else if (typeof value === 'function') { 127 | return { 128 | type: 'function', 129 | id: callbacksRegistry.add(value), 130 | location: callbacksRegistry.getLocation(value)!, 131 | length: value.length 132 | } 133 | } else { 134 | return { 135 | type: 'value', 136 | value 137 | } 138 | } 139 | } 140 | return args.map(valueToMeta) 141 | } 142 | 143 | // Populate object's members from descriptors. 144 | // The |ref| will be kept referenced by |members|. 145 | // This matches |getObjectMemebers| in rpc-server. 146 | function setObjectMembers (ref: any, object: any, metaId: number, members: ObjectMember[]) { 147 | if (!Array.isArray(members)) return 148 | 149 | for (const member of members) { 150 | if (Object.prototype.hasOwnProperty.call(object, member.name)) continue 151 | 152 | const descriptor: PropertyDescriptor = { enumerable: member.enumerable } 153 | if (member.type === 'method') { 154 | const remoteMemberFunction = function (this: any, ...args: any[]) { 155 | let command 156 | if (this && this.constructor === remoteMemberFunction) { 157 | command = IPC_MESSAGES.BROWSER_MEMBER_CONSTRUCTOR 158 | } else { 159 | command = IPC_MESSAGES.BROWSER_MEMBER_CALL 160 | } 161 | const ret = ipcRenderer.sendSync(command, contextId, metaId, member.name, wrapArgs(args)) 162 | return metaToValue(ret) 163 | } 164 | 165 | let descriptorFunction = proxyFunctionProperties(remoteMemberFunction, metaId, member.name) 166 | 167 | descriptor.get = () => { 168 | descriptorFunction.ref = ref // The member should reference its object. 169 | return descriptorFunction 170 | } 171 | // Enable monkey-patch the method 172 | descriptor.set = (value) => { 173 | descriptorFunction = value 174 | return value 175 | } 176 | descriptor.configurable = true 177 | } else if (member.type === 'get') { 178 | descriptor.get = () => { 179 | const command = IPC_MESSAGES.BROWSER_MEMBER_GET 180 | const meta = ipcRenderer.sendSync(command, contextId, metaId, member.name) 181 | return metaToValue(meta) 182 | } 183 | 184 | if (member.writable) { 185 | descriptor.set = (value) => { 186 | const args = wrapArgs([value]) 187 | const command = IPC_MESSAGES.BROWSER_MEMBER_SET 188 | const meta = ipcRenderer.sendSync(command, contextId, metaId, member.name, args) 189 | if (meta != null) metaToValue(meta) 190 | return value 191 | } 192 | } 193 | } 194 | 195 | Object.defineProperty(object, member.name, descriptor) 196 | } 197 | } 198 | 199 | // Populate object's prototype from descriptor. 200 | // This matches |getObjectPrototype| in rpc-server. 201 | function setObjectPrototype (ref: any, object: any, metaId: number, descriptor: ObjProtoDescriptor) { 202 | if (descriptor === null) return 203 | const proto = {} 204 | setObjectMembers(ref, proto, metaId, descriptor.members) 205 | setObjectPrototype(ref, proto, metaId, descriptor.proto) 206 | Object.setPrototypeOf(object, proto) 207 | } 208 | 209 | // Wrap function in Proxy for accessing remote properties 210 | function proxyFunctionProperties (remoteMemberFunction: Function, metaId: number, name: string) { 211 | let loaded = false 212 | 213 | // Lazily load function properties 214 | const loadRemoteProperties = () => { 215 | if (loaded) return 216 | loaded = true 217 | const command = IPC_MESSAGES.BROWSER_MEMBER_GET 218 | const meta = ipcRenderer.sendSync(command, contextId, metaId, name) 219 | setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members) 220 | } 221 | 222 | return new Proxy(remoteMemberFunction as any, { 223 | set: (target, property, value) => { 224 | if (property !== 'ref') loadRemoteProperties() 225 | target[property] = value 226 | return true 227 | }, 228 | get: (target, property) => { 229 | if (property === IS_REMOTE_PROXY) return true 230 | if (!Object.prototype.hasOwnProperty.call(target, property)) loadRemoteProperties() 231 | const value = target[property] 232 | if (property === 'toString' && typeof value === 'function') { 233 | return value.bind(target) 234 | } 235 | return value 236 | }, 237 | ownKeys: (target) => { 238 | loadRemoteProperties() 239 | return Object.getOwnPropertyNames(target) 240 | }, 241 | getOwnPropertyDescriptor: (target, property) => { 242 | const descriptor = Object.getOwnPropertyDescriptor(target, property) 243 | if (descriptor) return descriptor 244 | loadRemoteProperties() 245 | return Object.getOwnPropertyDescriptor(target, property) 246 | } 247 | }) 248 | } 249 | 250 | // Convert meta data from browser into real value. 251 | function metaToValue (meta: MetaType): any { 252 | if (!meta) return {} 253 | if (meta.type === 'value') { 254 | return meta.value 255 | } else if (meta.type === 'array') { 256 | return meta.members.map((member) => metaToValue(member)) 257 | } else if (meta.type === 'nativeimage') { 258 | return deserialize(meta.value) 259 | } else if (meta.type === 'buffer') { 260 | return Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength) 261 | } else if (meta.type === 'promise') { 262 | return Promise.resolve({ then: metaToValue(meta.then) }) 263 | } else if (meta.type === 'error') { 264 | return metaToError(meta) 265 | } else if (meta.type === 'exception') { 266 | if (meta.value.type === 'error') { throw metaToError(meta.value); } else { throw new Error(`Unexpected value type in exception: ${meta.value.type}`); } 267 | } else { 268 | let ret 269 | if ('id' in meta) { 270 | const cached = getCachedRemoteObject(meta.id) 271 | if (cached !== undefined) { return cached; } 272 | } 273 | 274 | // A shadow class to represent the remote function object. 275 | if (meta.type === 'function') { 276 | const remoteFunction = function (this: any, ...args: any[]) { 277 | let command 278 | if (this && this.constructor === remoteFunction) { 279 | command = IPC_MESSAGES.BROWSER_CONSTRUCTOR 280 | } else { 281 | command = IPC_MESSAGES.BROWSER_FUNCTION_CALL 282 | } 283 | const obj = ipcRenderer.sendSync(command, contextId, meta.id, wrapArgs(args)) 284 | return metaToValue(obj) 285 | } 286 | ret = remoteFunction 287 | } else { 288 | ret = {} 289 | } 290 | 291 | setObjectMembers(ret, ret, meta.id, meta.members) 292 | setObjectPrototype(ret, ret, meta.id, meta.proto) 293 | if (ret.constructor && (ret.constructor as any)[IS_REMOTE_PROXY]) { 294 | Object.defineProperty(ret.constructor, 'name', { value: meta.name }) 295 | } 296 | 297 | // Track delegate obj's lifetime & tell browser to clean up when object is GCed. 298 | electronIds.set(ret, meta.id); 299 | setCachedRemoteObject(meta.id, ret) 300 | return ret 301 | } 302 | } 303 | 304 | function metaToError (meta: { type: 'error', value: any, members: ObjectMember[] }) { 305 | const obj = meta.value 306 | for (const { name, value } of meta.members) { 307 | obj[name] = metaToValue(value) 308 | } 309 | return obj 310 | } 311 | 312 | // Backwards compatibility interface for Electron < v28 313 | interface IpcRendererEventWithSenderId extends IpcRendererEvent { 314 | senderId: number 315 | } 316 | 317 | function hasSenderId(input: any): input is IpcRendererEventWithSenderId { 318 | return typeof input.senderId === "number" 319 | } 320 | 321 | function handleMessage (channel: string, handler: Function) { 322 | ipcRenderer.on(channel, (event, passedContextId, id, ...args) => { 323 | if (hasSenderId(event)) { 324 | if (event.senderId !== 0 && event.senderId !== undefined) { 325 | console.error(`Message ${channel} sent by unexpected WebContents (${event.senderId})`); 326 | return; 327 | } 328 | } 329 | 330 | if (passedContextId === contextId) { 331 | handler(id, ...args) 332 | } else { 333 | // Message sent to an un-exist context, notify the error to main process. 334 | ipcRenderer.send(IPC_MESSAGES.BROWSER_WRONG_CONTEXT_ERROR, contextId, passedContextId, id) 335 | } 336 | }) 337 | } 338 | 339 | const enableStacks = process.argv.includes('--enable-api-filtering-logging') 340 | 341 | function getCurrentStack (): string | undefined { 342 | const target = { stack: undefined as string | undefined } 343 | if (enableStacks) { 344 | Error.captureStackTrace(target, getCurrentStack) 345 | } 346 | return target.stack 347 | } 348 | 349 | // Browser calls a callback in renderer. 350 | handleMessage(IPC_MESSAGES.RENDERER_CALLBACK, (id: number, args: any) => { 351 | callbacksRegistry.apply(id, metaToValue(args)) 352 | }) 353 | 354 | // A callback in browser is released. 355 | handleMessage(IPC_MESSAGES.RENDERER_RELEASE_CALLBACK, (id: number) => { 356 | callbacksRegistry.remove(id) 357 | }) 358 | 359 | exports.require = (module: string) => { 360 | const command = IPC_MESSAGES.BROWSER_REQUIRE 361 | const meta = ipcRenderer.sendSync(command, contextId, module, getCurrentStack()) 362 | return metaToValue(meta) 363 | } 364 | 365 | // Alias to remote.require('electron').xxx. 366 | export function getBuiltin (module: string) { 367 | const command = IPC_MESSAGES.BROWSER_GET_BUILTIN 368 | const meta = ipcRenderer.sendSync(command, contextId, module, getCurrentStack()) 369 | return metaToValue(meta) 370 | } 371 | 372 | export function getCurrentWindow (): BrowserWindow { 373 | const command = IPC_MESSAGES.BROWSER_GET_CURRENT_WINDOW 374 | const meta = ipcRenderer.sendSync(command, contextId, getCurrentStack()) 375 | return metaToValue(meta) 376 | } 377 | 378 | // Get current WebContents object. 379 | export function getCurrentWebContents (): WebContents { 380 | const command = IPC_MESSAGES.BROWSER_GET_CURRENT_WEB_CONTENTS 381 | const meta = ipcRenderer.sendSync(command, contextId, getCurrentStack()) 382 | return metaToValue(meta) 383 | } 384 | 385 | // Get a global object in browser. 386 | export function getGlobal (name: string): T { 387 | const command = IPC_MESSAGES.BROWSER_GET_GLOBAL 388 | const meta = ipcRenderer.sendSync(command, contextId, name, getCurrentStack()) 389 | return metaToValue(meta) 390 | } 391 | 392 | // Get the process object in browser. 393 | Object.defineProperty(exports, 'process', { 394 | enumerable: true, 395 | get: () => exports.getGlobal('process') 396 | }) 397 | 398 | // Create a function that will return the specified value when called in browser. 399 | export function createFunctionWithReturnValue (returnValue: T): () => T { 400 | const func = () => returnValue 401 | isReturnValue.add(func); 402 | return func 403 | } 404 | 405 | const addBuiltinProperty = (name: string) => { 406 | Object.defineProperty(exports, name, { 407 | enumerable: true, 408 | get: () => exports.getBuiltin(name) 409 | }) 410 | } 411 | 412 | browserModuleNames 413 | .forEach(addBuiltinProperty) 414 | -------------------------------------------------------------------------------- /test/all.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { enable, initialize } from '../src/main' 3 | import { expect } from 'chai' 4 | import * as path from 'path' 5 | import { ipcMain, BrowserWindow, protocol, nativeImage, NativeImage, app, WebContents } from 'electron' 6 | 7 | 8 | import { closeAllWindows } from './window-helpers' 9 | import { emittedOnce } from './events-helpers' 10 | import { serialize, deserialize } from '../src/common/type-utils' 11 | 12 | before(() => { 13 | initialize() 14 | }) 15 | 16 | 17 | const expectPathsEqual = (path1: string, path2: string) => { 18 | if (process.platform === 'win32') { 19 | path1 = path1.toLowerCase() 20 | path2 = path2.toLowerCase() 21 | } 22 | expect(path1).to.equal(path2) 23 | } 24 | 25 | function makeRemotely (windowGetter: () => BrowserWindow) { 26 | async function remotely (script: Function, ...args: any[]) { 27 | // executeJavaScript obfuscates the error if the script throws, so catch any 28 | // errors manually. 29 | const assembledScript = `(async function() { 30 | try { 31 | return { result: await Promise.resolve((${script})(...${JSON.stringify(args)})) } 32 | } catch (e) { 33 | return { error: e.message, stack: e.stack } 34 | } 35 | })()` 36 | const { result, error, stack } = await windowGetter().webContents.executeJavaScript(assembledScript) 37 | if (error) { 38 | const e = new Error(error) 39 | e.stack = stack 40 | throw e 41 | } 42 | return result 43 | } 44 | remotely.it = (...vars: any[]) => (name: string, fn: Function) => { 45 | it(name, async () => { 46 | await remotely(fn, ...vars) 47 | }) 48 | } 49 | return remotely 50 | } 51 | 52 | function makeWindow () { 53 | let w: BrowserWindow 54 | before(async () => { 55 | w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } }) 56 | enable(w.webContents) 57 | await w.loadURL('about:blank') 58 | await w.webContents.executeJavaScript(`{ 59 | const chai_1 = window.chai_1 = require('chai') 60 | chai_1.use(require('chai-as-promised')) 61 | chai_1.use(require('dirty-chai')) 62 | null 63 | }`) 64 | }) 65 | after(closeAllWindows) 66 | return () => w 67 | } 68 | 69 | function makeEachWindow () { 70 | let w: BrowserWindow 71 | beforeEach(async () => { 72 | w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } }) 73 | enable(w.webContents) 74 | await w.loadURL('about:blank') 75 | await w.webContents.executeJavaScript(`{ 76 | const chai_1 = window.chai_1 = require('chai') 77 | chai_1.use(require('chai-as-promised')) 78 | chai_1.use(require('dirty-chai')) 79 | null 80 | }`) 81 | }) 82 | afterEach(closeAllWindows) 83 | return () => w 84 | } 85 | 86 | describe('typeUtils serialization/deserialization', () => { 87 | it('serializes and deserializes an empty NativeImage', () => { 88 | const image = nativeImage.createEmpty() 89 | const serializedImage = serialize(image) 90 | const empty = deserialize(serializedImage) 91 | 92 | expect(empty.isEmpty()).to.be.true() 93 | expect(empty.getAspectRatio()).to.equal(1) 94 | expect(empty.toDataURL()).to.equal('data:image/png;base64,') 95 | expect(empty.toDataURL({ scaleFactor: 2.0 })).to.equal('data:image/png;base64,') 96 | expect(empty.getSize()).to.deep.equal({ width: 0, height: 0 }) 97 | expect(empty.getBitmap()).to.be.empty() 98 | expect(empty.getBitmap({ scaleFactor: 2.0 })).to.be.empty() 99 | expect(empty.toBitmap()).to.be.empty() 100 | expect(empty.toBitmap({ scaleFactor: 2.0 })).to.be.empty() 101 | expect(empty.toJPEG(100)).to.be.empty() 102 | expect(empty.toPNG()).to.be.empty() 103 | expect(empty.toPNG({ scaleFactor: 2.0 })).to.be.empty() 104 | }) 105 | 106 | it('serializes and deserializes a non-empty NativeImage', () => { 107 | const dataURL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAFklEQVQYlWP8//8/AwMDEwMDAwMDAwAkBgMBBMzldwAAAABJRU5ErkJggg==' 108 | const image = nativeImage.createFromDataURL(dataURL) 109 | const serializedImage = serialize(image) 110 | const nonEmpty = deserialize(serializedImage) 111 | 112 | expect(nonEmpty.isEmpty()).to.be.false() 113 | expect(nonEmpty.getAspectRatio()).to.equal(1) 114 | expect(nonEmpty.toDataURL()).to.not.be.empty() 115 | expect(nonEmpty.toBitmap({ scaleFactor: 1.0 })).to.deep.equal(image.toBitmap({ scaleFactor: 1.0 })) 116 | expect(nonEmpty.getSize()).to.deep.equal({ width: 2, height: 2 }) 117 | expect(nonEmpty.getBitmap()).to.not.be.empty() 118 | expect(nonEmpty.toPNG()).to.not.be.empty() 119 | }) 120 | 121 | it('serializes and deserializes a non-empty NativeImage with multiple representations', () => { 122 | const image = nativeImage.createEmpty() 123 | 124 | const dataURL1 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYlWNgAAIAAAUAAdafFs0AAAAASUVORK5CYII=' 125 | image.addRepresentation({ scaleFactor: 1.0, dataURL: dataURL1 }) 126 | 127 | const dataURL2 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAFklEQVQYlWP8//8/AwMDEwMDAwMDAwAkBgMBBMzldwAAAABJRU5ErkJggg==' 128 | image.addRepresentation({ scaleFactor: 2.0, dataURL: dataURL2 }) 129 | 130 | const serializedImage = serialize(image) 131 | const nonEmpty = deserialize(serializedImage) 132 | 133 | expect(nonEmpty.isEmpty()).to.be.false() 134 | expect(nonEmpty.getAspectRatio()).to.equal(1) 135 | expect(nonEmpty.getSize()).to.deep.equal({ width: 1, height: 1 }) 136 | expect(nonEmpty.getBitmap()).to.not.be.empty() 137 | expect(nonEmpty.getBitmap({ scaleFactor: 1.0 })).to.not.be.empty() 138 | expect(nonEmpty.getBitmap({ scaleFactor: 2.0 })).to.not.be.empty() 139 | expect(nonEmpty.toBitmap()).to.not.be.empty() 140 | expect(nonEmpty.toBitmap({ scaleFactor: 1.0 })).to.deep.equal(image.toBitmap({ scaleFactor: 1.0 })) 141 | expect(nonEmpty.toBitmap({ scaleFactor: 2.0 })).to.deep.equal(image.toBitmap({ scaleFactor: 2.0 })) 142 | expect(nonEmpty.toPNG()).to.not.be.empty() 143 | expect(nonEmpty.toPNG({ scaleFactor: 1.0 })).to.not.be.empty() 144 | expect(nonEmpty.toPNG({ scaleFactor: 2.0 })).to.not.be.empty() 145 | expect(nonEmpty.toDataURL()).to.not.be.empty() 146 | }) 147 | 148 | it('serializes and deserializes an Array', () => { 149 | const array = [1, 2, 3, 4, 5] 150 | const serialized = serialize(array) 151 | const deserialized = deserialize(serialized) 152 | 153 | expect(deserialized).to.deep.equal(array) 154 | }) 155 | 156 | it('serializes and deserializes a Buffer', () => { 157 | const buffer = Buffer.from('hello world!', 'utf-8') 158 | const serialized = serialize(buffer) 159 | const deserialized = deserialize(serialized) 160 | 161 | expect(deserialized).to.deep.equal(buffer) 162 | }) 163 | 164 | it('serializes and deserializes a Boolean', () => { 165 | const bool = true 166 | const serialized = serialize(bool) 167 | const deserialized = deserialize(serialized) 168 | 169 | expect(deserialized).to.equal(bool) 170 | }) 171 | 172 | it('serializes and deserializes a Date', () => { 173 | const date = new Date() 174 | const serialized = serialize(date) 175 | const deserialized = deserialize(serialized) 176 | 177 | expect(deserialized).to.equal(date) 178 | }) 179 | 180 | it('serializes and deserializes a Number', () => { 181 | const number = 42 182 | const serialized = serialize(number) 183 | const deserialized = deserialize(serialized) 184 | 185 | expect(deserialized).to.equal(number) 186 | }) 187 | 188 | it('serializes and deserializes a Regexp', () => { 189 | const regex = new RegExp('ab+c') 190 | const serialized = serialize(regex) 191 | const deserialized = deserialize(serialized) 192 | 193 | expect(deserialized).to.equal(regex) 194 | }) 195 | 196 | it('serializes and deserializes a String', () => { 197 | const str = 'hello world' 198 | const serialized = serialize(str) 199 | const deserialized = deserialize(serialized) 200 | 201 | expect(deserialized).to.equal(str) 202 | }) 203 | 204 | it('serializes and deserializes an Error', () => { 205 | const err = new Error('oh crap') 206 | const serialized = serialize(err) 207 | const deserialized = deserialize(serialized) 208 | 209 | expect(deserialized).to.equal(err) 210 | }) 211 | 212 | it('serializes and deserializes a simple Object', () => { 213 | const obj = { hello: 'world', 'answer-to-everything': 42 } 214 | const serialized = serialize(obj) 215 | const deserialized = deserialize(serialized) 216 | 217 | expect(deserialized).to.deep.equal(obj) 218 | }) 219 | }) 220 | 221 | describe('remote module', () => { 222 | const fixtures = path.join(__dirname, 'fixtures') 223 | 224 | describe('filtering', () => { 225 | const w = makeWindow() 226 | const remotely = makeRemotely(w) 227 | 228 | const emitters = [ 229 | {name: 'webContents', f: () => w().webContents}, 230 | {name: 'app', f: () => app} 231 | ] 232 | for (const {name, f} of emitters) { 233 | const emitter = f as any as () => any 234 | const returnFirstArg = name === 'app' ? (e: any, _: any, a: any) => e.returnValue = a : (e: any, a: any) => e.returnValue = a 235 | const returnConstant = (k: any) => (e: any) => e.returnValue = k 236 | const preventDefault = (e: any) => e.preventDefault() 237 | describe(name, () => { 238 | describe('remote.getGlobal', () => { 239 | it('can return custom values', async () => { 240 | emitter().once('remote-get-global' as any, returnFirstArg) 241 | expect(await remotely(() => require('./renderer').getGlobal('test'))).to.equal('test') 242 | }) 243 | 244 | it('throws when no returnValue set', async () => { 245 | emitter().once('remote-get-global' as any, preventDefault) 246 | await expect(remotely(() => require('./renderer').getGlobal('test'))).to.eventually.be.rejected(`Blocked remote.getGlobal('test')`) 247 | }) 248 | }) 249 | 250 | describe('remote.getBuiltin', () => { 251 | it('can return custom values', async () => { 252 | emitter().once('remote-get-builtin', returnFirstArg) 253 | expect(await remotely(() => (require('./renderer') as any).getBuiltin('test'))).to.equal('test') 254 | }) 255 | 256 | it('throws when no returnValue set', async () => { 257 | emitter().once('remote-get-builtin', preventDefault) 258 | await expect(remotely(() => (require('./renderer') as any).getBuiltin('test'))).to.eventually.be.rejected(`Blocked remote.getGlobal('test')`) 259 | }) 260 | }) 261 | 262 | describe('remote.require', () => { 263 | it('can return custom values', async () => { 264 | emitter().once('remote-require', returnFirstArg) 265 | expect(await remotely(() => require('./renderer').require('test'))).to.equal('test') 266 | }) 267 | 268 | it('throws when no returnValue set', async () => { 269 | emitter().once('remote-require', preventDefault) 270 | await expect(remotely(() => require('./renderer').require('test'))).to.eventually.be.rejected(`Blocked remote.require('test')`) 271 | }) 272 | }) 273 | 274 | describe('remote.getCurrentWindow', () => { 275 | it('can return custom value', async () => { 276 | emitter().once('remote-get-current-window', returnConstant('some window')) 277 | expect(await remotely(() => require('./renderer').getCurrentWindow())).to.equal('some window') 278 | }) 279 | 280 | it('throws when no returnValue set', async () => { 281 | emitter().once('remote-get-current-window', preventDefault) 282 | await expect(remotely(() => require('./renderer').getCurrentWindow())).to.eventually.be.rejected(`Blocked remote.getCurrentWindow()`) 283 | }) 284 | }) 285 | 286 | describe('remote.getCurrentWebContents', () => { 287 | it('can return custom value', async () => { 288 | emitter().once('remote-get-current-web-contents', returnConstant('some web contents')) 289 | expect(await remotely(() => require('./renderer').getCurrentWebContents())).to.equal('some web contents') 290 | }) 291 | 292 | it('throws when no returnValue set', async () => { 293 | emitter().once('remote-get-current-web-contents', preventDefault) 294 | await expect(remotely(() => require('./renderer').getCurrentWebContents())).to.eventually.be.rejected(`Blocked remote.getCurrentWebContents()`) 295 | }) 296 | }) 297 | }) 298 | } 299 | }) 300 | 301 | describe('remote references', () => { 302 | const w = makeEachWindow() 303 | it('render-view-deleted is sent when page is destroyed', (done) => { 304 | w().webContents.once('render-view-deleted' as any, () => { 305 | done() 306 | }) 307 | w().destroy() 308 | }) 309 | 310 | // The ELECTRON_BROWSER_CONTEXT_RELEASE message relies on this to work. 311 | it('message can be sent on exit when page is being navigated', async () => { 312 | after(() => { ipcMain.removeAllListeners('SENT_ON_EXIT') }) 313 | w().webContents.once('did-finish-load', () => { 314 | w().webContents.loadURL('about:blank') 315 | }) 316 | w().loadFile(path.join(fixtures, 'send-on-exit.html')) 317 | await emittedOnce(ipcMain, 'SENT_ON_EXIT') 318 | }) 319 | }) 320 | 321 | describe('remote function in renderer', () => { 322 | afterEach(() => { 323 | ipcMain.removeAllListeners('done') 324 | }) 325 | afterEach(closeAllWindows) 326 | 327 | it('works when created in preload script', async () => { 328 | const preload = path.join(fixtures, 'preload-remote-function.js') 329 | const w = new BrowserWindow({ 330 | show: false, 331 | webPreferences: { 332 | preload, 333 | sandbox: false 334 | } 335 | }) 336 | enable(w.webContents) 337 | w.loadURL('about:blank') 338 | await emittedOnce(ipcMain, 'done') 339 | }) 340 | }) 341 | 342 | describe('remote objects registry', () => { 343 | it('does not dereference until the render view is deleted (regression)', (done) => { 344 | const w = new BrowserWindow({ 345 | show: false, 346 | webPreferences: { 347 | nodeIntegration: true, 348 | contextIsolation: false 349 | } 350 | }) 351 | enable(w.webContents) 352 | 353 | ipcMain.once('error-message', (event, message) => { 354 | expect(message).to.match(/^Cannot call method 'getURL' on missing remote object/) 355 | done() 356 | }) 357 | 358 | w.loadFile(path.join(fixtures, 'render-view-deleted.html')) 359 | }) 360 | }) 361 | 362 | describe('nativeImage serialization', () => { 363 | const w = makeWindow() 364 | const remotely = makeRemotely(w) 365 | 366 | it('can serialize an empty nativeImage from renderer to main', async () => { 367 | const getImageEmpty = (img: NativeImage) => img.isEmpty() 368 | 369 | w().webContents.once('remote-get-global' as any, (event: any) => { 370 | event.returnValue = getImageEmpty 371 | }) 372 | 373 | await expect(remotely(() => { 374 | const emptyImage = require('electron').nativeImage.createEmpty() 375 | return require('./renderer').getGlobal('someFunction')(emptyImage) 376 | })).to.eventually.be.true() 377 | }) 378 | 379 | it('can serialize an empty nativeImage from main to renderer', async () => { 380 | w().webContents.once('remote-get-global' as any, (event: any) => { 381 | const emptyImage = require('electron').nativeImage.createEmpty() 382 | event.returnValue = emptyImage 383 | }) 384 | 385 | await expect(remotely(() => { 386 | const image = require('./renderer').getGlobal('someFunction') 387 | return image.isEmpty() 388 | })).to.eventually.be.true() 389 | }) 390 | 391 | it('can serialize a non-empty nativeImage from renderer to main', async () => { 392 | const getImageSize = (img: NativeImage) => img.getSize() 393 | 394 | w().webContents.once('remote-get-global' as any, (event: any) => { 395 | event.returnValue = getImageSize 396 | }) 397 | 398 | await expect(remotely(() => { 399 | const { nativeImage } = require('electron') 400 | const remote = require('./renderer') 401 | const nonEmptyImage = nativeImage.createFromDataURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAFklEQVQYlWP8//8/AwMDEwMDAwMDAwAkBgMBBMzldwAAAABJRU5ErkJggg==') 402 | return remote.getGlobal('someFunction')(nonEmptyImage) 403 | })).to.eventually.deep.equal({ width: 2, height: 2 }) 404 | }) 405 | 406 | it('can serialize a non-empty nativeImage from main to renderer', async () => { 407 | w().webContents.once('remote-get-global' as any, (event: any) => { 408 | const nonEmptyImage = nativeImage.createFromDataURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAFklEQVQYlWP8//8/AwMDEwMDAwMDAwAkBgMBBMzldwAAAABJRU5ErkJggg==') 409 | event.returnValue = nonEmptyImage 410 | }) 411 | 412 | await expect(remotely(() => { 413 | const image = require('./renderer').getGlobal('someFunction') 414 | return image.getSize() 415 | })).to.eventually.deep.equal({ width: 2, height: 2 }) 416 | }) 417 | 418 | it('can properly create a menu with an nativeImage icon in the renderer', async () => { 419 | await expect(remotely(() => { 420 | const { nativeImage } = require('electron') 421 | const remote = require('./renderer') 422 | remote.Menu.buildFromTemplate([ 423 | { 424 | label: 'hello', 425 | icon: nativeImage.createEmpty() 426 | } 427 | ]) 428 | })).to.be.fulfilled() 429 | }) 430 | }) 431 | 432 | describe('remote listeners', () => { 433 | afterEach(closeAllWindows) 434 | 435 | it('detaches listeners subscribed to destroyed renderers, and shows a warning', async () => { 436 | const w = new BrowserWindow({ 437 | show: false, 438 | webPreferences: { 439 | nodeIntegration: true, 440 | contextIsolation: false 441 | } 442 | }) 443 | enable(w.webContents) 444 | await w.loadFile(path.join(fixtures, 'remote-event-handler.html')) 445 | w.webContents.reload() 446 | await emittedOnce(w.webContents, 'did-finish-load') 447 | 448 | const expectedMessage = [ 449 | 'Attempting to call a function in a renderer window that has been closed or released.', 450 | 'Function provided here: remote-event-handler.html:11:33', 451 | 'Remote event names: remote-handler, other-remote-handler' 452 | ].join('\n') 453 | 454 | expect(w.webContents.listenerCount('remote-handler')).to.equal(2) 455 | let warnMessage: string | null = null 456 | const originalWarn = console.warn 457 | let warned: Function 458 | const warnPromise = new Promise(resolve => { 459 | warned = resolve 460 | }) 461 | try { 462 | console.warn = (message: string) => { 463 | warnMessage = message 464 | warned() 465 | } 466 | w.webContents.emit('remote-handler', { sender: w.webContents }) 467 | await warnPromise 468 | } finally { 469 | console.warn = originalWarn 470 | } 471 | expect(w.webContents.listenerCount('remote-handler')).to.equal(1) 472 | expect(warnMessage).to.equal(expectedMessage) 473 | }) 474 | }) 475 | 476 | describe('remote.require', () => { 477 | const w = makeWindow() 478 | const remotely = makeRemotely(w) 479 | 480 | remotely.it()('should returns same object for the same module', () => { 481 | const remote = require('./renderer') 482 | const a = remote.require('electron') 483 | const b = remote.require('electron') 484 | expect(a).to.equal(b) 485 | }) 486 | 487 | remotely.it(path.join(fixtures, 'id.js'))('should work when object contains id property', (module: string) => { 488 | const { id } = require('./renderer').require(module) 489 | expect(id).to.equal(1127) 490 | }) 491 | 492 | remotely.it(path.join(fixtures, 'no-prototype.js'))('should work when object has no prototype', (module: string) => { 493 | const a = require('./renderer').require(module) 494 | expect(a.foo.bar).to.equal('baz') 495 | expect(a.foo.baz).to.equal(false) 496 | expect(a.bar).to.equal(1234) 497 | expect(a.getConstructorName(Object.create(null))).to.equal('') 498 | expect(a.getConstructorName(new (class {})())).to.equal('') 499 | }) 500 | 501 | /* TODO: Electron 28 removed process.mainModule, so we need to find an alternative way to test that the remotely 502 | required module in the renderer uses the same main module as the main process. In src/main/server.ts:388 we do so 503 | by looping over the module parents until getting the top most one and using their require, but that require 504 | doesn't send along the module paths, so we can't use it here. */ 505 | 506 | it.skip('should search module from the user app', async () => { 507 | expectPathsEqual( 508 | path.normalize(await remotely(() => require('./renderer').process.mainModule!.filename)), 509 | path.resolve(__dirname, 'index.js') 510 | ) 511 | /* 512 | expectPathsEqual( 513 | path.normalize(await remotely(() => require('./renderer').process.mainModule!.paths[0])), 514 | path.resolve(__dirname, 'node_modules') 515 | ) 516 | */ 517 | }) 518 | 519 | remotely.it(fixtures)('should work with function properties', (fixtures: string) => { 520 | const path = require('path') 521 | 522 | { 523 | const a = require('./renderer').require(path.join(fixtures, 'export-function-with-properties.js')) 524 | expect(typeof a).to.equal('function') 525 | expect(a.bar).to.equal('baz') 526 | } 527 | 528 | { 529 | const a = require('./renderer').require(path.join(fixtures, 'function-with-properties.js')) 530 | expect(typeof a).to.equal('object') 531 | expect(a.foo()).to.equal('hello') 532 | expect(a.foo.bar).to.equal('baz') 533 | expect(a.foo.nested.prop).to.equal('yes') 534 | expect(a.foo.method1()).to.equal('world') 535 | expect(a.foo.method1.prop1()).to.equal(123) 536 | } 537 | 538 | { 539 | const a = require('./renderer').require(path.join(fixtures, 'function-with-missing-properties.js')).setup() 540 | expect(a.bar()).to.equal(true) 541 | expect(a.bar.baz).to.be.undefined() 542 | } 543 | }) 544 | 545 | remotely.it(fixtures)('should work with static class members', (fixtures: string) => { 546 | const path = require('path') 547 | const a = require('./renderer').require(path.join(fixtures, 'remote-static.js')) 548 | expect(typeof a.Foo).to.equal('function') 549 | expect(a.Foo.foo()).to.equal(3) 550 | expect(a.Foo.bar).to.equal('baz') 551 | expect(new a.Foo().baz()).to.equal(123) 552 | }) 553 | 554 | remotely.it(fixtures)('includes the length of functions specified as arguments', (fixtures: string) => { 555 | const path = require('path') 556 | const a = require('./renderer').require(path.join(fixtures, 'function-with-args.js')) 557 | /* eslint-disable @typescript-eslint/no-unused-vars */ 558 | expect(a((a: any, b: any, c: any) => {})).to.equal(3) 559 | expect(a((a: any) => {})).to.equal(1) 560 | expect(a((...args: any[]) => {})).to.equal(0) 561 | /* eslint-enable @typescript-eslint/no-unused-vars */ 562 | }) 563 | 564 | remotely.it(fixtures)('handles circular references in arrays and objects', (fixtures: string) => { 565 | const path = require('path') 566 | const a = require('./renderer').require(path.join(fixtures, 'circular.js')) 567 | 568 | let arrayA: any[] = ['foo'] 569 | const arrayB = [arrayA, 'bar'] 570 | arrayA.push(arrayB) 571 | expect(a.returnArgs(arrayA, arrayB)).to.deep.equal([ 572 | ['foo', [null, 'bar']], 573 | [['foo', null], 'bar'] 574 | ]) 575 | 576 | let objectA: any = { foo: 'bar' } 577 | const objectB = { baz: objectA } 578 | objectA.objectB = objectB 579 | expect(a.returnArgs(objectA, objectB)).to.deep.equal([ 580 | { foo: 'bar', objectB: { baz: null } }, 581 | { baz: { foo: 'bar', objectB: null } } 582 | ]) 583 | 584 | arrayA = [1, 2, 3] 585 | expect(a.returnArgs({ foo: arrayA }, { bar: arrayA })).to.deep.equal([ 586 | { foo: [1, 2, 3] }, 587 | { bar: [1, 2, 3] } 588 | ]) 589 | 590 | objectA = { foo: 'bar' } 591 | expect(a.returnArgs({ foo: objectA }, { bar: objectA })).to.deep.equal([ 592 | { foo: { foo: 'bar' } }, 593 | { bar: { foo: 'bar' } } 594 | ]) 595 | 596 | arrayA = [] 597 | arrayA.push(arrayA) 598 | expect(a.returnArgs(arrayA)).to.deep.equal([ 599 | [null] 600 | ]) 601 | 602 | objectA = {} 603 | objectA.foo = objectA 604 | objectA.bar = 'baz' 605 | expect(a.returnArgs(objectA)).to.deep.equal([ 606 | { foo: null, bar: 'baz' } 607 | ]) 608 | 609 | objectA = {} 610 | objectA.foo = { bar: objectA } 611 | objectA.bar = 'baz' 612 | expect(a.returnArgs(objectA)).to.deep.equal([ 613 | { foo: { bar: null }, bar: 'baz' } 614 | ]) 615 | }) 616 | }) 617 | 618 | describe('remote.createFunctionWithReturnValue', () => { 619 | const remotely = makeRemotely(makeWindow()) 620 | 621 | remotely.it(fixtures)('should be called in browser synchronously', async (fixtures: string) => { 622 | const remote = require('./renderer') 623 | const path = require('path') 624 | const buf = Buffer.from('test') 625 | const call = remote.require(path.join(fixtures, 'call.js')) 626 | const result = call.call((remote as any).createFunctionWithReturnValue(buf)) 627 | expect(result).to.be.an.instanceOf(Uint8Array) 628 | }) 629 | }) 630 | 631 | describe('remote modules', () => { 632 | const remotely = makeRemotely(makeWindow()) 633 | 634 | const mainModules = Object.keys(require('electron')) 635 | remotely.it(mainModules)('includes browser process modules as properties', (mainModules: string[]) => { 636 | const remote = require('./renderer') 637 | const remoteModules = mainModules.filter(name => (remote as any)[name]) 638 | expect(remoteModules).to.be.deep.equal(mainModules) 639 | }) 640 | 641 | remotely.it(fixtures)('returns toString() of original function via toString()', (fixtures: string) => { 642 | const path = require('path') 643 | const { readText } = require('./renderer').clipboard 644 | expect(readText.toString().startsWith('function')).to.be.true() 645 | 646 | const { functionWithToStringProperty } = require('./renderer').require(path.join(fixtures, 'to-string-non-function.js')) 647 | expect(functionWithToStringProperty.toString).to.equal('hello') 648 | }) 649 | 650 | const protocolKeys = Object.getOwnPropertyNames(protocol); 651 | remotely.it(protocolKeys)('remote.protocol returns all keys', (protocolKeys: [string]) => { 652 | const protocol = require('./renderer').protocol; 653 | const remoteKeys = Object.getOwnPropertyNames(protocol); 654 | expect(remoteKeys).to.deep.equal(protocolKeys); 655 | for (const key of remoteKeys) { 656 | expect(typeof (protocol as any)[key]).to.equal('function'); 657 | } 658 | }); 659 | }) 660 | 661 | describe('remote object in renderer', () => { 662 | const win = makeWindow() 663 | const remotely = makeRemotely(win) 664 | 665 | remotely.it(fixtures)('can change its properties', (fixtures: string) => { 666 | const module = require('path').join(fixtures, 'property.js') 667 | const property = require('./renderer').require(module) 668 | expect(property.property).to.equal(1127) 669 | property.property = null 670 | expect(property.property).to.equal(null) 671 | property.property = undefined 672 | expect(property.property).to.equal(undefined) 673 | property.property = 1007 674 | expect(property.property).to.equal(1007) 675 | 676 | expect(property.getFunctionProperty()).to.equal('foo-browser') 677 | property.func.property = 'bar' 678 | expect(property.getFunctionProperty()).to.equal('bar-browser') 679 | property.func.property = 'foo' // revert back 680 | 681 | const property2 = require('./renderer').require(module) 682 | expect(property2.property).to.equal(1007) 683 | 684 | property.property = 1127 // revert back 685 | }) 686 | 687 | remotely.it(fixtures)('rethrows errors getting/setting properties', (fixtures: string) => { 688 | const foo = require('./renderer').require(require('path').join(fixtures, 'error-properties.js')) 689 | 690 | expect(() => { 691 | // eslint-disable-next-line 692 | foo.bar 693 | }).to.throw('getting error') 694 | 695 | expect(() => { 696 | foo.bar = 'test' 697 | }).to.throw('setting error') 698 | }) 699 | 700 | remotely.it(fixtures)('can set a remote property with a remote object', (fixtures: string) => { 701 | const remote = require('./renderer') 702 | const foo = remote.require(require('path').join(fixtures, 'remote-object-set.js')) 703 | foo.bar = remote.getCurrentWindow() 704 | }) 705 | 706 | remotely.it(fixtures)('can construct an object from its member', (fixtures: string) => { 707 | const call = require('./renderer').require(require('path').join(fixtures, 'call.js')) 708 | const obj = new call.constructor() 709 | expect(obj.test).to.equal('test') 710 | }) 711 | 712 | remotely.it(fixtures)('can reassign and delete its member functions', (fixtures: string) => { 713 | const remoteFunctions = require('./renderer').require(require('path').join(fixtures, 'function.js')) 714 | expect(remoteFunctions.aFunction()).to.equal(1127) 715 | 716 | remoteFunctions.aFunction = () => { return 1234 } 717 | expect(remoteFunctions.aFunction()).to.equal(1234) 718 | 719 | expect(delete remoteFunctions.aFunction).to.equal(true) 720 | }) 721 | 722 | remotely.it('is referenced by its members', () => { 723 | const stringify = require('./renderer').getGlobal('JSON').stringify 724 | global.gc() 725 | stringify({}) 726 | }) 727 | 728 | it('can handle objects without constructors', async () => { 729 | win().webContents.once('remote-get-global' as any, (event: any) => { 730 | class Foo { bar () { return 'bar'; } } 731 | Foo.prototype.constructor = undefined as any 732 | event.returnValue = new Foo() 733 | }) 734 | expect(await remotely(() => require('./renderer').getGlobal('test').bar())).to.equal('bar') 735 | }) 736 | }) 737 | 738 | describe('remote value in browser', () => { 739 | const remotely = makeRemotely(makeWindow()) 740 | const print = path.join(fixtures, 'print_name.js') 741 | 742 | remotely.it(print)('preserves NaN', (print: string) => { 743 | const printName = require('./renderer').require(print) 744 | expect(printName.getNaN()).to.be.NaN() 745 | expect(printName.echo(NaN)).to.be.NaN() 746 | }) 747 | 748 | remotely.it(print)('preserves Infinity', (print: string) => { 749 | const printName = require('./renderer').require(print) 750 | expect(printName.getInfinity()).to.equal(Infinity) 751 | expect(printName.echo(Infinity)).to.equal(Infinity) 752 | }) 753 | 754 | remotely.it(print)('keeps its constructor name for objects', (print: string) => { 755 | const printName = require('./renderer').require(print) 756 | const buf = Buffer.from('test') 757 | expect(printName.print(buf)).to.equal('Buffer') 758 | }) 759 | 760 | remotely.it(print)('supports instanceof Boolean', (print: string) => { 761 | const printName = require('./renderer').require(print) 762 | const obj = Boolean(true) 763 | expect(printName.print(obj)).to.equal('Boolean') 764 | expect(printName.echo(obj)).to.deep.equal(obj) 765 | }) 766 | 767 | remotely.it(print)('supports instanceof Number', (print: string) => { 768 | const printName = require('./renderer').require(print) 769 | const obj = Number(42) 770 | expect(printName.print(obj)).to.equal('Number') 771 | expect(printName.echo(obj)).to.deep.equal(obj) 772 | }) 773 | 774 | remotely.it(print)('supports instanceof String', (print: string) => { 775 | const printName = require('./renderer').require(print) 776 | const obj = String('Hello World!') 777 | expect(printName.print(obj)).to.equal('String') 778 | expect(printName.echo(obj)).to.deep.equal(obj) 779 | }) 780 | 781 | remotely.it(print)('supports instanceof Date', (print: string) => { 782 | const printName = require('./renderer').require(print) 783 | const now = new Date() 784 | expect(printName.print(now)).to.equal('Date') 785 | expect(printName.echo(now)).to.deep.equal(now) 786 | }) 787 | 788 | remotely.it(print)('supports instanceof RegExp', (print: string) => { 789 | const printName = require('./renderer').require(print) 790 | const regexp = RegExp('.*') 791 | expect(printName.print(regexp)).to.equal('RegExp') 792 | expect(printName.echo(regexp)).to.deep.equal(regexp) 793 | }) 794 | 795 | remotely.it(print)('supports instanceof Buffer', (print: string) => { 796 | const printName = require('./renderer').require(print) 797 | const buffer = Buffer.from('test') 798 | expect(buffer.equals(printName.echo(buffer))).to.be.true() 799 | 800 | const objectWithBuffer = { a: 'foo', b: Buffer.from('bar') } 801 | expect(objectWithBuffer.b.equals(printName.echo(objectWithBuffer).b)).to.be.true() 802 | 803 | const arrayWithBuffer = [1, 2, Buffer.from('baz')] 804 | expect((arrayWithBuffer[2] as Buffer).equals(printName.echo(arrayWithBuffer)[2])).to.be.true() 805 | }) 806 | 807 | remotely.it(print)('supports instanceof ArrayBuffer', (print: string) => { 808 | const printName = require('./renderer').require(print) 809 | const buffer = new ArrayBuffer(8) 810 | const view = new DataView(buffer) 811 | 812 | view.setFloat64(0, Math.PI) 813 | expect(printName.echo(buffer)).to.deep.equal(buffer) 814 | expect(printName.print(buffer)).to.equal('ArrayBuffer') 815 | }) 816 | 817 | const arrayTests: [string, number[]][] = [ 818 | ['Int8Array', [1, 2, 3, 4]], 819 | ['Uint8Array', [1, 2, 3, 4]], 820 | ['Uint8ClampedArray', [1, 2, 3, 4]], 821 | ['Int16Array', [0x1234, 0x2345, 0x3456, 0x4567]], 822 | ['Uint16Array', [0x1234, 0x2345, 0x3456, 0x4567]], 823 | ['Int32Array', [0x12345678, 0x23456789]], 824 | ['Uint32Array', [0x12345678, 0x23456789]], 825 | ['Float32Array', [0.5, 1.0, 1.5]], 826 | ['Float64Array', [0.5, 1.0, 1.5]] 827 | ] 828 | 829 | arrayTests.forEach(([arrayType, values]) => { 830 | remotely.it(print, arrayType, values)(`supports instanceof ${arrayType}`, (print: string, arrayType: string, values: number[]) => { 831 | const printName = require('./renderer').require(print) 832 | expect([...printName.typedArray(arrayType, values)]).to.deep.equal(values) 833 | 834 | const int8values = new ((window as any)[arrayType])(values) 835 | expect(printName.typedArray(arrayType, int8values)).to.deep.equal(int8values) 836 | expect(printName.print(int8values)).to.equal(arrayType) 837 | }) 838 | }) 839 | 840 | describe('constructing a Uint8Array', () => { 841 | remotely.it()('does not crash', () => { 842 | const RUint8Array = require('./renderer').getGlobal('Uint8Array') 843 | new RUint8Array() // eslint-disable-line 844 | }) 845 | }) 846 | }) 847 | 848 | describe('remote promise', () => { 849 | const remotely = makeRemotely(makeWindow()) 850 | 851 | remotely.it(fixtures)('can be used as promise in each side', async (fixtures: string) => { 852 | const promise = require('./renderer').require(require('path').join(fixtures, 'promise.js')) 853 | const value = await promise.twicePromise(Promise.resolve(1234)) 854 | expect(value).to.equal(2468) 855 | }) 856 | 857 | remotely.it(fixtures)('handles rejections via catch(onRejected)', async (fixtures: string) => { 858 | const promise = require('./renderer').require(require('path').join(fixtures, 'rejected-promise.js')) 859 | const error = await new Promise(resolve => { 860 | promise.reject(Promise.resolve(1234)).catch(resolve) 861 | }) 862 | expect(error.message).to.equal('rejected') 863 | }) 864 | 865 | remotely.it(fixtures)('handles rejections via then(onFulfilled, onRejected)', async (fixtures: string) => { 866 | const promise = require('./renderer').require(require('path').join(fixtures, 'rejected-promise.js')) 867 | const error = await new Promise(resolve => { 868 | promise.reject(Promise.resolve(1234)).then(() => {}, resolve) 869 | }) 870 | expect(error.message).to.equal('rejected') 871 | }) 872 | 873 | it('does not emit unhandled rejection events in the main process', (done) => { 874 | function onUnhandledRejection () { 875 | done(new Error('Unexpected unhandledRejection event')) 876 | } 877 | process.once('unhandledRejection', onUnhandledRejection) 878 | 879 | remotely(async (fixtures: string) => { 880 | const promise = require('./renderer').require(require('path').join(fixtures, 'unhandled-rejection.js')) 881 | return new Promise((resolve, reject) => { 882 | promise.reject().then(() => { 883 | reject(new Error('Promise was not rejected')) 884 | }).catch((error: Error) => { 885 | resolve(error) 886 | }) 887 | }) 888 | }, fixtures).then(error => { 889 | try { 890 | expect(error.message).to.equal('rejected') 891 | done() 892 | } catch (e) { 893 | done(e) 894 | } finally { 895 | process.off('unhandledRejection' as any, onUnhandledRejection) 896 | } 897 | }) 898 | }) 899 | 900 | it('emits unhandled rejection events in the renderer process', (done) => { 901 | remotely((module: string) => new Promise((resolve, reject) => { 902 | const promise = require('./renderer').require(module) 903 | 904 | window.addEventListener('unhandledrejection', function handler (event) { 905 | event.preventDefault() 906 | window.removeEventListener('unhandledrejection', handler) 907 | resolve(event.reason.message) 908 | }) 909 | 910 | promise.reject().then(() => { 911 | reject(new Error('Promise was not rejected')) 912 | }) 913 | }), path.join(fixtures, 'unhandled-rejection.js')).then( 914 | (message) => { 915 | try { 916 | expect(message).to.equal('rejected') 917 | done() 918 | } catch (e) { 919 | done(e) 920 | } 921 | }, 922 | done 923 | ) 924 | }) 925 | 926 | before(() => { 927 | (global as any).returnAPromise = (value: any) => new Promise((resolve) => setTimeout(() => resolve(value), 100)) 928 | }) 929 | after(() => { 930 | delete (global as any).returnAPromise 931 | }) 932 | remotely.it()('using a promise based method resolves correctly when global Promise is overridden', async () => { 933 | const remote = require('./renderer') 934 | const original = global.Promise 935 | try { 936 | expect(await remote.getGlobal('returnAPromise')(123)).to.equal(123) 937 | global.Promise = { resolve: () => ({}) } as any 938 | expect(await remote.getGlobal('returnAPromise')(456)).to.equal(456) 939 | } finally { 940 | global.Promise = original 941 | } 942 | }) 943 | }) 944 | 945 | describe('remote webContents', () => { 946 | const remotely = makeRemotely(makeWindow()) 947 | 948 | it('can return same object with different getters', async () => { 949 | const equal = await remotely(() => { 950 | const remote = require('./renderer') 951 | const contents1 = remote.getCurrentWindow().webContents 952 | const contents2 = remote.getCurrentWebContents() 953 | return contents1 === contents2 954 | }) 955 | expect(equal).to.be.true() 956 | }) 957 | }) 958 | 959 | describe('remote class', () => { 960 | const remotely = makeRemotely(makeWindow()) 961 | 962 | remotely.it(fixtures)('can get methods', (fixtures: string) => { 963 | const { base } = require('./renderer').require(require('path').join(fixtures, 'class.js')) 964 | expect(base.method()).to.equal('method') 965 | }) 966 | 967 | remotely.it(fixtures)('can get properties', (fixtures: string) => { 968 | const { base } = require('./renderer').require(require('path').join(fixtures, 'class.js')) 969 | expect(base.readonly).to.equal('readonly') 970 | }) 971 | 972 | remotely.it(fixtures)('can change properties', (fixtures: string) => { 973 | const { base } = require('./renderer').require(require('path').join(fixtures, 'class.js')) 974 | expect(base.value).to.equal('old') 975 | base.value = 'new' 976 | expect(base.value).to.equal('new') 977 | base.value = 'old' 978 | }) 979 | 980 | remotely.it(fixtures)('has unenumerable methods', (fixtures: string) => { 981 | const { base } = require('./renderer').require(require('path').join(fixtures, 'class.js')) 982 | expect(base).to.not.have.ownProperty('method') 983 | expect(Object.getPrototypeOf(base)).to.have.ownProperty('method') 984 | }) 985 | 986 | remotely.it(fixtures)('keeps prototype chain in derived class', (fixtures: string) => { 987 | const { derived } = require('./renderer').require(require('path').join(fixtures, 'class.js')) 988 | expect(derived.method()).to.equal('method') 989 | expect(derived.readonly).to.equal('readonly') 990 | expect(derived).to.not.have.ownProperty('method') 991 | const proto = Object.getPrototypeOf(derived) 992 | expect(proto).to.not.have.ownProperty('method') 993 | expect(Object.getPrototypeOf(proto)).to.have.ownProperty('method') 994 | }) 995 | 996 | remotely.it(fixtures)('is referenced by methods in prototype chain', (fixtures: string) => { 997 | let { derived } = require('./renderer').require(require('path').join(fixtures, 'class.js')) 998 | const method = derived.method 999 | derived = null 1000 | global.gc() 1001 | expect(method()).to.equal('method') 1002 | }) 1003 | }) 1004 | 1005 | describe('remote exception', () => { 1006 | const remotely = makeRemotely(makeWindow()) 1007 | 1008 | remotely.it(fixtures)('throws errors from the main process', (fixtures: string) => { 1009 | const throwFunction = require('./renderer').require(require('path').join(fixtures, 'exception.js')) 1010 | expect(() => { 1011 | throwFunction() 1012 | }).to.throw(/undefined/) 1013 | }) 1014 | 1015 | remotely.it(fixtures)('tracks error cause', (fixtures: string) => { 1016 | const throwFunction = require('./renderer').require(require('path').join(fixtures, 'exception.js')) 1017 | try { 1018 | throwFunction(new Error('error from main')) 1019 | expect.fail() 1020 | } catch (e: any) { 1021 | expect(e.message).to.match(/Could not call remote function/) 1022 | if (parseInt(process.versions.electron) < 14) // FIXME 1023 | expect(e.cause.message).to.equal('error from main') 1024 | } 1025 | }) 1026 | }) 1027 | 1028 | describe('gc behavior', () => { 1029 | const win = makeWindow() 1030 | const remotely = makeRemotely(win) 1031 | it('is resilient to gc happening between request and response', async () => { 1032 | const obj = { x: 'y' } 1033 | win().webContents.on('remote-get-global' as any, (event: any) => { 1034 | event.returnValue = obj 1035 | }) 1036 | await remotely(() => { 1037 | const { ipcRenderer } = require('electron') 1038 | const originalSendSync = ipcRenderer.sendSync.bind(ipcRenderer) as any 1039 | ipcRenderer.sendSync = (...args: any[]): any => { 1040 | const ret = originalSendSync(...args) 1041 | ;(window as any).gc() 1042 | return ret 1043 | } 1044 | 1045 | for (let i = 0; i < 100; i++) { 1046 | // eslint-disable-next-line 1047 | require('./renderer').getGlobal('test').x 1048 | } 1049 | }) 1050 | }) 1051 | }) 1052 | }) 1053 | -------------------------------------------------------------------------------- /test/events-helpers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview A set of helper functions to make it easier to work 3 | * with events in async/await manner. 4 | */ 5 | 6 | /** 7 | * @param {!EventTarget} target 8 | * @param {string} eventName 9 | * @return {!Promise} 10 | */ 11 | export const waitForEvent = (target: EventTarget, eventName: string) => { 12 | return new Promise(resolve => { 13 | target.addEventListener(eventName, resolve, { once: true }) 14 | }) 15 | } 16 | 17 | /** 18 | * @param {!EventEmitter} emitter 19 | * @param {string} eventName 20 | * @return {!Promise} With Event as the first item. 21 | */ 22 | export const emittedOnce = (emitter: NodeJS.EventEmitter, eventName: string, trigger?: () => void) => { 23 | return emittedNTimes(emitter, eventName, 1, trigger).then(([result]) => result) 24 | } 25 | 26 | export const emittedNTimes = async (emitter: NodeJS.EventEmitter, eventName: string, times: number, trigger?: () => void) => { 27 | const events: any[][] = [] 28 | const p = new Promise(resolve => { 29 | const handler = (...args: any[]) => { 30 | events.push(args) 31 | if (events.length === times) { 32 | emitter.removeListener(eventName, handler) 33 | resolve(events) 34 | } 35 | } 36 | emitter.on(eventName, handler) 37 | }) 38 | if (trigger) { 39 | await Promise.resolve(trigger()) 40 | } 41 | return p 42 | } 43 | 44 | export const emittedUntil = async (emitter: NodeJS.EventEmitter, eventName: string, untilFn: Function) => { 45 | const p = new Promise(resolve => { 46 | const handler = (...args: any[]) => { 47 | if (untilFn(...args)) { 48 | emitter.removeListener(eventName, handler) 49 | resolve(args) 50 | } 51 | } 52 | emitter.on(eventName, handler) 53 | }) 54 | return p 55 | } 56 | -------------------------------------------------------------------------------- /test/fixtures/call.js: -------------------------------------------------------------------------------- 1 | exports.call = function (func) { 2 | return func() 3 | } 4 | 5 | exports.constructor = function () { 6 | this.test = 'test' 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/circular.js: -------------------------------------------------------------------------------- 1 | exports.returnArgs = function (...args) { 2 | return args 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/class.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | let value = 'old' 4 | 5 | class BaseClass { 6 | method () { 7 | return 'method' 8 | } 9 | 10 | get readonly () { 11 | return 'readonly' 12 | } 13 | 14 | get value () { 15 | return value 16 | } 17 | 18 | set value (val) { 19 | value = val 20 | } 21 | } 22 | 23 | class DerivedClass extends BaseClass { 24 | } 25 | 26 | module.exports = { 27 | base: new BaseClass(), 28 | derived: new DerivedClass() 29 | } 30 | -------------------------------------------------------------------------------- /test/fixtures/error-properties.js: -------------------------------------------------------------------------------- 1 | class Foo { 2 | set bar (value) { 3 | throw new Error('setting error') 4 | } 5 | 6 | get bar () { 7 | throw new Error('getting error') 8 | } 9 | } 10 | 11 | module.exports = new Foo() 12 | -------------------------------------------------------------------------------- /test/fixtures/exception.js: -------------------------------------------------------------------------------- 1 | module.exports = function (error) { 2 | throw error 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/export-function-with-properties.js: -------------------------------------------------------------------------------- 1 | function foo () {} 2 | foo.bar = 'baz' 3 | 4 | module.exports = foo 5 | -------------------------------------------------------------------------------- /test/fixtures/function-with-args.js: -------------------------------------------------------------------------------- 1 | module.exports = function (cb) { 2 | return cb.length 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/function-with-missing-properties.js: -------------------------------------------------------------------------------- 1 | exports.setup = function () { 2 | const foo = {} 3 | 4 | foo.bar = function () { 5 | return delete foo.bar.baz && delete foo.bar 6 | } 7 | 8 | foo.bar.baz = function () { 9 | return 3 10 | } 11 | 12 | return foo 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/function-with-properties.js: -------------------------------------------------------------------------------- 1 | function foo () { 2 | return 'hello' 3 | } 4 | foo.bar = 'baz' 5 | foo.nested = { 6 | prop: 'yes' 7 | } 8 | foo.method1 = function () { 9 | return 'world' 10 | } 11 | foo.method1.prop1 = function () { 12 | return 123 13 | } 14 | 15 | module.exports = { 16 | foo: foo 17 | } 18 | -------------------------------------------------------------------------------- /test/fixtures/function.js: -------------------------------------------------------------------------------- 1 | exports.aFunction = function () { return 1127 } 2 | -------------------------------------------------------------------------------- /test/fixtures/id.js: -------------------------------------------------------------------------------- 1 | exports.id = 1127 2 | -------------------------------------------------------------------------------- /test/fixtures/no-prototype.js: -------------------------------------------------------------------------------- 1 | const foo = Object.create(null) 2 | foo.bar = 'baz' 3 | foo.baz = false 4 | module.exports = { 5 | foo: foo, 6 | bar: 1234, 7 | anonymous: new (class {})(), 8 | getConstructorName: function (value) { 9 | return value.constructor.name 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/preload-remote-function.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer } = require('electron') 2 | const remote = require('../../renderer') 3 | remote.getCurrentWindow().rendererFunc = () => { 4 | ipcRenderer.send('done') 5 | } 6 | remote.getCurrentWindow().rendererFunc() 7 | -------------------------------------------------------------------------------- /test/fixtures/print_name.js: -------------------------------------------------------------------------------- 1 | exports.print = function (obj) { 2 | return obj.constructor.name 3 | } 4 | 5 | exports.echo = function (obj) { 6 | return obj 7 | } 8 | 9 | const typedArrays = { 10 | Int8Array, 11 | Uint8Array, 12 | Uint8ClampedArray, 13 | Int16Array, 14 | Uint16Array, 15 | Int32Array, 16 | Uint32Array, 17 | Float32Array, 18 | Float64Array 19 | } 20 | 21 | exports.typedArray = function (type, values) { 22 | const constructor = typedArrays[type] 23 | const array = new constructor(values.length) 24 | for (let i = 0; i < values.length; ++i) { 25 | array[i] = values[i] 26 | } 27 | return array 28 | } 29 | 30 | exports.getNaN = function () { 31 | return NaN 32 | } 33 | 34 | exports.getInfinity = function () { 35 | return Infinity 36 | } 37 | -------------------------------------------------------------------------------- /test/fixtures/promise.js: -------------------------------------------------------------------------------- 1 | exports.twicePromise = function (promise) { 2 | return promise.then(function (value) { 3 | return value * 2 4 | }) 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/property.js: -------------------------------------------------------------------------------- 1 | exports.property = 1127 2 | 3 | function func () { 4 | 5 | } 6 | func.property = 'foo' 7 | exports.func = func 8 | 9 | exports.getFunctionProperty = () => { 10 | return `${func.property}-${process.type}` 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/rejected-promise.js: -------------------------------------------------------------------------------- 1 | exports.reject = function (promise) { 2 | return promise.then(function () { 3 | throw Error('rejected'); 4 | }); 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/remote-event-handler.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/fixtures/remote-object-set.js: -------------------------------------------------------------------------------- 1 | const { BrowserWindow } = require('electron') 2 | 3 | class Foo { 4 | set bar (value) { 5 | if (!(value instanceof BrowserWindow)) { 6 | throw new Error('setting error') 7 | } 8 | } 9 | } 10 | 11 | module.exports = new Foo() 12 | -------------------------------------------------------------------------------- /test/fixtures/remote-static.js: -------------------------------------------------------------------------------- 1 | class Foo { 2 | static foo () { 3 | return 3 4 | } 5 | 6 | baz () { 7 | return 123 8 | } 9 | } 10 | 11 | Foo.bar = 'baz' 12 | 13 | module.exports = { 14 | Foo: Foo 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/render-view-deleted.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /test/fixtures/send-on-exit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/fixtures/to-string-non-function.js: -------------------------------------------------------------------------------- 1 | function hello () { 2 | } 3 | hello.toString = 'hello' 4 | module.exports = { functionWithToStringProperty: hello } 5 | -------------------------------------------------------------------------------- /test/fixtures/unhandled-rejection.js: -------------------------------------------------------------------------------- 1 | exports.reject = function () { 2 | return Promise.reject(new Error('rejected')); 3 | }; 4 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai') 2 | chai.use(require('chai-as-promised')) 3 | chai.use(require('dirty-chai')) 4 | const { app } = require('electron') 5 | 6 | app.on('window-all-closed', () => null) 7 | 8 | app.whenReady().then(() => { 9 | require('mocha/bin/mocha') 10 | }) 11 | -------------------------------------------------------------------------------- /test/window-helpers.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { BrowserWindow } from 'electron' 3 | import { emittedOnce } from './events-helpers' 4 | 5 | async function ensureWindowIsClosed (window: BrowserWindow | null) { 6 | if (window && !window.isDestroyed()) { 7 | if (window.webContents && !window.webContents.isDestroyed()) { 8 | // If a window isn't destroyed already, and it has non-destroyed WebContents, 9 | // then calling destroy() won't immediately destroy it, as it may have 10 | // children which need to be destroyed first. In that case, we 11 | // await the 'closed' event which signals the complete shutdown of the 12 | // window. 13 | const isClosed = emittedOnce(window, 'closed') 14 | window.close() 15 | await isClosed 16 | } else { 17 | // If there's no WebContents or if the WebContents is already destroyed, 18 | // then the 'closed' event has already been emitted so there's nothing to 19 | // wait for. 20 | window.close() 21 | } 22 | } 23 | } 24 | 25 | export const closeWindow = async ( 26 | window: BrowserWindow | null = null, 27 | { assertNotWindows } = { assertNotWindows: true } 28 | ) => { 29 | await ensureWindowIsClosed(window) 30 | 31 | if (assertNotWindows) { 32 | const windows = BrowserWindow.getAllWindows() 33 | try { 34 | expect(windows).to.have.lengthOf(0) 35 | } finally { 36 | for (const win of windows) { 37 | await ensureWindowIsClosed(win) 38 | } 39 | } 40 | } 41 | } 42 | 43 | export async function closeAllWindows () { 44 | for (const w of BrowserWindow.getAllWindows()) { 45 | await closeWindow(w, { assertNotWindows: false }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "target": "es2019", 5 | "lib": [ 6 | "es2019", 7 | "esnext.weakref", 8 | "dom", 9 | "dom.iterable" 10 | ], 11 | "module": "commonjs", 12 | "outDir": "./dist", 13 | "strict": true, 14 | "esModuleInterop": true 15 | }, 16 | "exclude": ["dist", "main", "renderer"] 17 | } 18 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@electron/get@^2.0.0": 6 | version "2.0.2" 7 | resolved "https://registry.yarnpkg.com/@electron/get/-/get-2.0.2.tgz#ae2a967b22075e9c25aaf00d5941cd79c21efd7e" 8 | integrity sha512-eFZVFoRXb3GFGd7Ak7W4+6jBl9wBtiZ4AaYOse97ej6mKj5tkyO0dUnUChs1IhJZtx1BENo4/p4WUTXpi6vT+g== 9 | dependencies: 10 | debug "^4.1.1" 11 | env-paths "^2.2.0" 12 | fs-extra "^8.1.0" 13 | got "^11.8.5" 14 | progress "^2.0.3" 15 | semver "^6.2.0" 16 | sumchecker "^3.0.1" 17 | optionalDependencies: 18 | global-agent "^3.0.0" 19 | 20 | "@sindresorhus/is@^4.0.0": 21 | version "4.6.0" 22 | resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" 23 | integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== 24 | 25 | "@szmarczak/http-timer@^4.0.5": 26 | version "4.0.6" 27 | resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" 28 | integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== 29 | dependencies: 30 | defer-to-connect "^2.0.0" 31 | 32 | "@types/cacheable-request@^6.0.1": 33 | version "6.0.3" 34 | resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" 35 | integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== 36 | dependencies: 37 | "@types/http-cache-semantics" "*" 38 | "@types/keyv" "^3.1.4" 39 | "@types/node" "*" 40 | "@types/responselike" "^1.0.0" 41 | 42 | "@types/chai-as-promised@*", "@types/chai-as-promised@^7.1.2": 43 | version "7.1.2" 44 | resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.2.tgz#2f564420e81eaf8650169e5a3a6b93e096e5068b" 45 | integrity sha512-PO2gcfR3Oxa+u0QvECLe1xKXOqYTzCmWf0FhLhjREoW3fPAVamjihL7v1MOVLJLsnAMdLcjkfrs01yvDMwVK4Q== 46 | dependencies: 47 | "@types/chai" "*" 48 | 49 | "@types/chai@*", "@types/chai@^4.2.11": 50 | version "4.2.11" 51 | resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.11.tgz#d3614d6c5f500142358e6ed24e1bf16657536c50" 52 | integrity sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw== 53 | 54 | "@types/color-name@^1.1.1": 55 | version "1.1.1" 56 | resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" 57 | integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== 58 | 59 | "@types/dirty-chai@^2.0.2": 60 | version "2.0.2" 61 | resolved "https://registry.yarnpkg.com/@types/dirty-chai/-/dirty-chai-2.0.2.tgz#eeac4802329a41ed7815ac0c1a6360335bf77d0c" 62 | integrity sha512-BruwIN/UQEU0ePghxEX+OyjngpOfOUKJQh3cmfeq2h2Su/g001iljVi3+Y2y2EFp3IPgjf4sMrRU33Hxv1FUqw== 63 | dependencies: 64 | "@types/chai" "*" 65 | "@types/chai-as-promised" "*" 66 | 67 | "@types/http-cache-semantics@*": 68 | version "4.0.1" 69 | resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" 70 | integrity sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ== 71 | 72 | "@types/keyv@^3.1.4": 73 | version "3.1.4" 74 | resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" 75 | integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== 76 | dependencies: 77 | "@types/node" "*" 78 | 79 | "@types/mocha@^10.0.10": 80 | version "10.0.10" 81 | resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.10.tgz#91f62905e8d23cbd66225312f239454a23bebfa0" 82 | integrity sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q== 83 | 84 | "@types/node@*": 85 | version "18.11.10" 86 | resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.10.tgz#4c64759f3c2343b7e6c4b9caf761c7a3a05cee34" 87 | integrity sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ== 88 | 89 | "@types/node@^14.17.0": 90 | version "14.17.18" 91 | resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.18.tgz#0198489a751005f71217744aa966cd1f29447c81" 92 | integrity sha512-haYyibw4pbteEhkSg0xdDLAI3679L75EJ799ymVrPxOA922bPx3ML59SoDsQ//rHlvqpu+e36kcbR3XRQtFblA== 93 | 94 | "@types/node@^16.11.26": 95 | version "16.18.4" 96 | resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.4.tgz#712ba61b4caf091fc6490301b1888356638c17bd" 97 | integrity sha512-9qGjJ5GyShZjUfx2ArBIGM+xExdfLvvaCyQR0t6yRXKPcWCVYF/WemtX/uIU3r7FYECXRXkIiw2Vnhn6y8d+pw== 98 | 99 | "@types/responselike@^1.0.0": 100 | version "1.0.0" 101 | resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" 102 | integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== 103 | dependencies: 104 | "@types/node" "*" 105 | 106 | "@types/yauzl@^2.9.1": 107 | version "2.10.0" 108 | resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" 109 | integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw== 110 | dependencies: 111 | "@types/node" "*" 112 | 113 | ansi-colors@^4.1.3: 114 | version "4.1.3" 115 | resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" 116 | integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== 117 | 118 | ansi-regex@^3.0.0: 119 | version "3.0.1" 120 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1" 121 | integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw== 122 | 123 | ansi-regex@^5.0.0: 124 | version "5.0.1" 125 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" 126 | integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== 127 | 128 | ansi-styles@^4.0.0, ansi-styles@^4.1.0: 129 | version "4.2.1" 130 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" 131 | integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== 132 | dependencies: 133 | "@types/color-name" "^1.1.1" 134 | color-convert "^2.0.1" 135 | 136 | anymatch@~3.1.2: 137 | version "3.1.3" 138 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" 139 | integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== 140 | dependencies: 141 | normalize-path "^3.0.0" 142 | picomatch "^2.0.4" 143 | 144 | arg@^4.1.0: 145 | version "4.1.3" 146 | resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" 147 | integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== 148 | 149 | argparse@^2.0.1: 150 | version "2.0.1" 151 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" 152 | integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== 153 | 154 | assertion-error@^1.1.0: 155 | version "1.1.0" 156 | resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" 157 | integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== 158 | 159 | balanced-match@^1.0.0: 160 | version "1.0.0" 161 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 162 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 163 | 164 | binary-extensions@^2.0.0: 165 | version "2.0.0" 166 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" 167 | integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== 168 | 169 | boolean@^3.0.0, boolean@^3.0.1: 170 | version "3.0.1" 171 | resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.1.tgz#35ecf2b4a2ee191b0b44986f14eb5f052a5cbb4f" 172 | integrity sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA== 173 | 174 | brace-expansion@^2.0.1: 175 | version "2.0.1" 176 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" 177 | integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== 178 | dependencies: 179 | balanced-match "^1.0.0" 180 | 181 | braces@~3.0.2: 182 | version "3.0.3" 183 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" 184 | integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== 185 | dependencies: 186 | fill-range "^7.1.1" 187 | 188 | browser-stdout@^1.3.1: 189 | version "1.3.1" 190 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" 191 | integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== 192 | 193 | buffer-crc32@~0.2.3: 194 | version "0.2.13" 195 | resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" 196 | integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= 197 | 198 | buffer-from@^1.0.0: 199 | version "1.1.1" 200 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 201 | integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== 202 | 203 | cacheable-lookup@^5.0.3: 204 | version "5.0.4" 205 | resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" 206 | integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== 207 | 208 | cacheable-request@^7.0.2: 209 | version "7.0.2" 210 | resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" 211 | integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== 212 | dependencies: 213 | clone-response "^1.0.2" 214 | get-stream "^5.1.0" 215 | http-cache-semantics "^4.0.0" 216 | keyv "^4.0.0" 217 | lowercase-keys "^2.0.0" 218 | normalize-url "^6.0.1" 219 | responselike "^2.0.0" 220 | 221 | camelcase@^5.0.0: 222 | version "5.3.1" 223 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" 224 | integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== 225 | 226 | camelcase@^6.0.0: 227 | version "6.3.0" 228 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" 229 | integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== 230 | 231 | chai-as-promised@^7.1.1: 232 | version "7.1.1" 233 | resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0" 234 | integrity sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA== 235 | dependencies: 236 | check-error "^1.0.2" 237 | 238 | chai@^4.2.0: 239 | version "4.2.0" 240 | resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" 241 | integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== 242 | dependencies: 243 | assertion-error "^1.1.0" 244 | check-error "^1.0.2" 245 | deep-eql "^3.0.1" 246 | get-func-name "^2.0.0" 247 | pathval "^1.1.0" 248 | type-detect "^4.0.5" 249 | 250 | chalk@^4.1.0: 251 | version "4.1.2" 252 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" 253 | integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== 254 | dependencies: 255 | ansi-styles "^4.1.0" 256 | supports-color "^7.1.0" 257 | 258 | charenc@~0.0.1: 259 | version "0.0.2" 260 | resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" 261 | integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= 262 | 263 | check-error@^1.0.2: 264 | version "1.0.2" 265 | resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" 266 | integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= 267 | 268 | chokidar@^3.5.3: 269 | version "3.6.0" 270 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" 271 | integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== 272 | dependencies: 273 | anymatch "~3.1.2" 274 | braces "~3.0.2" 275 | glob-parent "~5.1.2" 276 | is-binary-path "~2.1.0" 277 | is-glob "~4.0.1" 278 | normalize-path "~3.0.0" 279 | readdirp "~3.6.0" 280 | optionalDependencies: 281 | fsevents "~2.3.2" 282 | 283 | cliui@^6.0.0: 284 | version "6.0.0" 285 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" 286 | integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== 287 | dependencies: 288 | string-width "^4.2.0" 289 | strip-ansi "^6.0.0" 290 | wrap-ansi "^6.2.0" 291 | 292 | cliui@^7.0.2: 293 | version "7.0.4" 294 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" 295 | integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== 296 | dependencies: 297 | string-width "^4.2.0" 298 | strip-ansi "^6.0.0" 299 | wrap-ansi "^7.0.0" 300 | 301 | clone-response@^1.0.2: 302 | version "1.0.2" 303 | resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" 304 | integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= 305 | dependencies: 306 | mimic-response "^1.0.0" 307 | 308 | color-convert@^2.0.1: 309 | version "2.0.1" 310 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 311 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 312 | dependencies: 313 | color-name "~1.1.4" 314 | 315 | color-name@~1.1.4: 316 | version "1.1.4" 317 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 318 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 319 | 320 | crypt@~0.0.1: 321 | version "0.0.2" 322 | resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" 323 | integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= 324 | 325 | debug@^2.2.0: 326 | version "2.6.9" 327 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 328 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 329 | dependencies: 330 | ms "2.0.0" 331 | 332 | debug@^3.1.0: 333 | version "3.2.7" 334 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" 335 | integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== 336 | dependencies: 337 | ms "^2.1.1" 338 | 339 | debug@^4.1.0, debug@^4.1.1, debug@^4.3.5: 340 | version "4.4.0" 341 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" 342 | integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== 343 | dependencies: 344 | ms "^2.1.3" 345 | 346 | decamelize@^1.2.0: 347 | version "1.2.0" 348 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 349 | integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= 350 | 351 | decamelize@^4.0.0: 352 | version "4.0.0" 353 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" 354 | integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== 355 | 356 | decompress-response@^6.0.0: 357 | version "6.0.0" 358 | resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" 359 | integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== 360 | dependencies: 361 | mimic-response "^3.1.0" 362 | 363 | deep-eql@^3.0.1: 364 | version "3.0.1" 365 | resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" 366 | integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== 367 | dependencies: 368 | type-detect "^4.0.0" 369 | 370 | defer-to-connect@^2.0.0: 371 | version "2.0.1" 372 | resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" 373 | integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== 374 | 375 | define-properties@^1.1.3: 376 | version "1.1.3" 377 | resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" 378 | integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== 379 | dependencies: 380 | object-keys "^1.0.12" 381 | 382 | detect-node@^2.0.4: 383 | version "2.0.4" 384 | resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" 385 | integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== 386 | 387 | diff@^4.0.1: 388 | version "4.0.2" 389 | resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" 390 | integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== 391 | 392 | diff@^5.2.0: 393 | version "5.2.0" 394 | resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" 395 | integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== 396 | 397 | dirty-chai@^2.0.1: 398 | version "2.0.1" 399 | resolved "https://registry.yarnpkg.com/dirty-chai/-/dirty-chai-2.0.1.tgz#6b2162ef17f7943589da840abc96e75bda01aff3" 400 | integrity sha512-ys79pWKvDMowIDEPC6Fig8d5THiC0DJ2gmTeGzVAoEH18J8OzLud0Jh7I9IWg3NSk8x2UocznUuFmfHCXYZx9w== 401 | 402 | electron@22.x: 403 | version "22.3.25" 404 | resolved "https://registry.yarnpkg.com/electron/-/electron-22.3.25.tgz#a9a70b63a6712c658cd7fab343129b2a78450f80" 405 | integrity sha512-AjrP7bebMs/IPsgmyowptbA7jycTkrJC7jLZTb5JoH30PkBC6pZx/7XQ0aDok82SsmSiF4UJDOg+HoLrEBiqmg== 406 | dependencies: 407 | "@electron/get" "^2.0.0" 408 | "@types/node" "^16.11.26" 409 | extract-zip "^2.0.1" 410 | 411 | emoji-regex@^8.0.0: 412 | version "8.0.0" 413 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 414 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 415 | 416 | end-of-stream@^1.1.0: 417 | version "1.4.4" 418 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 419 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== 420 | dependencies: 421 | once "^1.4.0" 422 | 423 | env-paths@^2.2.0: 424 | version "2.2.0" 425 | resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" 426 | integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== 427 | 428 | es6-error@^4.1.1: 429 | version "4.1.1" 430 | resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" 431 | integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== 432 | 433 | escalade@^3.1.1: 434 | version "3.1.1" 435 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" 436 | integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== 437 | 438 | escape-string-regexp@^4.0.0: 439 | version "4.0.0" 440 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" 441 | integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== 442 | 443 | extract-zip@^2.0.1: 444 | version "2.0.1" 445 | resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" 446 | integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== 447 | dependencies: 448 | debug "^4.1.1" 449 | get-stream "^5.1.0" 450 | yauzl "^2.10.0" 451 | optionalDependencies: 452 | "@types/yauzl" "^2.9.1" 453 | 454 | fd-slicer@~1.1.0: 455 | version "1.1.0" 456 | resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" 457 | integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= 458 | dependencies: 459 | pend "~1.2.0" 460 | 461 | fill-range@^7.1.1: 462 | version "7.1.1" 463 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" 464 | integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== 465 | dependencies: 466 | to-regex-range "^5.0.1" 467 | 468 | find-up@^4.1.0: 469 | version "4.1.0" 470 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" 471 | integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== 472 | dependencies: 473 | locate-path "^5.0.0" 474 | path-exists "^4.0.0" 475 | 476 | find-up@^5.0.0: 477 | version "5.0.0" 478 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" 479 | integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== 480 | dependencies: 481 | locate-path "^6.0.0" 482 | path-exists "^4.0.0" 483 | 484 | flat@^5.0.2: 485 | version "5.0.2" 486 | resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" 487 | integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== 488 | 489 | fs-extra@^8.1.0: 490 | version "8.1.0" 491 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" 492 | integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== 493 | dependencies: 494 | graceful-fs "^4.2.0" 495 | jsonfile "^4.0.0" 496 | universalify "^0.1.0" 497 | 498 | fs.realpath@^1.0.0: 499 | version "1.0.0" 500 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 501 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 502 | 503 | fsevents@~2.3.2: 504 | version "2.3.2" 505 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 506 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 507 | 508 | get-caller-file@^2.0.1, get-caller-file@^2.0.5: 509 | version "2.0.5" 510 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 511 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 512 | 513 | get-func-name@^2.0.0: 514 | version "2.0.2" 515 | resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" 516 | integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== 517 | 518 | get-stream@^5.1.0: 519 | version "5.1.0" 520 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" 521 | integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== 522 | dependencies: 523 | pump "^3.0.0" 524 | 525 | glob-parent@~5.1.2: 526 | version "5.1.2" 527 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 528 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 529 | dependencies: 530 | is-glob "^4.0.1" 531 | 532 | glob@^8.1.0: 533 | version "8.1.0" 534 | resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" 535 | integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== 536 | dependencies: 537 | fs.realpath "^1.0.0" 538 | inflight "^1.0.4" 539 | inherits "2" 540 | minimatch "^5.0.1" 541 | once "^1.3.0" 542 | 543 | global-agent@^3.0.0: 544 | version "3.0.0" 545 | resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-3.0.0.tgz#ae7cd31bd3583b93c5a16437a1afe27cc33a1ab6" 546 | integrity sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q== 547 | dependencies: 548 | boolean "^3.0.1" 549 | es6-error "^4.1.1" 550 | matcher "^3.0.0" 551 | roarr "^2.15.3" 552 | semver "^7.3.2" 553 | serialize-error "^7.0.1" 554 | 555 | globalthis@^1.0.1: 556 | version "1.0.1" 557 | resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.1.tgz#40116f5d9c071f9e8fb0037654df1ab3a83b7ef9" 558 | integrity sha512-mJPRTc/P39NH/iNG4mXa9aIhNymaQikTrnspeCa2ZuJ+mH2QN/rXwtX3XwKrHqWgUQFbNZKtHM105aHzJalElw== 559 | dependencies: 560 | define-properties "^1.1.3" 561 | 562 | got@^11.8.5: 563 | version "11.8.5" 564 | resolved "https://registry.yarnpkg.com/got/-/got-11.8.5.tgz#ce77d045136de56e8f024bebb82ea349bc730046" 565 | integrity sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ== 566 | dependencies: 567 | "@sindresorhus/is" "^4.0.0" 568 | "@szmarczak/http-timer" "^4.0.5" 569 | "@types/cacheable-request" "^6.0.1" 570 | "@types/responselike" "^1.0.0" 571 | cacheable-lookup "^5.0.3" 572 | cacheable-request "^7.0.2" 573 | decompress-response "^6.0.0" 574 | http2-wrapper "^1.0.0-beta.5.2" 575 | lowercase-keys "^2.0.0" 576 | p-cancelable "^2.0.0" 577 | responselike "^2.0.0" 578 | 579 | graceful-fs@^4.1.6, graceful-fs@^4.2.0: 580 | version "4.2.4" 581 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" 582 | integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== 583 | 584 | has-flag@^4.0.0: 585 | version "4.0.0" 586 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 587 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 588 | 589 | he@^1.2.0: 590 | version "1.2.0" 591 | resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" 592 | integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== 593 | 594 | http-cache-semantics@^4.0.0: 595 | version "4.1.1" 596 | resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" 597 | integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== 598 | 599 | http2-wrapper@^1.0.0-beta.5.2: 600 | version "1.0.3" 601 | resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" 602 | integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== 603 | dependencies: 604 | quick-lru "^5.1.1" 605 | resolve-alpn "^1.0.0" 606 | 607 | inflight@^1.0.4: 608 | version "1.0.6" 609 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 610 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 611 | dependencies: 612 | once "^1.3.0" 613 | wrappy "1" 614 | 615 | inherits@2: 616 | version "2.0.4" 617 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 618 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 619 | 620 | is-binary-path@~2.1.0: 621 | version "2.1.0" 622 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 623 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 624 | dependencies: 625 | binary-extensions "^2.0.0" 626 | 627 | is-buffer@~1.1.1: 628 | version "1.1.6" 629 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" 630 | integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== 631 | 632 | is-extglob@^2.1.1: 633 | version "2.1.1" 634 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 635 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 636 | 637 | is-fullwidth-code-point@^3.0.0: 638 | version "3.0.0" 639 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 640 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 641 | 642 | is-glob@^4.0.1, is-glob@~4.0.1: 643 | version "4.0.1" 644 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" 645 | integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== 646 | dependencies: 647 | is-extglob "^2.1.1" 648 | 649 | is-number@^7.0.0: 650 | version "7.0.0" 651 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 652 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 653 | 654 | is-plain-obj@^2.1.0: 655 | version "2.1.0" 656 | resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" 657 | integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== 658 | 659 | is-unicode-supported@^0.1.0: 660 | version "0.1.0" 661 | resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" 662 | integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== 663 | 664 | js-yaml@^4.1.0: 665 | version "4.1.0" 666 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" 667 | integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== 668 | dependencies: 669 | argparse "^2.0.1" 670 | 671 | json-buffer@3.0.1: 672 | version "3.0.1" 673 | resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" 674 | integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== 675 | 676 | json-stringify-safe@^5.0.1: 677 | version "5.0.1" 678 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 679 | integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= 680 | 681 | jsonfile@^4.0.0: 682 | version "4.0.0" 683 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" 684 | integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= 685 | optionalDependencies: 686 | graceful-fs "^4.1.6" 687 | 688 | keyv@^4.0.0: 689 | version "4.5.2" 690 | resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.2.tgz#0e310ce73bf7851ec702f2eaf46ec4e3805cce56" 691 | integrity sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g== 692 | dependencies: 693 | json-buffer "3.0.1" 694 | 695 | locate-path@^5.0.0: 696 | version "5.0.0" 697 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" 698 | integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== 699 | dependencies: 700 | p-locate "^4.1.0" 701 | 702 | locate-path@^6.0.0: 703 | version "6.0.0" 704 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" 705 | integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== 706 | dependencies: 707 | p-locate "^5.0.0" 708 | 709 | lodash@^4.16.4: 710 | version "4.17.21" 711 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" 712 | integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== 713 | 714 | log-symbols@^4.1.0: 715 | version "4.1.0" 716 | resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" 717 | integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== 718 | dependencies: 719 | chalk "^4.1.0" 720 | is-unicode-supported "^0.1.0" 721 | 722 | lowercase-keys@^2.0.0: 723 | version "2.0.0" 724 | resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" 725 | integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== 726 | 727 | lru-cache@^6.0.0: 728 | version "6.0.0" 729 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 730 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== 731 | dependencies: 732 | yallist "^4.0.0" 733 | 734 | make-error@^1.1.1: 735 | version "1.3.6" 736 | resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" 737 | integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== 738 | 739 | matcher@^3.0.0: 740 | version "3.0.0" 741 | resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" 742 | integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng== 743 | dependencies: 744 | escape-string-regexp "^4.0.0" 745 | 746 | md5@^2.1.0: 747 | version "2.2.1" 748 | resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" 749 | integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= 750 | dependencies: 751 | charenc "~0.0.1" 752 | crypt "~0.0.1" 753 | is-buffer "~1.1.1" 754 | 755 | mimic-response@^1.0.0: 756 | version "1.0.1" 757 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" 758 | integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== 759 | 760 | mimic-response@^3.1.0: 761 | version "3.1.0" 762 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" 763 | integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== 764 | 765 | minimatch@^5.0.1, minimatch@^5.1.6: 766 | version "5.1.6" 767 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" 768 | integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== 769 | dependencies: 770 | brace-expansion "^2.0.1" 771 | 772 | minimist@^1.2.5: 773 | version "1.2.6" 774 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" 775 | integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== 776 | 777 | mkdirp@~0.5.1: 778 | version "0.5.5" 779 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" 780 | integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== 781 | dependencies: 782 | minimist "^1.2.5" 783 | 784 | mocha-junit-reporter@^1.23.3: 785 | version "1.23.3" 786 | resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-1.23.3.tgz#941e219dd759ed732f8641e165918aa8b167c981" 787 | integrity sha512-ed8LqbRj1RxZfjt/oC9t12sfrWsjZ3gNnbhV1nuj9R/Jb5/P3Xb4duv2eCfCDMYH+fEu0mqca7m4wsiVjsxsvA== 788 | dependencies: 789 | debug "^2.2.0" 790 | md5 "^2.1.0" 791 | mkdirp "~0.5.1" 792 | strip-ansi "^4.0.0" 793 | xml "^1.0.0" 794 | 795 | mocha-multi-reporters@^1.1.7: 796 | version "1.1.7" 797 | resolved "https://registry.yarnpkg.com/mocha-multi-reporters/-/mocha-multi-reporters-1.1.7.tgz#cc7f3f4d32f478520941d852abb64d9988587d82" 798 | integrity sha1-zH8/TTL0eFIJQdhSq7ZNmYhYfYI= 799 | dependencies: 800 | debug "^3.1.0" 801 | lodash "^4.16.4" 802 | 803 | mocha@^10.8.2: 804 | version "10.8.2" 805 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.8.2.tgz#8d8342d016ed411b12a429eb731b825f961afb96" 806 | integrity sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg== 807 | dependencies: 808 | ansi-colors "^4.1.3" 809 | browser-stdout "^1.3.1" 810 | chokidar "^3.5.3" 811 | debug "^4.3.5" 812 | diff "^5.2.0" 813 | escape-string-regexp "^4.0.0" 814 | find-up "^5.0.0" 815 | glob "^8.1.0" 816 | he "^1.2.0" 817 | js-yaml "^4.1.0" 818 | log-symbols "^4.1.0" 819 | minimatch "^5.1.6" 820 | ms "^2.1.3" 821 | serialize-javascript "^6.0.2" 822 | strip-json-comments "^3.1.1" 823 | supports-color "^8.1.1" 824 | workerpool "^6.5.1" 825 | yargs "^16.2.0" 826 | yargs-parser "^20.2.9" 827 | yargs-unparser "^2.0.0" 828 | 829 | ms@2.0.0: 830 | version "2.0.0" 831 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 832 | integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== 833 | 834 | ms@^2.1.1, ms@^2.1.3: 835 | version "2.1.3" 836 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 837 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 838 | 839 | normalize-path@^3.0.0, normalize-path@~3.0.0: 840 | version "3.0.0" 841 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 842 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 843 | 844 | normalize-url@^6.0.1: 845 | version "6.1.0" 846 | resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" 847 | integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== 848 | 849 | object-keys@^1.0.12: 850 | version "1.1.1" 851 | resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" 852 | integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== 853 | 854 | once@^1.3.0, once@^1.3.1, once@^1.4.0: 855 | version "1.4.0" 856 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 857 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 858 | dependencies: 859 | wrappy "1" 860 | 861 | p-cancelable@^2.0.0: 862 | version "2.1.1" 863 | resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" 864 | integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== 865 | 866 | p-limit@^2.2.0: 867 | version "2.3.0" 868 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" 869 | integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== 870 | dependencies: 871 | p-try "^2.0.0" 872 | 873 | p-limit@^3.0.2: 874 | version "3.1.0" 875 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" 876 | integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== 877 | dependencies: 878 | yocto-queue "^0.1.0" 879 | 880 | p-locate@^4.1.0: 881 | version "4.1.0" 882 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" 883 | integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== 884 | dependencies: 885 | p-limit "^2.2.0" 886 | 887 | p-locate@^5.0.0: 888 | version "5.0.0" 889 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" 890 | integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== 891 | dependencies: 892 | p-limit "^3.0.2" 893 | 894 | p-try@^2.0.0: 895 | version "2.2.0" 896 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" 897 | integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== 898 | 899 | path-exists@^4.0.0: 900 | version "4.0.0" 901 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" 902 | integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== 903 | 904 | pathval@^1.1.0: 905 | version "1.1.1" 906 | resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" 907 | integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== 908 | 909 | pend@~1.2.0: 910 | version "1.2.0" 911 | resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" 912 | integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= 913 | 914 | picomatch@^2.0.4: 915 | version "2.2.2" 916 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" 917 | integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== 918 | 919 | picomatch@^2.2.1: 920 | version "2.3.1" 921 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 922 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 923 | 924 | progress@^2.0.3: 925 | version "2.0.3" 926 | resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" 927 | integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== 928 | 929 | pump@^3.0.0: 930 | version "3.0.0" 931 | resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" 932 | integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== 933 | dependencies: 934 | end-of-stream "^1.1.0" 935 | once "^1.3.1" 936 | 937 | quick-lru@^5.1.1: 938 | version "5.1.1" 939 | resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" 940 | integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== 941 | 942 | randombytes@^2.1.0: 943 | version "2.1.0" 944 | resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" 945 | integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== 946 | dependencies: 947 | safe-buffer "^5.1.0" 948 | 949 | readdirp@~3.6.0: 950 | version "3.6.0" 951 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" 952 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== 953 | dependencies: 954 | picomatch "^2.2.1" 955 | 956 | require-directory@^2.1.1: 957 | version "2.1.1" 958 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 959 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= 960 | 961 | require-main-filename@^2.0.0: 962 | version "2.0.0" 963 | resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" 964 | integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== 965 | 966 | resolve-alpn@^1.0.0: 967 | version "1.2.1" 968 | resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" 969 | integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== 970 | 971 | responselike@^2.0.0: 972 | version "2.0.1" 973 | resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" 974 | integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw== 975 | dependencies: 976 | lowercase-keys "^2.0.0" 977 | 978 | roarr@^2.15.3: 979 | version "2.15.3" 980 | resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.3.tgz#65248a291a15af3ebfd767cbf7e44cb402d1d836" 981 | integrity sha512-AEjYvmAhlyxOeB9OqPUzQCo3kuAkNfuDk/HqWbZdFsqDFpapkTjiw+p4svNEoRLvuqNTxqfL+s+gtD4eDgZ+CA== 982 | dependencies: 983 | boolean "^3.0.0" 984 | detect-node "^2.0.4" 985 | globalthis "^1.0.1" 986 | json-stringify-safe "^5.0.1" 987 | semver-compare "^1.0.0" 988 | sprintf-js "^1.1.2" 989 | 990 | safe-buffer@^5.1.0: 991 | version "5.2.1" 992 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 993 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 994 | 995 | semver-compare@^1.0.0: 996 | version "1.0.0" 997 | resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" 998 | integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= 999 | 1000 | semver@^6.2.0: 1001 | version "6.3.1" 1002 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" 1003 | integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== 1004 | 1005 | semver@^7.3.2: 1006 | version "7.5.4" 1007 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" 1008 | integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== 1009 | dependencies: 1010 | lru-cache "^6.0.0" 1011 | 1012 | serialize-error@^7.0.1: 1013 | version "7.0.1" 1014 | resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" 1015 | integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw== 1016 | dependencies: 1017 | type-fest "^0.13.1" 1018 | 1019 | serialize-javascript@^6.0.2: 1020 | version "6.0.2" 1021 | resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" 1022 | integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== 1023 | dependencies: 1024 | randombytes "^2.1.0" 1025 | 1026 | set-blocking@^2.0.0: 1027 | version "2.0.0" 1028 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 1029 | integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= 1030 | 1031 | source-map-support@^0.5.17: 1032 | version "0.5.19" 1033 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" 1034 | integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== 1035 | dependencies: 1036 | buffer-from "^1.0.0" 1037 | source-map "^0.6.0" 1038 | 1039 | source-map@^0.6.0: 1040 | version "0.6.1" 1041 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 1042 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 1043 | 1044 | sprintf-js@^1.1.2: 1045 | version "1.1.2" 1046 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" 1047 | integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== 1048 | 1049 | string-width@^4.1.0, string-width@^4.2.0: 1050 | version "4.2.0" 1051 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" 1052 | integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== 1053 | dependencies: 1054 | emoji-regex "^8.0.0" 1055 | is-fullwidth-code-point "^3.0.0" 1056 | strip-ansi "^6.0.0" 1057 | 1058 | strip-ansi@^4.0.0: 1059 | version "4.0.0" 1060 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" 1061 | integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= 1062 | dependencies: 1063 | ansi-regex "^3.0.0" 1064 | 1065 | strip-ansi@^6.0.0: 1066 | version "6.0.0" 1067 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" 1068 | integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== 1069 | dependencies: 1070 | ansi-regex "^5.0.0" 1071 | 1072 | strip-json-comments@^3.1.1: 1073 | version "3.1.1" 1074 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" 1075 | integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== 1076 | 1077 | sumchecker@^3.0.1: 1078 | version "3.0.1" 1079 | resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" 1080 | integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg== 1081 | dependencies: 1082 | debug "^4.1.0" 1083 | 1084 | supports-color@^7.1.0: 1085 | version "7.1.0" 1086 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" 1087 | integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== 1088 | dependencies: 1089 | has-flag "^4.0.0" 1090 | 1091 | supports-color@^8.1.1: 1092 | version "8.1.1" 1093 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" 1094 | integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== 1095 | dependencies: 1096 | has-flag "^4.0.0" 1097 | 1098 | to-regex-range@^5.0.1: 1099 | version "5.0.1" 1100 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 1101 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 1102 | dependencies: 1103 | is-number "^7.0.0" 1104 | 1105 | ts-node@^8.10.2: 1106 | version "8.10.2" 1107 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" 1108 | integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== 1109 | dependencies: 1110 | arg "^4.1.0" 1111 | diff "^4.0.1" 1112 | make-error "^1.1.1" 1113 | source-map-support "^0.5.17" 1114 | yn "3.1.1" 1115 | 1116 | type-detect@^4.0.0, type-detect@^4.0.5: 1117 | version "4.0.8" 1118 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" 1119 | integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== 1120 | 1121 | type-fest@^0.13.1: 1122 | version "0.13.1" 1123 | resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" 1124 | integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== 1125 | 1126 | typescript@^4.1.3: 1127 | version "4.1.3" 1128 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" 1129 | integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== 1130 | 1131 | universalify@^0.1.0: 1132 | version "0.1.2" 1133 | resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" 1134 | integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== 1135 | 1136 | walkdir@^0.4.1: 1137 | version "0.4.1" 1138 | resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.4.1.tgz#dc119f83f4421df52e3061e514228a2db20afa39" 1139 | integrity sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ== 1140 | 1141 | which-module@^2.0.0: 1142 | version "2.0.0" 1143 | resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" 1144 | integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= 1145 | 1146 | workerpool@^6.5.1: 1147 | version "6.5.1" 1148 | resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" 1149 | integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== 1150 | 1151 | wrap-ansi@^6.2.0: 1152 | version "6.2.0" 1153 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" 1154 | integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== 1155 | dependencies: 1156 | ansi-styles "^4.0.0" 1157 | string-width "^4.1.0" 1158 | strip-ansi "^6.0.0" 1159 | 1160 | wrap-ansi@^7.0.0: 1161 | version "7.0.0" 1162 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" 1163 | integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== 1164 | dependencies: 1165 | ansi-styles "^4.0.0" 1166 | string-width "^4.1.0" 1167 | strip-ansi "^6.0.0" 1168 | 1169 | wrappy@1: 1170 | version "1.0.2" 1171 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 1172 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 1173 | 1174 | xml@^1.0.0: 1175 | version "1.0.1" 1176 | resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" 1177 | integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU= 1178 | 1179 | y18n@^4.0.0: 1180 | version "4.0.3" 1181 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" 1182 | integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== 1183 | 1184 | y18n@^5.0.5: 1185 | version "5.0.8" 1186 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" 1187 | integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== 1188 | 1189 | yallist@^4.0.0: 1190 | version "4.0.0" 1191 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 1192 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 1193 | 1194 | yargs-parser@^18.1.1: 1195 | version "18.1.3" 1196 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" 1197 | integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== 1198 | dependencies: 1199 | camelcase "^5.0.0" 1200 | decamelize "^1.2.0" 1201 | 1202 | yargs-parser@^20.2.2, yargs-parser@^20.2.9: 1203 | version "20.2.9" 1204 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" 1205 | integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== 1206 | 1207 | yargs-unparser@^2.0.0: 1208 | version "2.0.0" 1209 | resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" 1210 | integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== 1211 | dependencies: 1212 | camelcase "^6.0.0" 1213 | decamelize "^4.0.0" 1214 | flat "^5.0.2" 1215 | is-plain-obj "^2.1.0" 1216 | 1217 | yargs@^15.3.1: 1218 | version "15.3.1" 1219 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b" 1220 | integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA== 1221 | dependencies: 1222 | cliui "^6.0.0" 1223 | decamelize "^1.2.0" 1224 | find-up "^4.1.0" 1225 | get-caller-file "^2.0.1" 1226 | require-directory "^2.1.1" 1227 | require-main-filename "^2.0.0" 1228 | set-blocking "^2.0.0" 1229 | string-width "^4.2.0" 1230 | which-module "^2.0.0" 1231 | y18n "^4.0.0" 1232 | yargs-parser "^18.1.1" 1233 | 1234 | yargs@^16.2.0: 1235 | version "16.2.0" 1236 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" 1237 | integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== 1238 | dependencies: 1239 | cliui "^7.0.2" 1240 | escalade "^3.1.1" 1241 | get-caller-file "^2.0.5" 1242 | require-directory "^2.1.1" 1243 | string-width "^4.2.0" 1244 | y18n "^5.0.5" 1245 | yargs-parser "^20.2.2" 1246 | 1247 | yauzl@^2.10.0: 1248 | version "2.10.0" 1249 | resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" 1250 | integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= 1251 | dependencies: 1252 | buffer-crc32 "~0.2.3" 1253 | fd-slicer "~1.1.0" 1254 | 1255 | yn@3.1.1: 1256 | version "3.1.1" 1257 | resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" 1258 | integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== 1259 | 1260 | yocto-queue@^0.1.0: 1261 | version "0.1.0" 1262 | resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" 1263 | integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== 1264 | --------------------------------------------------------------------------------