diff --git a/packages/core/package.json b/packages/core/package.json index 50fe27c4..a8a5f9f9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -15,6 +15,7 @@ "./src/utils/platform/transport.js": "./src/utils/platform/transport.web.js", "./src/utils/platform/logging.js": "./src/utils/platform/logging.web.js", "./src/utils/platform/random.js": "./src/utils/platform/random.web.js", + "./src/utils/platform/exit-hook.js": "./src/utils/platform/exit-hook.web.js", "./src/storage/json-file.js": false }, "distOnlyFields": { diff --git a/packages/core/src/storage/json-file.ts b/packages/core/src/storage/json-file.ts index 54b5b0be..04b0bc23 100644 --- a/packages/core/src/storage/json-file.ts +++ b/packages/core/src/storage/json-file.ts @@ -1,10 +1,9 @@ // eslint-disable-next-line no-restricted-imports import * as fs from 'fs' +import { beforeExit } from '../utils/index.js' import { JsonMemoryStorage } from './json.js' -const EVENTS = ['exit', 'SIGINT', 'SIGUSR1', 'SIGUSR2', 'uncaughtException', 'SIGTERM'] - /** * mtcute storage that stores data in a JSON file. * @@ -20,7 +19,7 @@ const EVENTS = ['exit', 'SIGINT', 'SIGUSR1', 'SIGUSR2', 'uncaughtException', 'SI export class JsonFileStorage extends JsonMemoryStorage { private readonly _filename: string private readonly _safe: boolean - private readonly _cleanup: boolean + private readonly _cleanupUnregister?: () => void constructor( filename: string, @@ -50,11 +49,9 @@ export class JsonFileStorage extends JsonMemoryStorage { this._filename = filename this._safe = params?.safe ?? true - this._cleanup = params?.cleanup ?? true - if (this._cleanup) { - this._onProcessExit = this._onProcessExit.bind(this) - EVENTS.forEach((event) => process.on(event, this._onProcessExit)) + if (params?.cleanup !== false) { + this._cleanupUnregister = beforeExit(() => this._onProcessExit()) } } @@ -82,12 +79,8 @@ export class JsonFileStorage extends JsonMemoryStorage { }) } - private _processExitHandled = false private _onProcessExit(): void { // on exit handler must be synchronous, thus we use sync methods here - if (this._processExitHandled) return - this._processExitHandled = true - try { fs.writeFileSync(this._filename, this._saveJson()) } catch (e) {} @@ -100,8 +93,6 @@ export class JsonFileStorage extends JsonMemoryStorage { } destroy(): void { - if (this._cleanup) { - EVENTS.forEach((event) => process.off(event, this._onProcessExit)) - } + this._cleanupUnregister?.() } } diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 3cffd965..20b68940 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -16,6 +16,7 @@ export * from './lru-map.js' export * from './lru-set.js' export * from './misc-utils.js' export * from './peer-utils.js' +export * from './platform/exit-hook.js' export * from './sorted-array.js' export * from './string-session.js' export * from './tl-json.js' diff --git a/packages/core/src/utils/platform/exit-hook.ts b/packages/core/src/utils/platform/exit-hook.ts new file mode 100644 index 00000000..3adbb349 --- /dev/null +++ b/packages/core/src/utils/platform/exit-hook.ts @@ -0,0 +1,53 @@ +// roughly based on https://github.com/sindresorhus/exit-hook/blob/main/index.js, MIT license + +let installed = false +let handled = false + +const callbacks = new Set<() => void>() + +function exit(shouldManuallyExit: boolean, signal: number, event: string) { + return function eventHandler() { + if (handled) { + return + } + + handled = true + + const exitCode = 128 + signal + + for (const callback of callbacks) { + callback() + } + + if (shouldManuallyExit) { + // if the user has some custom handlers after us, we don't want to exit the process + + const listeners = process.rawListeners(event) + const idx = listeners.indexOf(eventHandler) + + if (idx === listeners.length - 1) { + process.exit(exitCode) + } + } + } +} + +export function beforeExit(fn: () => void): () => void { + // unsupported platform + if (typeof process === 'undefined') return () => {} + + if (!installed) { + installed = true + + process.on('beforeExit', exit(true, -128, 'beforeExit')) + process.on('SIGINT', exit(true, 2, 'SIGINT')) + process.on('SIGTERM', exit(true, 15, 'SIGINT')) + process.on('exit', exit(false, 15, 'exit')) + } + + callbacks.add(fn) + + return () => { + callbacks.delete(fn) + } +} diff --git a/packages/core/src/utils/platform/exit-hook.web.ts b/packages/core/src/utils/platform/exit-hook.web.ts new file mode 100644 index 00000000..ce8f3e1e --- /dev/null +++ b/packages/core/src/utils/platform/exit-hook.web.ts @@ -0,0 +1,21 @@ +const callbacks = new Set<() => void>() + +let registered = false + +export function beforeExit(fn: () => void): () => void { + if (!registered) { + registered = true + + window.addEventListener('beforeunload', () => { + for (const callback of callbacks) { + callback() + } + }) + } + + callbacks.add(fn) + + return () => { + callbacks.delete(fn) + } +}