napi-rs/examples/napi/electron.js
Bo e47c13f177
fix(napi): check if the tokio runtime exists when registering the module
And recreate it if it does not exist.

In windows, electron renderer process will crash if:
1. Import some NAPI module that enable `tokio-rt` flag in renderer process.
2. Reload the page.
3. Call a function imported from that NAPI module.

Because the tokio runtime will be dropped when reloading the page, and won't create again, but currently we assume that the runtime must exist in tokio-based `within_runtime_if_available`.
This will cause some panic like this:

```
thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', napi-rs\crates\napi\src\tokio_runtime.rs:72:42
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Error: Renderer process crashed: crashed, exitCode: -529697949
    at EventEmitter.<anonymous> (napi-rs\examples\napi\electron.js:33:9)
    at EventEmitter.emit (node:events:525:35)
```
2023-03-28 12:03:00 +08:00

95 lines
2.1 KiB
JavaScript

const assert = require('assert')
const { readFileSync } = require('fs')
const { app, BrowserWindow, ipcMain } = require('electron')
const {
readFileAsync,
callThreadsafeFunction,
withAbortController,
createExternalTypedArray,
} = require('./index')
const FILE_CONTENT = readFileSync(__filename, 'utf8')
const createWindowAndReload = async () => {
await app.whenReady()
const win = new BrowserWindow({
width: 800,
height: 600,
show: false,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
})
await win.loadFile('./electron-renderer/index.html')
await new Promise((resolve, reject) => {
win.webContents.on('render-process-gone', (e, detail) => {
reject(
new Error(
`Renderer process crashed: ${detail.reason}, exitCode: ${detail.exitCode}`,
),
)
})
// reload to check if there is any crash
win.reload()
// Wait for a while to make sure if a crash happens, the 'resolve' function should be called after the crash
setTimeout(() => {
// make sure the renderer process is still alive
ipcMain.once('pong', () => resolve())
win.webContents.send('ping')
}, 500)
})
}
async function main() {
const ctrl = new AbortController()
const promise = withAbortController(1, 2, ctrl.signal)
try {
ctrl.abort()
await promise
throw new Error('Should throw AbortError')
} catch (err) {
assert(err.message === 'AbortError')
}
const buf = await readFileAsync(__filename)
assert(FILE_CONTENT === buf.toString('utf8'))
const value = await new Promise((resolve, reject) => {
let i = 0
let value = 0
callThreadsafeFunction((err, v) => {
if (err != null) {
reject(err)
return
}
i++
value += v
if (i === 100) {
resolve(value)
}
})
})
assert(
value ===
Array.from({ length: 100 }, (_, i) => i + 1).reduce((a, b) => a + b),
)
console.info(createExternalTypedArray())
}
Promise.all([main(), createWindowAndReload()])
.then(() => {
process.exit(0)
})
.catch((e) => {
console.error(e)
process.exit(1)
})