-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathindex.js
More file actions
264 lines (226 loc) · 7.32 KB
/
index.js
File metadata and controls
264 lines (226 loc) · 7.32 KB
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
var Traverse = require('traverse');
var EventEmitter = require('events').EventEmitter;
var net = process.title === 'browser' ? {} : require('net');
var exports = module.exports = function (wrapper) {
var self = {};
self.sessions = {};
self.create = function () {
var id = null;
do {
id = Math.floor(
Math.random() * Math.pow(2,32)
).toString(16);
} while (self.sessions[id]);
var s = Session(id, wrapper);
self.sessions[id] = s;
return s;
};
self.destroy = function (id) {
delete self.clients[id];
};
return self;
};
var Session = exports.Session = function (id, wrapper) {
var self = new EventEmitter;
self.id = id;
self.remote = {};
var instance = self.instance =
typeof(wrapper) == 'function'
? new wrapper(self.remote, self)
: wrapper || {}
;
var scrubber = new Scrubber;
self.start = function () {
self.request('methods', [ instance ]);
};
self.request = function (method, args) {
var scrub = scrubber.scrub(args);
self.emit('request', {
method : method,
arguments : scrub.arguments,
callbacks : scrub.callbacks,
links : scrub.links
});
};
self.parse = function (line) {
var msg = null;
try { msg = JSON.parse(line) }
catch (err) {
self.emit('error', new SyntaxError(
'Error parsing JSON message: ' + JSON.stringify(line))
);
return;
}
try { self.handle(msg) }
catch (err) { self.emit('error', err) }
};
var wrapped = {};
self.handle = function (req) {
var args = scrubber.unscrub(req, function (id) {
if (!(id in wrapped)) {
// create a new function only if one hasn't already been created
// for a particular id
wrapped[id] = function () {
self.request(id, [].slice.apply(arguments));
};
}
return wrapped[id];
});
if (req.method === 'methods') {
handleMethods(args[0]);
}
else if (req.method === 'error') {
var methods = args[0];
self.emit('remoteError', methods);
}
else if (typeof req.method === 'string') {
if (self.instance.propertyIsEnumerable(req.method)) {
apply(self.instance[req.method], self.instance, args);
}
else {
self.emit('error', new Error(
'Request for non-enumerable method: ' + req.method
));
}
}
else if (typeof req.method == 'number') {
apply(scrubber.callbacks[req.method], self.instance, args);
}
}
function handleMethods (methods) {
if (typeof methods != 'object') {
methods = {};
}
// copy since assignment discards the previous refs
Object.keys(self.remote).forEach(function (key) {
delete self.remote[key];
});
Object.keys(methods).forEach(function (key) {
self.remote[key] = methods[key];
});
self.emit('remote', self.remote);
self.emit('ready');
}
function apply(f, obj, args) {
try { f.apply(obj, args) }
catch (err) { self.emit('error', err) }
}
return self;
};
// scrub callbacks out of requests in order to call them again later
var Scrubber = exports.Scrubber = function () {
var self = {};
self.callbacks = {};
var wrapped = [];
var cbId = 0;
// Take the functions out and note them for future use
self.scrub = function (obj) {
var paths = {};
var links = [];
var args = Traverse(obj).map(function (node) {
if (typeof(node) == 'function') {
var i = wrapped.indexOf(node);
if (i >= 0 && !(i in paths)) {
// Keep previous function IDs only for the first function
// found. This is somewhat suboptimal but the alternatives
// are worse.
paths[i] = this.path;
}
else {
self.callbacks[cbId] = node;
wrapped.push(node);
paths[cbId] = this.path;
cbId++;
}
this.update('[Function]');
}
else if (this.circular) {
links.push({ from : this.circular.path, to : this.path });
this.update('[Circular]');
}
});
return {
arguments : args,
callbacks : paths,
links : links
};
};
// Replace callbacks. The supplied function should take a callback id and
// return a callback of its own.
self.unscrub = function (msg, f) {
var args = msg.arguments || [];
Object.keys(msg.callbacks || {}).forEach(function (strId) {
var id = parseInt(strId,10);
var path = msg.callbacks[id];
args = setAt(args, path, f(id));
});
(msg.links || []).forEach(function (link) {
var value = getAt(args, link.from);
args = setAt(args, link.to, value);
});
return args;
};
function setAt (ref, path, value) {
var node = ref;
path.slice(0,-1).forEach(function (key) {
node = node[key];
});
var last = path.slice(-1)[0];
if (last === undefined) {
return value;
}
else {
node[last] = value;
return ref;
}
}
function getAt (node, path) {
path.forEach(function (key) {
node = node[key];
});
return node;
}
return self;
}
var parseArgs = exports.parseArgs = function (argv) {
var params = {};
[].slice.call(argv).forEach(function (arg) {
if (typeof arg === 'string') {
if (arg.match(/^\d+$/)) {
params.port = parseInt(arg, 10);
}
else {
params.host = arg;
}
}
else if (typeof arg === 'number') {
params.port = arg;
}
else if (typeof arg === 'function') {
params.block = arg;
}
else if (typeof arg === 'object') {
if (arg.__proto__ === Object.prototype) {
// merge vanilla objects into params
Object.keys(arg).forEach(function (key) {
params[key] = arg[key];
});
}
else if (net.Stream && arg instanceof net.Stream) {
params.stream = arg;
}
else {
// and non-Stream, non-vanilla objects are probably servers
params.server = arg;
}
}
else if (typeof arg === 'undefined') {
// ignore
}
else {
throw new Error('Not sure what to do about '
+ typeof arg + ' objects');
}
});
return params;
};