forked from meteor/meteor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathshell-client.js
171 lines (141 loc) · 3.97 KB
/
shell-client.js
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
var assert = require("assert");
var fs = require("fs");
var net = require("net");
var eachline = require("eachline");
var chalk = require("chalk");
var EOL = require("os").EOL;
var server = require("./server/shell-server.js");
var EXITING_MESSAGE = server.EXITING_MESSAGE;
var getInfoFile = server.getInfoFile;
// Invoked by the process running `meteor shell` to attempt to connect to
// the server via the socket file.
exports.connect = function connect(shellDir) {
new Client(shellDir).connect();
};
function Client(shellDir) {
var self = this;
assert.ok(self instanceof Client);
self.shellDir = shellDir;
self.exitOnClose = false;
self.firstTimeConnecting = true;
self.connected = false;
self.reconnectCount = 0;
}
var Cp = Client.prototype;
Cp.reconnect = function reconnect(delay) {
var self = this;
// Display the "Server unavailable" warning only on the third attempt
// to reconnect, so it doesn't get shown for successful reconnects.
if (++self.reconnectCount === 3) {
console.error(chalk.yellow(
"Server unavailable (waiting to reconnect)"
));
}
if (!self.reconnectTimer) {
self.reconnectTimer = setTimeout(function() {
delete self.reconnectTimer;
self.connect();
}, delay || 100);
}
};
Cp.connect = function connect() {
var self = this;
var infoFile = getInfoFile(self.shellDir);
fs.readFile(infoFile, "utf8", function(err, json) {
if (err) {
return self.reconnect();
}
try {
var info = JSON.parse(json);
} catch (err) {
return self.reconnect();
}
if (info.status !== "enabled") {
if (self.firstTimeConnecting) {
return self.reconnect();
}
if (info.reason) {
console.error(info.reason);
}
console.error(EXITING_MESSAGE);
process.exit(0);
}
self.setUpSocket(
net.connect(info.port, "127.0.0.1"),
info.key
);
});
};
Cp.setUpSocket = function setUpSocket(sock, key) {
var self = this;
self.sock = sock;
// Put STDIN into "flowing mode":
// http://nodejs.org/api/stream.html#stream_compatibility_with_older_node_versions
process.stdin.resume();
function onConnect() {
self.firstTimeConnecting = false;
self.reconnectCount = 0;
self.connected = true;
// Sending a JSON-stringified options object (even just an empty
// object) over the socket is required to start the REPL session.
sock.write(JSON.stringify({
terminal: ! process.env.EMACS,
key: key
}));
process.stderr.write(shellBanner());
process.stdin.pipe(sock);
process.stdin.setRawMode(true);
}
function onClose() {
tearDown();
// If we received the special EXITING_MESSAGE just before the socket
// closed, then exit the shell instead of reconnecting.
if (self.exitOnClose) {
process.exit(0);
} else {
self.reconnect();
}
}
function onError(err) {
tearDown();
self.reconnect();
}
function tearDown() {
self.connected = false;
process.stdin.setRawMode(false);
process.stdin.unpipe(sock);
sock.unpipe(process.stdout);
sock.removeListener("connect", onConnect);
sock.removeListener("close", onClose);
sock.removeListener("error", onError);
sock.end();
}
sock.pipe(process.stdout);
eachline(sock, "utf8", function(line) {
self.exitOnClose = line.indexOf(EXITING_MESSAGE) >= 0;
});
sock.on("connect", onConnect);
sock.on("close", onClose);
sock.on("error", onError);
};
function shellBanner() {
var bannerLines = [
"",
"Welcome to the server-side interactive shell!"
];
if (! process.env.EMACS) {
// Tab completion sadly does not work in Emacs.
bannerLines.push(
"",
"Tab completion is enabled for global variables."
);
}
bannerLines.push(
"",
"Type .reload to restart the server and the shell.",
"Type .exit to disconnect from the server and leave the shell.",
"Type .help for additional help.",
EOL
);
return chalk.green(bannerLines.join(EOL));
}