• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  Boolean,
5} = primordials;
6
7const { Interface } = require('readline');
8const path = require('path');
9const fs = require('fs');
10const os = require('os');
11let debug = require('internal/util/debuglog').debuglog('repl', (fn) => {
12  debug = fn;
13});
14const { clearTimeout, setTimeout } = require('timers');
15
16// XXX(chrisdickinson): The 15ms debounce value is somewhat arbitrary.
17// The debounce is to guard against code pasted into the REPL.
18const kDebounceHistoryMS = 15;
19
20module.exports = setupHistory;
21
22function _writeToOutput(repl, message) {
23  repl._writeToOutput(message);
24  repl._refreshLine();
25}
26
27function setupHistory(repl, historyPath, ready) {
28  // Empty string disables persistent history
29  if (typeof historyPath === 'string')
30    historyPath = historyPath.trim();
31
32  if (historyPath === '') {
33    repl._historyPrev = _replHistoryMessage;
34    return ready(null, repl);
35  }
36
37  if (!historyPath) {
38    try {
39      historyPath = path.join(os.homedir(), '.node_repl_history');
40    } catch (err) {
41      _writeToOutput(repl, '\nError: Could not get the home directory.\n' +
42        'REPL session history will not be persisted.\n');
43
44      debug(err.stack);
45      repl._historyPrev = _replHistoryMessage;
46      return ready(null, repl);
47    }
48  }
49
50  let timer = null;
51  let writing = false;
52  let pending = false;
53  repl.pause();
54  // History files are conventionally not readable by others:
55  // https://github.com/nodejs/node/issues/3392
56  // https://github.com/nodejs/node/pull/3394
57  fs.open(historyPath, 'a+', 0o0600, oninit);
58
59  function oninit(err, hnd) {
60    if (err) {
61      // Cannot open history file.
62      // Don't crash, just don't persist history.
63      _writeToOutput(repl, '\nError: Could not open history file.\n' +
64        'REPL session history will not be persisted.\n');
65      debug(err.stack);
66
67      repl._historyPrev = _replHistoryMessage;
68      repl.resume();
69      return ready(null, repl);
70    }
71    fs.close(hnd, onclose);
72  }
73
74  function onclose(err) {
75    if (err) {
76      return ready(err);
77    }
78    fs.readFile(historyPath, 'utf8', onread);
79  }
80
81  function onread(err, data) {
82    if (err) {
83      return ready(err);
84    }
85
86    if (data) {
87      repl.history = data.split(/[\n\r]+/, repl.historySize);
88    } else {
89      repl.history = [];
90    }
91
92    fs.open(historyPath, 'r+', onhandle);
93  }
94
95  function onhandle(err, hnd) {
96    if (err) {
97      return ready(err);
98    }
99    fs.ftruncate(hnd, 0, (err) => {
100      repl._historyHandle = hnd;
101      repl.on('line', online);
102
103      // Reading the file data out erases it
104      repl.once('flushHistory', function() {
105        repl.resume();
106        ready(null, repl);
107      });
108      flushHistory();
109    });
110  }
111
112  // ------ history listeners ------
113  function online(line) {
114    repl._flushing = true;
115
116    if (timer) {
117      clearTimeout(timer);
118    }
119
120    timer = setTimeout(flushHistory, kDebounceHistoryMS);
121  }
122
123  function flushHistory() {
124    timer = null;
125    if (writing) {
126      pending = true;
127      return;
128    }
129    writing = true;
130    const historyData = repl.history.join(os.EOL);
131    fs.write(repl._historyHandle, historyData, 0, 'utf8', onwritten);
132  }
133
134  function onwritten(err, data) {
135    writing = false;
136    if (pending) {
137      pending = false;
138      online();
139    } else {
140      repl._flushing = Boolean(timer);
141      if (!repl._flushing) {
142        repl.emit('flushHistory');
143      }
144    }
145  }
146}
147
148function _replHistoryMessage() {
149  if (this.history.length === 0) {
150    _writeToOutput(
151      this,
152      '\nPersistent history support disabled. ' +
153      'Set the NODE_REPL_HISTORY environment\nvariable to ' +
154      'a valid, user-writable path to enable.\n'
155    );
156  }
157  this._historyPrev = Interface.prototype._historyPrev;
158  return this._historyPrev();
159}
160