summaryrefslogtreecommitdiffhomepage
path: root/app/shutdown-handler.js
blob: afd10f61b239d6a4684cbebafe17dc5517695c0e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// @flow
import { app, ipcMain, ipcRenderer } from 'electron';
import { log } from './lib/platform';
import type { WebContents } from 'electron';

// The timeout before the shutdown is enforced
const SHUTDOWN_TIMEOUT = 3000;

/**
 * Shutdown coordinator postpones the application shutdown until ShutdownHandler finished the
 * shutdown sequence.
 *
 * ShutdownCoordinator can only be created from main process.
 */
class ShutdownCoordinator {
  _canQuit = false;
  _isQuitting = false;
  _webContents: WebContents;
  _shutdownTimeout: ?TimeoutID;

  constructor(webContents: WebContents) {
    this._webContents = webContents;

    app.on('before-quit', this._onBeforeQuit);
    ipcMain.on('app-shutdown-reply', this._onShutdownReply);
  }

  dispose() {
    app
      .removeListener('before-quit', this._onBeforeQuit)
      .removeListener('app-shutdown-reply', this._onShutdownReply);
  }

  _onBeforeQuit = (event) => {
    if (!this._canQuit) {
      // make sure we don't call the shutdown handler twice
      if (!this._isQuitting) {
        this._isQuitting = true;

        // start timer to force shutdown if the renderer process is not able to handle shutdown in
        // a timely manner.
        this._shutdownTimeout = setTimeout(this._onShutdownTimeout, SHUTDOWN_TIMEOUT);

        this._webContents.send('app-shutdown');
      }

      event.preventDefault();
    }
  };

  _onShutdownReply = () => {
    const shutdownTimeout = this._shutdownTimeout;
    if (shutdownTimeout) {
      clearTimeout(shutdownTimeout);
    }
    this._grantShutdown();
  };

  _onShutdownTimeout = () => {
    log.warn('It took longer than expected to shutdown. Forcing shutdown now.');
    this._grantShutdown();
  };

  _grantShutdown() {
    this._canQuit = true;
    app.quit();
  }
}

/**
 * Executes the provided closure when application is about to quit.
 * It works in tandem with ShutdownCoordinator and can only be used in renderer process.
 * The shutdown will be postponed until the Promise returned from closure is resolved.
 * ShutdownCoordinator will force the shutdown if the returned Promise is not resolved within
 * the allocated time (see SHUTDOWN_TIMEOUT).
 */
function setShutdownHandler(handler: () => Promise<void>) {
  ipcRenderer.once('app-shutdown', async (event) => {
    try {
      await handler();
    } catch (error) {
      log.warn(`An error occurred in shutdown handler: ${error.message}`);
    }
    event.sender.send('app-shutdown-reply');
  });
}

if (ipcMain) {
  module.exports = { ShutdownCoordinator };
} else {
  module.exports = { setShutdownHandler };
}