mongodb-livedata-server 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/livedata_server.d.ts +4 -4
- package/dist/livedata_server.js +11 -11
- package/dist/meteor/binary-heap/max_heap.d.ts +31 -31
- package/dist/meteor/binary-heap/max_heap.js +186 -186
- package/dist/meteor/binary-heap/min_heap.d.ts +6 -6
- package/dist/meteor/binary-heap/min_heap.js +17 -17
- package/dist/meteor/binary-heap/min_max_heap.d.ts +11 -11
- package/dist/meteor/binary-heap/min_max_heap.js +48 -48
- package/dist/meteor/callback-hook/hook.d.ts +11 -11
- package/dist/meteor/callback-hook/hook.js +78 -78
- package/dist/meteor/ddp/crossbar.d.ts +15 -15
- package/dist/meteor/ddp/crossbar.js +136 -136
- package/dist/meteor/ddp/heartbeat.d.ts +19 -19
- package/dist/meteor/ddp/heartbeat.js +77 -77
- package/dist/meteor/ddp/livedata_server.d.ts +141 -142
- package/dist/meteor/ddp/livedata_server.js +403 -403
- package/dist/meteor/ddp/method-invocation.d.ts +35 -35
- package/dist/meteor/ddp/method-invocation.js +72 -72
- package/dist/meteor/ddp/random-stream.d.ts +8 -8
- package/dist/meteor/ddp/random-stream.js +100 -100
- package/dist/meteor/ddp/session-collection-view.d.ts +20 -20
- package/dist/meteor/ddp/session-collection-view.js +106 -106
- package/dist/meteor/ddp/session-document-view.d.ts +8 -8
- package/dist/meteor/ddp/session-document-view.js +82 -82
- package/dist/meteor/ddp/session.d.ts +75 -75
- package/dist/meteor/ddp/session.js +590 -590
- package/dist/meteor/ddp/stream_server.d.ts +20 -21
- package/dist/meteor/ddp/stream_server.js +181 -181
- package/dist/meteor/ddp/subscription.d.ts +94 -94
- package/dist/meteor/ddp/subscription.js +370 -370
- package/dist/meteor/ddp/utils.d.ts +8 -8
- package/dist/meteor/ddp/utils.js +104 -104
- package/dist/meteor/ddp/writefence.d.ts +20 -20
- package/dist/meteor/ddp/writefence.js +111 -111
- package/dist/meteor/diff-sequence/diff.d.ts +17 -17
- package/dist/meteor/diff-sequence/diff.js +257 -257
- package/dist/meteor/ejson/ejson.d.ts +82 -82
- package/dist/meteor/ejson/ejson.js +568 -569
- package/dist/meteor/ejson/stringify.d.ts +2 -2
- package/dist/meteor/ejson/stringify.js +119 -119
- package/dist/meteor/ejson/utils.d.ts +12 -12
- package/dist/meteor/ejson/utils.js +42 -42
- package/dist/meteor/mongo/caching_change_observer.d.ts +16 -16
- package/dist/meteor/mongo/caching_change_observer.js +63 -63
- package/dist/meteor/mongo/doc_fetcher.d.ts +7 -7
- package/dist/meteor/mongo/doc_fetcher.js +53 -53
- package/dist/meteor/mongo/geojson_utils.d.ts +3 -3
- package/dist/meteor/mongo/geojson_utils.js +40 -41
- package/dist/meteor/mongo/live_connection.d.ts +28 -28
- package/dist/meteor/mongo/live_connection.js +264 -264
- package/dist/meteor/mongo/live_cursor.d.ts +25 -25
- package/dist/meteor/mongo/live_cursor.js +60 -60
- package/dist/meteor/mongo/minimongo_common.d.ts +84 -84
- package/dist/meteor/mongo/minimongo_common.js +1998 -1998
- package/dist/meteor/mongo/minimongo_matcher.d.ts +23 -23
- package/dist/meteor/mongo/minimongo_matcher.js +283 -283
- package/dist/meteor/mongo/minimongo_sorter.d.ts +16 -16
- package/dist/meteor/mongo/minimongo_sorter.js +268 -268
- package/dist/meteor/mongo/observe_driver_utils.d.ts +9 -9
- package/dist/meteor/mongo/observe_driver_utils.js +72 -73
- package/dist/meteor/mongo/observe_multiplexer.d.ts +46 -46
- package/dist/meteor/mongo/observe_multiplexer.js +203 -203
- package/dist/meteor/mongo/oplog-observe-driver.d.ts +68 -68
- package/dist/meteor/mongo/oplog-observe-driver.js +918 -918
- package/dist/meteor/mongo/oplog_tailing.d.ts +35 -35
- package/dist/meteor/mongo/oplog_tailing.js +352 -352
- package/dist/meteor/mongo/oplog_v2_converter.d.ts +1 -1
- package/dist/meteor/mongo/oplog_v2_converter.js +125 -126
- package/dist/meteor/mongo/polling_observe_driver.d.ts +30 -30
- package/dist/meteor/mongo/polling_observe_driver.js +216 -221
- package/dist/meteor/mongo/synchronous-cursor.d.ts +17 -17
- package/dist/meteor/mongo/synchronous-cursor.js +261 -261
- package/dist/meteor/mongo/synchronous-queue.d.ts +13 -13
- package/dist/meteor/mongo/synchronous-queue.js +110 -110
- package/dist/meteor/ordered-dict/ordered_dict.d.ts +31 -31
- package/dist/meteor/ordered-dict/ordered_dict.js +198 -198
- package/dist/meteor/random/AbstractRandomGenerator.d.ts +42 -42
- package/dist/meteor/random/AbstractRandomGenerator.js +92 -92
- package/dist/meteor/random/AleaRandomGenerator.d.ts +13 -13
- package/dist/meteor/random/AleaRandomGenerator.js +90 -90
- package/dist/meteor/random/NodeRandomGenerator.d.ts +16 -16
- package/dist/meteor/random/NodeRandomGenerator.js +42 -42
- package/dist/meteor/random/createAleaGenerator.d.ts +2 -2
- package/dist/meteor/random/createAleaGenerator.js +32 -32
- package/dist/meteor/random/createRandom.d.ts +1 -1
- package/dist/meteor/random/createRandom.js +22 -22
- package/dist/meteor/random/main.d.ts +1 -1
- package/dist/meteor/random/main.js +12 -12
- package/dist/meteor/types.d.ts +1 -1
- package/dist/meteor/types.js +2 -2
- package/package.json +5 -5
|
@@ -1,590 +1,590 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.DDPSession = void 0;
|
|
7
|
-
const method_invocation_1 = require("./method-invocation");
|
|
8
|
-
const writefence_1 = require("./writefence");
|
|
9
|
-
const double_ended_queue_1 = __importDefault(require("double-ended-queue"));
|
|
10
|
-
const main_1 = require("../random/main");
|
|
11
|
-
const livedata_server_1 = require("./livedata_server");
|
|
12
|
-
const heartbeat_1 = require("./heartbeat");
|
|
13
|
-
const utils_1 = require("./utils");
|
|
14
|
-
const diff_1 = require("../diff-sequence/diff");
|
|
15
|
-
const session_collection_view_1 = require("./session-collection-view");
|
|
16
|
-
const subscription_1 = require("./subscription");
|
|
17
|
-
class DDPSession {
|
|
18
|
-
constructor(server, version, socket, options) {
|
|
19
|
-
this.protocol_handlers = {
|
|
20
|
-
sub: async function (msg) {
|
|
21
|
-
var self = this;
|
|
22
|
-
// reject malformed messages
|
|
23
|
-
if (typeof (msg.id) !== "string" ||
|
|
24
|
-
typeof (msg.name) !== "string" ||
|
|
25
|
-
(('params' in msg) && !(msg.params instanceof Array))) {
|
|
26
|
-
self.sendError("Malformed subscription", msg);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
if (!self.server.publish_handlers[msg.name]) {
|
|
30
|
-
self.send({
|
|
31
|
-
msg: 'nosub', id: msg.id,
|
|
32
|
-
error: (0, livedata_server_1.ddpError)(404, `Subscription '${msg.name}' not found`)
|
|
33
|
-
});
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
if (self._namedSubs.has(msg.id))
|
|
37
|
-
// subs are idempotent, or rather, they are ignored if a sub
|
|
38
|
-
// with that id already exists. this is important during
|
|
39
|
-
// reconnect.
|
|
40
|
-
return;
|
|
41
|
-
// XXX It'd be much better if we had generic hooks where any package can
|
|
42
|
-
// hook into subscription handling, but in the mean while we special case
|
|
43
|
-
// ddp-rate-limiter package. This is also done for weak requirements to
|
|
44
|
-
// add the ddp-rate-limiter package in case we don't have Accounts. A
|
|
45
|
-
// user trying to use the ddp-rate-limiter must explicitly require it.
|
|
46
|
-
/* if (Package['ddp-rate-limiter']) {
|
|
47
|
-
var DDPRateLimiter = Package['ddp-rate-limiter'].DDPRateLimiter;
|
|
48
|
-
var rateLimiterInput = {
|
|
49
|
-
userId: self.userId,
|
|
50
|
-
clientAddress: self.connectionHandle.clientAddress,
|
|
51
|
-
type: "subscription",
|
|
52
|
-
name: msg.name,
|
|
53
|
-
connectionId: self.id
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
DDPRateLimiter._increment(rateLimiterInput);
|
|
57
|
-
var rateLimitResult = DDPRateLimiter._check(rateLimiterInput);
|
|
58
|
-
if (!rateLimitResult.allowed) {
|
|
59
|
-
self.send({
|
|
60
|
-
msg: 'nosub', id: msg.id,
|
|
61
|
-
error: ddpError(
|
|
62
|
-
'too-many-requests',
|
|
63
|
-
DDPRateLimiter.getErrorMessage(rateLimitResult),
|
|
64
|
-
{timeToReset: rateLimitResult.timeToReset})
|
|
65
|
-
});
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
}*/
|
|
69
|
-
var handler = self.server.publish_handlers[msg.name];
|
|
70
|
-
self._startSubscription(handler, msg.id, msg.params, msg.name);
|
|
71
|
-
},
|
|
72
|
-
unsub: async function (msg) {
|
|
73
|
-
var self = this;
|
|
74
|
-
self._stopSubscription(msg.id);
|
|
75
|
-
},
|
|
76
|
-
method: async function (msg) {
|
|
77
|
-
var self = this;
|
|
78
|
-
// Reject malformed messages.
|
|
79
|
-
// For now, we silently ignore unknown attributes,
|
|
80
|
-
// for forwards compatibility.
|
|
81
|
-
if (typeof (msg.id) !== "string" ||
|
|
82
|
-
typeof (msg.method) !== "string" ||
|
|
83
|
-
(('params' in msg) && !(msg.params instanceof Array)) ||
|
|
84
|
-
(('randomSeed' in msg) && (typeof msg.randomSeed !== "string"))) {
|
|
85
|
-
self.sendError("Malformed method invocation", msg);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
var randomSeed = msg.randomSeed || null;
|
|
89
|
-
// Set up to mark the method as satisfied once all observers
|
|
90
|
-
// (and subscriptions) have reacted to any writes that were
|
|
91
|
-
// done.
|
|
92
|
-
var fence = new writefence_1._WriteFence();
|
|
93
|
-
fence.onAllCommitted(function () {
|
|
94
|
-
// Retire the fence so that future writes are allowed.
|
|
95
|
-
// This means that callbacks like timers are free to use
|
|
96
|
-
// the fence, and if they fire before it's armed (for
|
|
97
|
-
// example, because the method waits for them) their
|
|
98
|
-
// writes will be included in the fence.
|
|
99
|
-
fence.retire();
|
|
100
|
-
self.send({ msg: 'updated', methods: [msg.id] });
|
|
101
|
-
});
|
|
102
|
-
// Find the handler
|
|
103
|
-
var handler = self.server.method_handlers[msg.method];
|
|
104
|
-
if (!handler) {
|
|
105
|
-
self.send({ msg: 'result', id: msg.id, error: (0, livedata_server_1.ddpError)(404, `Method '${msg.method}' not found`) });
|
|
106
|
-
fence.arm();
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
var setUserId = function (userId) {
|
|
110
|
-
self._setUserId(userId);
|
|
111
|
-
};
|
|
112
|
-
var invocation = new method_invocation_1.MethodInvocation({
|
|
113
|
-
isSimulation: false,
|
|
114
|
-
userId: self.userId,
|
|
115
|
-
setUserId: setUserId,
|
|
116
|
-
connection: self.connectionHandle,
|
|
117
|
-
randomSeed: randomSeed
|
|
118
|
-
});
|
|
119
|
-
const oldInvocation = livedata_server_1.DDP._CurrentMethodInvocation;
|
|
120
|
-
const oldFence = writefence_1._WriteFence._CurrentWriteFence;
|
|
121
|
-
livedata_server_1.DDP._CurrentMethodInvocation = invocation;
|
|
122
|
-
writefence_1._WriteFence._CurrentWriteFence = fence;
|
|
123
|
-
function finish() {
|
|
124
|
-
livedata_server_1.DDP._CurrentMethodInvocation = oldInvocation;
|
|
125
|
-
writefence_1._WriteFence._CurrentWriteFence = oldFence;
|
|
126
|
-
fence.arm();
|
|
127
|
-
}
|
|
128
|
-
const payload = {
|
|
129
|
-
msg: "result",
|
|
130
|
-
id: msg.id,
|
|
131
|
-
result: undefined,
|
|
132
|
-
error: undefined
|
|
133
|
-
};
|
|
134
|
-
handler.apply(invocation, msg.params).then((result) => {
|
|
135
|
-
finish();
|
|
136
|
-
if (result !== undefined) {
|
|
137
|
-
payload.result = result;
|
|
138
|
-
}
|
|
139
|
-
self.send(payload);
|
|
140
|
-
}, (exception) => {
|
|
141
|
-
finish();
|
|
142
|
-
if (exception && !exception.isClientSafe)
|
|
143
|
-
console.error(`Exception while invoking method '${msg.method}'`, exception);
|
|
144
|
-
payload.error = (0, livedata_server_1.wrapInternalException)(exception, `while invoking method '${msg.method}'`);
|
|
145
|
-
self.send(payload);
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
var self = this;
|
|
150
|
-
self.id = main_1.Random.id();
|
|
151
|
-
self.server = server;
|
|
152
|
-
self.version = version;
|
|
153
|
-
self.initialized = false;
|
|
154
|
-
self.socket = socket;
|
|
155
|
-
// Set to null when the session is destroyed. Multiple places below
|
|
156
|
-
// use this to determine if the session is alive or not.
|
|
157
|
-
self.inQueue = new double_ended_queue_1.default();
|
|
158
|
-
self.workerRunning = false;
|
|
159
|
-
// Sub objects for active subscriptions
|
|
160
|
-
self._namedSubs = new Map();
|
|
161
|
-
self._universalSubs = [];
|
|
162
|
-
self.userId = null;
|
|
163
|
-
self.collectionViews = new Map();
|
|
164
|
-
// Set this to false to not send messages when collectionViews are
|
|
165
|
-
// modified. This is done when rerunning subs in _setUserId and those messages
|
|
166
|
-
// are calculated via a diff instead.
|
|
167
|
-
self._isSending = true;
|
|
168
|
-
// If this is true, don't start a newly-created universal publisher on this
|
|
169
|
-
// session. The session will take care of starting it when appropriate.
|
|
170
|
-
self._dontStartNewUniversalSubs = false;
|
|
171
|
-
// When we are rerunning subscriptions, any ready messages
|
|
172
|
-
// we want to buffer up for when we are done rerunning subscriptions
|
|
173
|
-
self._pendingReady = [];
|
|
174
|
-
// List of callbacks to call when this connection is closed.
|
|
175
|
-
self._closeCallbacks = [];
|
|
176
|
-
// XXX HACK: If a sockjs connection, save off the URL. This is
|
|
177
|
-
// temporary and will go away in the near future.
|
|
178
|
-
self._socketUrl = socket.url;
|
|
179
|
-
// Allow tests to disable responding to pings.
|
|
180
|
-
self._respondToPings = options.respondToPings;
|
|
181
|
-
// This object is the public interface to the session. In the public
|
|
182
|
-
// API, it is called the `connection` object. Internally we call it
|
|
183
|
-
// a `connectionHandle` to avoid ambiguity.
|
|
184
|
-
self.connectionHandle = {
|
|
185
|
-
id: self.id,
|
|
186
|
-
close: function () {
|
|
187
|
-
self.close();
|
|
188
|
-
},
|
|
189
|
-
onClose: function (cb) {
|
|
190
|
-
if (self.inQueue) {
|
|
191
|
-
self._closeCallbacks.push(cb);
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
setImmediate(cb);
|
|
195
|
-
}
|
|
196
|
-
},
|
|
197
|
-
clientAddress: self._clientAddress(),
|
|
198
|
-
httpHeaders: self.socket.headers,
|
|
199
|
-
sockjsSessionId: self.socket.pathname.split('/')[3]
|
|
200
|
-
};
|
|
201
|
-
self.send({ msg: 'connected', session: self.id });
|
|
202
|
-
// On initial connect, spin up all the universal publishers.
|
|
203
|
-
setImmediate(() => this.startUniversalSubs());
|
|
204
|
-
if (version !== 'pre1' && options.heartbeatInterval !== 0) {
|
|
205
|
-
// We no longer need the low level timeout because we have heartbeats.
|
|
206
|
-
socket.setWebsocketTimeout(0);
|
|
207
|
-
self.heartbeat = new heartbeat_1.Heartbeat({
|
|
208
|
-
heartbeatInterval: options.heartbeatInterval,
|
|
209
|
-
heartbeatTimeout: options.heartbeatTimeout,
|
|
210
|
-
onTimeout: function () {
|
|
211
|
-
self.close();
|
|
212
|
-
},
|
|
213
|
-
sendPing: function () {
|
|
214
|
-
self.send({ msg: 'ping' });
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
self.heartbeat.start();
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
sendReady(subscriptionIds) {
|
|
221
|
-
var self = this;
|
|
222
|
-
if (self._isSending)
|
|
223
|
-
self.send({ msg: "ready", subs: subscriptionIds });
|
|
224
|
-
else {
|
|
225
|
-
for (const subscriptionId of subscriptionIds) {
|
|
226
|
-
self._pendingReady.push(subscriptionId);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
_canSend(collectionName) {
|
|
231
|
-
return this._isSending || !this.server.getPublicationStrategy(collectionName).useCollectionView;
|
|
232
|
-
}
|
|
233
|
-
sendInitialAdds(collectionName, docs) {
|
|
234
|
-
if (this._canSend(collectionName)) {
|
|
235
|
-
const items = [];
|
|
236
|
-
docs.forEach(doc => items.push(doc));
|
|
237
|
-
this.send({ msg: "init", collection: collectionName, items });
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
sendAdded(collectionName, id, fields) {
|
|
241
|
-
if (this._canSend(collectionName))
|
|
242
|
-
this.send({ msg: "added", collection: collectionName, id, fields });
|
|
243
|
-
}
|
|
244
|
-
sendChanged(collectionName, id, fields) {
|
|
245
|
-
if (fields == null || Object.keys(fields).length === 0)
|
|
246
|
-
return;
|
|
247
|
-
if (this._canSend(collectionName)) {
|
|
248
|
-
this.send({
|
|
249
|
-
msg: "changed",
|
|
250
|
-
collection: collectionName,
|
|
251
|
-
id,
|
|
252
|
-
fields
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
sendRemoved(collectionName, id) {
|
|
257
|
-
if (this._canSend(collectionName))
|
|
258
|
-
this.send({ msg: "removed", collection: collectionName, id });
|
|
259
|
-
}
|
|
260
|
-
getSendCallbacks() {
|
|
261
|
-
return {
|
|
262
|
-
added: this.sendAdded.bind(this),
|
|
263
|
-
changed: this.sendChanged.bind(this),
|
|
264
|
-
removed: this.sendRemoved.bind(this)
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
getCollectionView(collectionName) {
|
|
268
|
-
var self = this;
|
|
269
|
-
var ret = self.collectionViews.get(collectionName);
|
|
270
|
-
if (!ret) {
|
|
271
|
-
ret = new session_collection_view_1.SessionCollectionView(collectionName, self.getSendCallbacks());
|
|
272
|
-
self.collectionViews.set(collectionName, ret);
|
|
273
|
-
}
|
|
274
|
-
return ret;
|
|
275
|
-
}
|
|
276
|
-
initialAdds(subscriptionHandle, collectionName, docs) {
|
|
277
|
-
if (this.server.getPublicationStrategy(collectionName).useCollectionView) {
|
|
278
|
-
const view = this.getCollectionView(collectionName);
|
|
279
|
-
docs.forEach((doc, id) => view.added(subscriptionHandle, id, doc));
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
this.sendInitialAdds(collectionName, docs);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
added(subscriptionHandle, collectionName, id, fields) {
|
|
286
|
-
if (this.server.getPublicationStrategy(collectionName).useCollectionView) {
|
|
287
|
-
const view = this.getCollectionView(collectionName);
|
|
288
|
-
view.added(subscriptionHandle, id, fields);
|
|
289
|
-
}
|
|
290
|
-
else {
|
|
291
|
-
this.sendAdded(collectionName, id, fields);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
removed(subscriptionHandle, collectionName, id) {
|
|
295
|
-
if (this.server.getPublicationStrategy(collectionName).useCollectionView) {
|
|
296
|
-
const view = this.getCollectionView(collectionName);
|
|
297
|
-
view.removed(subscriptionHandle, id);
|
|
298
|
-
if (view.isEmpty()) {
|
|
299
|
-
this.collectionViews.delete(collectionName);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
else {
|
|
303
|
-
this.sendRemoved(collectionName, id);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
changed(subscriptionHandle, collectionName, id, fields) {
|
|
307
|
-
if (this.server.getPublicationStrategy(collectionName).useCollectionView) {
|
|
308
|
-
const view = this.getCollectionView(collectionName);
|
|
309
|
-
view.changed(subscriptionHandle, id, fields);
|
|
310
|
-
}
|
|
311
|
-
else {
|
|
312
|
-
this.sendChanged(collectionName, id, fields);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
startUniversalSubs() {
|
|
316
|
-
// Make a shallow copy of the set of universal handlers and start them. If
|
|
317
|
-
// additional universal publishers start while we're running them (due to
|
|
318
|
-
// yielding), they will run separately as part of Server.publish.
|
|
319
|
-
var handlers = [...this.server.universal_publish_handlers];
|
|
320
|
-
for (const handler of handlers) {
|
|
321
|
-
this._startSubscription(handler);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
// Destroy this session and unregister it at the server.
|
|
325
|
-
close() {
|
|
326
|
-
var self = this;
|
|
327
|
-
// Destroy this session, even if it's not registered at the
|
|
328
|
-
// server. Stop all processing and tear everything down. If a socket
|
|
329
|
-
// was attached, close it.
|
|
330
|
-
// Already destroyed.
|
|
331
|
-
if (!self.inQueue)
|
|
332
|
-
return;
|
|
333
|
-
// Drop the merge box data immediately.
|
|
334
|
-
self.inQueue = null;
|
|
335
|
-
self.collectionViews = new Map();
|
|
336
|
-
if (self.heartbeat) {
|
|
337
|
-
self.heartbeat.stop();
|
|
338
|
-
self.heartbeat = null;
|
|
339
|
-
}
|
|
340
|
-
if (self.socket) {
|
|
341
|
-
self.socket.close();
|
|
342
|
-
self.socket._meteorSession = null;
|
|
343
|
-
}
|
|
344
|
-
setImmediate(function () {
|
|
345
|
-
// Stop callbacks can yield, so we defer this on close.
|
|
346
|
-
// sub._isDeactivated() detects that we set inQueue to null and
|
|
347
|
-
// treats it as semi-deactivated (it will ignore incoming callbacks, etc).
|
|
348
|
-
self._deactivateAllSubscriptions();
|
|
349
|
-
// Defer calling the close callbacks, so that the caller closing
|
|
350
|
-
// the session isn't waiting for all the callbacks to complete.
|
|
351
|
-
for (const callback of self._closeCallbacks) {
|
|
352
|
-
callback();
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
// Unregister the session.
|
|
356
|
-
self.server._removeSession(self);
|
|
357
|
-
}
|
|
358
|
-
// Send a message (doing nothing if no socket is connected right now).
|
|
359
|
-
// It should be a JSON object (it will be stringified).
|
|
360
|
-
send(msg) {
|
|
361
|
-
var self = this;
|
|
362
|
-
if (self.socket) {
|
|
363
|
-
self.socket.send((0, utils_1.stringifyDDP)(msg));
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
// Send a connection error.
|
|
367
|
-
sendError(reason, offendingMessage) {
|
|
368
|
-
var self = this;
|
|
369
|
-
var msg = { msg: 'error', reason, offendingMessage };
|
|
370
|
-
self.send(msg);
|
|
371
|
-
}
|
|
372
|
-
// Process 'msg' as an incoming message. As a guard against
|
|
373
|
-
// race conditions during reconnection, ignore the message if
|
|
374
|
-
// 'socket' is not the currently connected socket.
|
|
375
|
-
//
|
|
376
|
-
// We run the messages from the client one at a time, in the order
|
|
377
|
-
// given by the client. The message handler is passed an idempotent
|
|
378
|
-
// function 'unblock' which it may call to allow other messages to
|
|
379
|
-
// begin running in parallel in another fiber (for example, a method
|
|
380
|
-
// that wants to yield). Otherwise, it is automatically unblocked
|
|
381
|
-
// when it returns.
|
|
382
|
-
//
|
|
383
|
-
// Actually, we don't have to 'totally order' the messages in this
|
|
384
|
-
// way, but it's the easiest thing that's correct. (unsub needs to
|
|
385
|
-
// be ordered against sub, methods need to be ordered against each
|
|
386
|
-
// other).
|
|
387
|
-
async processMessage(msg_in) {
|
|
388
|
-
var self = this;
|
|
389
|
-
if (!self.inQueue) // we have been destroyed.
|
|
390
|
-
return;
|
|
391
|
-
// Respond to ping and pong messages immediately without queuing.
|
|
392
|
-
// If the negotiated DDP version is "pre1" which didn't support
|
|
393
|
-
// pings, preserve the "pre1" behavior of responding with a "bad
|
|
394
|
-
// request" for the unknown messages.
|
|
395
|
-
//
|
|
396
|
-
// Any message counts as receiving a pong, as it demonstrates that
|
|
397
|
-
// the client is still alive.
|
|
398
|
-
if (self.heartbeat) {
|
|
399
|
-
self.heartbeat.messageReceived();
|
|
400
|
-
}
|
|
401
|
-
if (self.version !== 'pre1' && msg_in.msg === 'ping') {
|
|
402
|
-
if (self._respondToPings)
|
|
403
|
-
self.send({ msg: "pong", id: msg_in.id });
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
if (self.version !== 'pre1' && msg_in.msg === 'pong') {
|
|
407
|
-
// Since everything is a pong, there is nothing to do
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
self.inQueue.push(msg_in);
|
|
411
|
-
if (self.workerRunning)
|
|
412
|
-
return;
|
|
413
|
-
self.workerRunning = true;
|
|
414
|
-
while (self.inQueue && self.inQueue.length > 0) {
|
|
415
|
-
var msg = self.inQueue.shift();
|
|
416
|
-
self.server.onMessageHook.each(callback => {
|
|
417
|
-
callback(msg, self);
|
|
418
|
-
return true;
|
|
419
|
-
});
|
|
420
|
-
if (self.protocol_handlers.hasOwnProperty(msg.msg))
|
|
421
|
-
await self.protocol_handlers[msg.msg].call(self, msg);
|
|
422
|
-
else
|
|
423
|
-
self.sendError('Bad request', msg);
|
|
424
|
-
}
|
|
425
|
-
self.workerRunning = false;
|
|
426
|
-
}
|
|
427
|
-
_eachSub(f) {
|
|
428
|
-
var self = this;
|
|
429
|
-
self._namedSubs.forEach(f);
|
|
430
|
-
self._universalSubs.forEach(f);
|
|
431
|
-
}
|
|
432
|
-
_diffCollectionViews(beforeCVs) {
|
|
433
|
-
var self = this;
|
|
434
|
-
diff_1.DiffSequence.diffMaps(beforeCVs, self.collectionViews, {
|
|
435
|
-
both: function (collectionName, leftValue, rightValue) {
|
|
436
|
-
rightValue.diff(leftValue);
|
|
437
|
-
},
|
|
438
|
-
rightOnly: function (collectionName, rightValue) {
|
|
439
|
-
rightValue.documents.forEach(function (docView, id) {
|
|
440
|
-
self.sendAdded(collectionName, id, docView.getFields());
|
|
441
|
-
});
|
|
442
|
-
},
|
|
443
|
-
leftOnly: function (collectionName, leftValue) {
|
|
444
|
-
leftValue.documents.forEach(function (doc, id) {
|
|
445
|
-
self.sendRemoved(collectionName, id);
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
// Sets the current user id in all appropriate contexts and reruns
|
|
451
|
-
// all subscriptions
|
|
452
|
-
_setUserId(userId) {
|
|
453
|
-
var self = this;
|
|
454
|
-
if (userId !== null && typeof userId !== "string")
|
|
455
|
-
throw new Error("setUserId must be called on string or null, not " +
|
|
456
|
-
typeof userId);
|
|
457
|
-
// Prevent newly-created universal subscriptions from being added to our
|
|
458
|
-
// session. They will be found below when we call startUniversalSubs.
|
|
459
|
-
//
|
|
460
|
-
// (We don't have to worry about named subscriptions, because we only add
|
|
461
|
-
// them when we process a 'sub' message. We are currently processing a
|
|
462
|
-
// 'method' message, and the method did not unblock, because it is illegal
|
|
463
|
-
// to call setUserId after unblock. Thus we cannot be concurrently adding a
|
|
464
|
-
// new named subscription).
|
|
465
|
-
self._dontStartNewUniversalSubs = true;
|
|
466
|
-
// Prevent current subs from updating our collectionViews and call their
|
|
467
|
-
// stop callbacks. This may yield.
|
|
468
|
-
self._eachSub(function (sub) {
|
|
469
|
-
sub._deactivate();
|
|
470
|
-
});
|
|
471
|
-
// All subs should now be deactivated. Stop sending messages to the client,
|
|
472
|
-
// save the state of the published collections, reset to an empty view, and
|
|
473
|
-
// update the userId.
|
|
474
|
-
self._isSending = false;
|
|
475
|
-
var beforeCVs = self.collectionViews;
|
|
476
|
-
self.collectionViews = new Map();
|
|
477
|
-
self.userId = userId;
|
|
478
|
-
// _setUserId is normally called from a Meteor method with
|
|
479
|
-
// DDP._CurrentMethodInvocation set. But DDP._CurrentMethodInvocation is not
|
|
480
|
-
// expected to be set inside a publish function, so we temporary unset it.
|
|
481
|
-
// Inside a publish function DDP._CurrentPublicationInvocation is set.
|
|
482
|
-
const oldInvocation = livedata_server_1.DDP._CurrentMethodInvocation;
|
|
483
|
-
livedata_server_1.DDP._CurrentMethodInvocation = undefined;
|
|
484
|
-
try {
|
|
485
|
-
// Save the old named subs, and reset to having no subscriptions.
|
|
486
|
-
var oldNamedSubs = self._namedSubs;
|
|
487
|
-
self._namedSubs = new Map();
|
|
488
|
-
self._universalSubs = [];
|
|
489
|
-
oldNamedSubs.forEach(function (sub, subscriptionId) {
|
|
490
|
-
var newSub = sub._recreate();
|
|
491
|
-
self._namedSubs.set(subscriptionId, newSub);
|
|
492
|
-
// nb: if the handler throws or calls this.error(), it will in fact
|
|
493
|
-
// immediately send its 'nosub'. This is OK, though.
|
|
494
|
-
newSub._runHandler();
|
|
495
|
-
});
|
|
496
|
-
// Allow newly-created universal subs to be started on our connection in
|
|
497
|
-
// parallel with the ones we're spinning up here, and spin up universal
|
|
498
|
-
// subs.
|
|
499
|
-
self._dontStartNewUniversalSubs = false;
|
|
500
|
-
self.startUniversalSubs();
|
|
501
|
-
}
|
|
502
|
-
finally {
|
|
503
|
-
livedata_server_1.DDP._CurrentMethodInvocation = oldInvocation;
|
|
504
|
-
}
|
|
505
|
-
// Start sending messages again, beginning with the diff from the previous
|
|
506
|
-
// state of the world to the current state. No yields are allowed during
|
|
507
|
-
// this diff, so that other changes cannot interleave.
|
|
508
|
-
self._isSending = true;
|
|
509
|
-
self._diffCollectionViews(beforeCVs);
|
|
510
|
-
if (self._pendingReady.length > 0) {
|
|
511
|
-
self.sendReady(self._pendingReady);
|
|
512
|
-
self._pendingReady = [];
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
async _startSubscription(handler, subId, params, name) {
|
|
516
|
-
var self = this;
|
|
517
|
-
var sub = new subscription_1.Subscription(self, handler, subId, params, name);
|
|
518
|
-
if (subId)
|
|
519
|
-
self._namedSubs.set(subId, sub);
|
|
520
|
-
else
|
|
521
|
-
self._universalSubs.push(sub);
|
|
522
|
-
await sub._runHandler();
|
|
523
|
-
}
|
|
524
|
-
// Tear down specified subscription
|
|
525
|
-
_stopSubscription(subId, error) {
|
|
526
|
-
var subName = null;
|
|
527
|
-
if (subId) {
|
|
528
|
-
var maybeSub = this._namedSubs.get(subId);
|
|
529
|
-
if (maybeSub) {
|
|
530
|
-
subName = maybeSub._name;
|
|
531
|
-
// version 1a doesn't send document deletions and relies on the clients for cleanup
|
|
532
|
-
if (this.version !== "1a")
|
|
533
|
-
maybeSub._removeAllDocuments();
|
|
534
|
-
maybeSub._deactivate();
|
|
535
|
-
this._namedSubs.delete(subId);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
var response = { msg: 'nosub', id: subId, error: undefined };
|
|
539
|
-
if (error) {
|
|
540
|
-
response.error = (0, livedata_server_1.wrapInternalException)(error, subName ? ("from sub " + subName + " id " + subId)
|
|
541
|
-
: ("from sub id " + subId));
|
|
542
|
-
}
|
|
543
|
-
this.send(response);
|
|
544
|
-
}
|
|
545
|
-
// Tear down all subscriptions. Note that this does NOT send removed or nosub
|
|
546
|
-
// messages, since we assume the client is gone.
|
|
547
|
-
_deactivateAllSubscriptions() {
|
|
548
|
-
var self = this;
|
|
549
|
-
self._namedSubs.forEach(function (sub, id) {
|
|
550
|
-
sub._deactivate();
|
|
551
|
-
});
|
|
552
|
-
self._namedSubs = new Map();
|
|
553
|
-
self._universalSubs.forEach(function (sub) {
|
|
554
|
-
sub._deactivate();
|
|
555
|
-
});
|
|
556
|
-
self._universalSubs = [];
|
|
557
|
-
}
|
|
558
|
-
// Determine the remote client's IP address, based on the
|
|
559
|
-
// HTTP_FORWARDED_COUNT environment variable representing how many
|
|
560
|
-
// proxies the server is behind.
|
|
561
|
-
_clientAddress() {
|
|
562
|
-
var self = this;
|
|
563
|
-
// For the reported client address for a connection to be correct,
|
|
564
|
-
// the developer must set the HTTP_FORWARDED_COUNT environment
|
|
565
|
-
// variable to an integer representing the number of hops they
|
|
566
|
-
// expect in the `x-forwarded-for` header. E.g., set to "1" if the
|
|
567
|
-
// server is behind one proxy.
|
|
568
|
-
//
|
|
569
|
-
// This could be computed once at startup instead of every time.
|
|
570
|
-
var httpForwardedCount = parseInt(process.env['HTTP_FORWARDED_COUNT']) || 0;
|
|
571
|
-
if (httpForwardedCount === 0)
|
|
572
|
-
return self.socket.remoteAddress;
|
|
573
|
-
let forwardedFor = self.socket.headers["x-forwarded-for"];
|
|
574
|
-
if (typeof forwardedFor !== "string")
|
|
575
|
-
return null;
|
|
576
|
-
forwardedFor = forwardedFor.trim().split(/\s*,\s*/);
|
|
577
|
-
// Typically the first value in the `x-forwarded-for` header is
|
|
578
|
-
// the original IP address of the client connecting to the first
|
|
579
|
-
// proxy. However, the end user can easily spoof the header, in
|
|
580
|
-
// which case the first value(s) will be the fake IP address from
|
|
581
|
-
// the user pretending to be a proxy reporting the original IP
|
|
582
|
-
// address value. By counting HTTP_FORWARDED_COUNT back from the
|
|
583
|
-
// end of the list, we ensure that we get the IP address being
|
|
584
|
-
// reported by *our* first proxy.
|
|
585
|
-
if (httpForwardedCount < 0 || httpForwardedCount > forwardedFor.length)
|
|
586
|
-
return null;
|
|
587
|
-
return forwardedFor[forwardedFor.length - httpForwardedCount];
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
exports.DDPSession = DDPSession;
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DDPSession = void 0;
|
|
7
|
+
const method_invocation_1 = require("./method-invocation");
|
|
8
|
+
const writefence_1 = require("./writefence");
|
|
9
|
+
const double_ended_queue_1 = __importDefault(require("double-ended-queue"));
|
|
10
|
+
const main_1 = require("../random/main");
|
|
11
|
+
const livedata_server_1 = require("./livedata_server");
|
|
12
|
+
const heartbeat_1 = require("./heartbeat");
|
|
13
|
+
const utils_1 = require("./utils");
|
|
14
|
+
const diff_1 = require("../diff-sequence/diff");
|
|
15
|
+
const session_collection_view_1 = require("./session-collection-view");
|
|
16
|
+
const subscription_1 = require("./subscription");
|
|
17
|
+
class DDPSession {
|
|
18
|
+
constructor(server, version, socket, options) {
|
|
19
|
+
this.protocol_handlers = {
|
|
20
|
+
sub: async function (msg) {
|
|
21
|
+
var self = this;
|
|
22
|
+
// reject malformed messages
|
|
23
|
+
if (typeof (msg.id) !== "string" ||
|
|
24
|
+
typeof (msg.name) !== "string" ||
|
|
25
|
+
(('params' in msg) && !(msg.params instanceof Array))) {
|
|
26
|
+
self.sendError("Malformed subscription", msg);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (!self.server.publish_handlers[msg.name]) {
|
|
30
|
+
self.send({
|
|
31
|
+
msg: 'nosub', id: msg.id,
|
|
32
|
+
error: (0, livedata_server_1.ddpError)(404, `Subscription '${msg.name}' not found`)
|
|
33
|
+
});
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (self._namedSubs.has(msg.id))
|
|
37
|
+
// subs are idempotent, or rather, they are ignored if a sub
|
|
38
|
+
// with that id already exists. this is important during
|
|
39
|
+
// reconnect.
|
|
40
|
+
return;
|
|
41
|
+
// XXX It'd be much better if we had generic hooks where any package can
|
|
42
|
+
// hook into subscription handling, but in the mean while we special case
|
|
43
|
+
// ddp-rate-limiter package. This is also done for weak requirements to
|
|
44
|
+
// add the ddp-rate-limiter package in case we don't have Accounts. A
|
|
45
|
+
// user trying to use the ddp-rate-limiter must explicitly require it.
|
|
46
|
+
/* if (Package['ddp-rate-limiter']) {
|
|
47
|
+
var DDPRateLimiter = Package['ddp-rate-limiter'].DDPRateLimiter;
|
|
48
|
+
var rateLimiterInput = {
|
|
49
|
+
userId: self.userId,
|
|
50
|
+
clientAddress: self.connectionHandle.clientAddress,
|
|
51
|
+
type: "subscription",
|
|
52
|
+
name: msg.name,
|
|
53
|
+
connectionId: self.id
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
DDPRateLimiter._increment(rateLimiterInput);
|
|
57
|
+
var rateLimitResult = DDPRateLimiter._check(rateLimiterInput);
|
|
58
|
+
if (!rateLimitResult.allowed) {
|
|
59
|
+
self.send({
|
|
60
|
+
msg: 'nosub', id: msg.id,
|
|
61
|
+
error: ddpError(
|
|
62
|
+
'too-many-requests',
|
|
63
|
+
DDPRateLimiter.getErrorMessage(rateLimitResult),
|
|
64
|
+
{timeToReset: rateLimitResult.timeToReset})
|
|
65
|
+
});
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}*/
|
|
69
|
+
var handler = self.server.publish_handlers[msg.name];
|
|
70
|
+
self._startSubscription(handler, msg.id, msg.params, msg.name);
|
|
71
|
+
},
|
|
72
|
+
unsub: async function (msg) {
|
|
73
|
+
var self = this;
|
|
74
|
+
self._stopSubscription(msg.id);
|
|
75
|
+
},
|
|
76
|
+
method: async function (msg) {
|
|
77
|
+
var self = this;
|
|
78
|
+
// Reject malformed messages.
|
|
79
|
+
// For now, we silently ignore unknown attributes,
|
|
80
|
+
// for forwards compatibility.
|
|
81
|
+
if (typeof (msg.id) !== "string" ||
|
|
82
|
+
typeof (msg.method) !== "string" ||
|
|
83
|
+
(('params' in msg) && !(msg.params instanceof Array)) ||
|
|
84
|
+
(('randomSeed' in msg) && (typeof msg.randomSeed !== "string"))) {
|
|
85
|
+
self.sendError("Malformed method invocation", msg);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
var randomSeed = msg.randomSeed || null;
|
|
89
|
+
// Set up to mark the method as satisfied once all observers
|
|
90
|
+
// (and subscriptions) have reacted to any writes that were
|
|
91
|
+
// done.
|
|
92
|
+
var fence = new writefence_1._WriteFence();
|
|
93
|
+
fence.onAllCommitted(function () {
|
|
94
|
+
// Retire the fence so that future writes are allowed.
|
|
95
|
+
// This means that callbacks like timers are free to use
|
|
96
|
+
// the fence, and if they fire before it's armed (for
|
|
97
|
+
// example, because the method waits for them) their
|
|
98
|
+
// writes will be included in the fence.
|
|
99
|
+
fence.retire();
|
|
100
|
+
self.send({ msg: 'updated', methods: [msg.id] });
|
|
101
|
+
});
|
|
102
|
+
// Find the handler
|
|
103
|
+
var handler = self.server.method_handlers[msg.method];
|
|
104
|
+
if (!handler) {
|
|
105
|
+
self.send({ msg: 'result', id: msg.id, error: (0, livedata_server_1.ddpError)(404, `Method '${msg.method}' not found`) });
|
|
106
|
+
fence.arm();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
var setUserId = function (userId) {
|
|
110
|
+
self._setUserId(userId);
|
|
111
|
+
};
|
|
112
|
+
var invocation = new method_invocation_1.MethodInvocation({
|
|
113
|
+
isSimulation: false,
|
|
114
|
+
userId: self.userId,
|
|
115
|
+
setUserId: setUserId,
|
|
116
|
+
connection: self.connectionHandle,
|
|
117
|
+
randomSeed: randomSeed
|
|
118
|
+
});
|
|
119
|
+
const oldInvocation = livedata_server_1.DDP._CurrentMethodInvocation;
|
|
120
|
+
const oldFence = writefence_1._WriteFence._CurrentWriteFence;
|
|
121
|
+
livedata_server_1.DDP._CurrentMethodInvocation = invocation;
|
|
122
|
+
writefence_1._WriteFence._CurrentWriteFence = fence;
|
|
123
|
+
function finish() {
|
|
124
|
+
livedata_server_1.DDP._CurrentMethodInvocation = oldInvocation;
|
|
125
|
+
writefence_1._WriteFence._CurrentWriteFence = oldFence;
|
|
126
|
+
fence.arm();
|
|
127
|
+
}
|
|
128
|
+
const payload = {
|
|
129
|
+
msg: "result",
|
|
130
|
+
id: msg.id,
|
|
131
|
+
result: undefined,
|
|
132
|
+
error: undefined
|
|
133
|
+
};
|
|
134
|
+
handler.apply(invocation, msg.params).then((result) => {
|
|
135
|
+
finish();
|
|
136
|
+
if (result !== undefined) {
|
|
137
|
+
payload.result = result;
|
|
138
|
+
}
|
|
139
|
+
self.send(payload);
|
|
140
|
+
}, (exception) => {
|
|
141
|
+
finish();
|
|
142
|
+
if (exception && !exception.isClientSafe)
|
|
143
|
+
console.error(`Exception while invoking method '${msg.method}'`, exception);
|
|
144
|
+
payload.error = (0, livedata_server_1.wrapInternalException)(exception, `while invoking method '${msg.method}'`);
|
|
145
|
+
self.send(payload);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
var self = this;
|
|
150
|
+
self.id = main_1.Random.id();
|
|
151
|
+
self.server = server;
|
|
152
|
+
self.version = version;
|
|
153
|
+
self.initialized = false;
|
|
154
|
+
self.socket = socket;
|
|
155
|
+
// Set to null when the session is destroyed. Multiple places below
|
|
156
|
+
// use this to determine if the session is alive or not.
|
|
157
|
+
self.inQueue = new double_ended_queue_1.default();
|
|
158
|
+
self.workerRunning = false;
|
|
159
|
+
// Sub objects for active subscriptions
|
|
160
|
+
self._namedSubs = new Map();
|
|
161
|
+
self._universalSubs = [];
|
|
162
|
+
self.userId = null;
|
|
163
|
+
self.collectionViews = new Map();
|
|
164
|
+
// Set this to false to not send messages when collectionViews are
|
|
165
|
+
// modified. This is done when rerunning subs in _setUserId and those messages
|
|
166
|
+
// are calculated via a diff instead.
|
|
167
|
+
self._isSending = true;
|
|
168
|
+
// If this is true, don't start a newly-created universal publisher on this
|
|
169
|
+
// session. The session will take care of starting it when appropriate.
|
|
170
|
+
self._dontStartNewUniversalSubs = false;
|
|
171
|
+
// When we are rerunning subscriptions, any ready messages
|
|
172
|
+
// we want to buffer up for when we are done rerunning subscriptions
|
|
173
|
+
self._pendingReady = [];
|
|
174
|
+
// List of callbacks to call when this connection is closed.
|
|
175
|
+
self._closeCallbacks = [];
|
|
176
|
+
// XXX HACK: If a sockjs connection, save off the URL. This is
|
|
177
|
+
// temporary and will go away in the near future.
|
|
178
|
+
self._socketUrl = socket.url;
|
|
179
|
+
// Allow tests to disable responding to pings.
|
|
180
|
+
self._respondToPings = options.respondToPings;
|
|
181
|
+
// This object is the public interface to the session. In the public
|
|
182
|
+
// API, it is called the `connection` object. Internally we call it
|
|
183
|
+
// a `connectionHandle` to avoid ambiguity.
|
|
184
|
+
self.connectionHandle = {
|
|
185
|
+
id: self.id,
|
|
186
|
+
close: function () {
|
|
187
|
+
self.close();
|
|
188
|
+
},
|
|
189
|
+
onClose: function (cb) {
|
|
190
|
+
if (self.inQueue) {
|
|
191
|
+
self._closeCallbacks.push(cb);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
setImmediate(cb);
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
clientAddress: self._clientAddress(),
|
|
198
|
+
httpHeaders: self.socket.headers,
|
|
199
|
+
sockjsSessionId: self.socket.pathname.split('/')[3]
|
|
200
|
+
};
|
|
201
|
+
self.send({ msg: 'connected', session: self.id });
|
|
202
|
+
// On initial connect, spin up all the universal publishers.
|
|
203
|
+
setImmediate(() => this.startUniversalSubs());
|
|
204
|
+
if (version !== 'pre1' && options.heartbeatInterval !== 0) {
|
|
205
|
+
// We no longer need the low level timeout because we have heartbeats.
|
|
206
|
+
socket.setWebsocketTimeout(0);
|
|
207
|
+
self.heartbeat = new heartbeat_1.Heartbeat({
|
|
208
|
+
heartbeatInterval: options.heartbeatInterval,
|
|
209
|
+
heartbeatTimeout: options.heartbeatTimeout,
|
|
210
|
+
onTimeout: function () {
|
|
211
|
+
self.close();
|
|
212
|
+
},
|
|
213
|
+
sendPing: function () {
|
|
214
|
+
self.send({ msg: 'ping' });
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
self.heartbeat.start();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
sendReady(subscriptionIds) {
|
|
221
|
+
var self = this;
|
|
222
|
+
if (self._isSending)
|
|
223
|
+
self.send({ msg: "ready", subs: subscriptionIds });
|
|
224
|
+
else {
|
|
225
|
+
for (const subscriptionId of subscriptionIds) {
|
|
226
|
+
self._pendingReady.push(subscriptionId);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
_canSend(collectionName) {
|
|
231
|
+
return this._isSending || !this.server.getPublicationStrategy(collectionName).useCollectionView;
|
|
232
|
+
}
|
|
233
|
+
sendInitialAdds(collectionName, docs) {
|
|
234
|
+
if (this._canSend(collectionName)) {
|
|
235
|
+
const items = [];
|
|
236
|
+
docs.forEach(doc => items.push(doc));
|
|
237
|
+
this.send({ msg: "init", collection: collectionName, items });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
sendAdded(collectionName, id, fields) {
|
|
241
|
+
if (this._canSend(collectionName))
|
|
242
|
+
this.send({ msg: "added", collection: collectionName, id, fields });
|
|
243
|
+
}
|
|
244
|
+
sendChanged(collectionName, id, fields) {
|
|
245
|
+
if (fields == null || Object.keys(fields).length === 0)
|
|
246
|
+
return;
|
|
247
|
+
if (this._canSend(collectionName)) {
|
|
248
|
+
this.send({
|
|
249
|
+
msg: "changed",
|
|
250
|
+
collection: collectionName,
|
|
251
|
+
id,
|
|
252
|
+
fields
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
sendRemoved(collectionName, id) {
|
|
257
|
+
if (this._canSend(collectionName))
|
|
258
|
+
this.send({ msg: "removed", collection: collectionName, id });
|
|
259
|
+
}
|
|
260
|
+
getSendCallbacks() {
|
|
261
|
+
return {
|
|
262
|
+
added: this.sendAdded.bind(this),
|
|
263
|
+
changed: this.sendChanged.bind(this),
|
|
264
|
+
removed: this.sendRemoved.bind(this)
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
getCollectionView(collectionName) {
|
|
268
|
+
var self = this;
|
|
269
|
+
var ret = self.collectionViews.get(collectionName);
|
|
270
|
+
if (!ret) {
|
|
271
|
+
ret = new session_collection_view_1.SessionCollectionView(collectionName, self.getSendCallbacks());
|
|
272
|
+
self.collectionViews.set(collectionName, ret);
|
|
273
|
+
}
|
|
274
|
+
return ret;
|
|
275
|
+
}
|
|
276
|
+
initialAdds(subscriptionHandle, collectionName, docs) {
|
|
277
|
+
if (this.server.getPublicationStrategy(collectionName).useCollectionView) {
|
|
278
|
+
const view = this.getCollectionView(collectionName);
|
|
279
|
+
docs.forEach((doc, id) => view.added(subscriptionHandle, id, doc));
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
this.sendInitialAdds(collectionName, docs);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
added(subscriptionHandle, collectionName, id, fields) {
|
|
286
|
+
if (this.server.getPublicationStrategy(collectionName).useCollectionView) {
|
|
287
|
+
const view = this.getCollectionView(collectionName);
|
|
288
|
+
view.added(subscriptionHandle, id, fields);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
this.sendAdded(collectionName, id, fields);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
removed(subscriptionHandle, collectionName, id) {
|
|
295
|
+
if (this.server.getPublicationStrategy(collectionName).useCollectionView) {
|
|
296
|
+
const view = this.getCollectionView(collectionName);
|
|
297
|
+
view.removed(subscriptionHandle, id);
|
|
298
|
+
if (view.isEmpty()) {
|
|
299
|
+
this.collectionViews.delete(collectionName);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
this.sendRemoved(collectionName, id);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
changed(subscriptionHandle, collectionName, id, fields) {
|
|
307
|
+
if (this.server.getPublicationStrategy(collectionName).useCollectionView) {
|
|
308
|
+
const view = this.getCollectionView(collectionName);
|
|
309
|
+
view.changed(subscriptionHandle, id, fields);
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
this.sendChanged(collectionName, id, fields);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
startUniversalSubs() {
|
|
316
|
+
// Make a shallow copy of the set of universal handlers and start them. If
|
|
317
|
+
// additional universal publishers start while we're running them (due to
|
|
318
|
+
// yielding), they will run separately as part of Server.publish.
|
|
319
|
+
var handlers = [...this.server.universal_publish_handlers];
|
|
320
|
+
for (const handler of handlers) {
|
|
321
|
+
this._startSubscription(handler);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// Destroy this session and unregister it at the server.
|
|
325
|
+
close() {
|
|
326
|
+
var self = this;
|
|
327
|
+
// Destroy this session, even if it's not registered at the
|
|
328
|
+
// server. Stop all processing and tear everything down. If a socket
|
|
329
|
+
// was attached, close it.
|
|
330
|
+
// Already destroyed.
|
|
331
|
+
if (!self.inQueue)
|
|
332
|
+
return;
|
|
333
|
+
// Drop the merge box data immediately.
|
|
334
|
+
self.inQueue = null;
|
|
335
|
+
self.collectionViews = new Map();
|
|
336
|
+
if (self.heartbeat) {
|
|
337
|
+
self.heartbeat.stop();
|
|
338
|
+
self.heartbeat = null;
|
|
339
|
+
}
|
|
340
|
+
if (self.socket) {
|
|
341
|
+
self.socket.close();
|
|
342
|
+
self.socket._meteorSession = null;
|
|
343
|
+
}
|
|
344
|
+
setImmediate(function () {
|
|
345
|
+
// Stop callbacks can yield, so we defer this on close.
|
|
346
|
+
// sub._isDeactivated() detects that we set inQueue to null and
|
|
347
|
+
// treats it as semi-deactivated (it will ignore incoming callbacks, etc).
|
|
348
|
+
self._deactivateAllSubscriptions();
|
|
349
|
+
// Defer calling the close callbacks, so that the caller closing
|
|
350
|
+
// the session isn't waiting for all the callbacks to complete.
|
|
351
|
+
for (const callback of self._closeCallbacks) {
|
|
352
|
+
callback();
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
// Unregister the session.
|
|
356
|
+
self.server._removeSession(self);
|
|
357
|
+
}
|
|
358
|
+
// Send a message (doing nothing if no socket is connected right now).
|
|
359
|
+
// It should be a JSON object (it will be stringified).
|
|
360
|
+
send(msg) {
|
|
361
|
+
var self = this;
|
|
362
|
+
if (self.socket) {
|
|
363
|
+
self.socket.send((0, utils_1.stringifyDDP)(msg));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
// Send a connection error.
|
|
367
|
+
sendError(reason, offendingMessage) {
|
|
368
|
+
var self = this;
|
|
369
|
+
var msg = { msg: 'error', reason, offendingMessage };
|
|
370
|
+
self.send(msg);
|
|
371
|
+
}
|
|
372
|
+
// Process 'msg' as an incoming message. As a guard against
|
|
373
|
+
// race conditions during reconnection, ignore the message if
|
|
374
|
+
// 'socket' is not the currently connected socket.
|
|
375
|
+
//
|
|
376
|
+
// We run the messages from the client one at a time, in the order
|
|
377
|
+
// given by the client. The message handler is passed an idempotent
|
|
378
|
+
// function 'unblock' which it may call to allow other messages to
|
|
379
|
+
// begin running in parallel in another fiber (for example, a method
|
|
380
|
+
// that wants to yield). Otherwise, it is automatically unblocked
|
|
381
|
+
// when it returns.
|
|
382
|
+
//
|
|
383
|
+
// Actually, we don't have to 'totally order' the messages in this
|
|
384
|
+
// way, but it's the easiest thing that's correct. (unsub needs to
|
|
385
|
+
// be ordered against sub, methods need to be ordered against each
|
|
386
|
+
// other).
|
|
387
|
+
async processMessage(msg_in) {
|
|
388
|
+
var self = this;
|
|
389
|
+
if (!self.inQueue) // we have been destroyed.
|
|
390
|
+
return;
|
|
391
|
+
// Respond to ping and pong messages immediately without queuing.
|
|
392
|
+
// If the negotiated DDP version is "pre1" which didn't support
|
|
393
|
+
// pings, preserve the "pre1" behavior of responding with a "bad
|
|
394
|
+
// request" for the unknown messages.
|
|
395
|
+
//
|
|
396
|
+
// Any message counts as receiving a pong, as it demonstrates that
|
|
397
|
+
// the client is still alive.
|
|
398
|
+
if (self.heartbeat) {
|
|
399
|
+
self.heartbeat.messageReceived();
|
|
400
|
+
}
|
|
401
|
+
if (self.version !== 'pre1' && msg_in.msg === 'ping') {
|
|
402
|
+
if (self._respondToPings)
|
|
403
|
+
self.send({ msg: "pong", id: msg_in.id });
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (self.version !== 'pre1' && msg_in.msg === 'pong') {
|
|
407
|
+
// Since everything is a pong, there is nothing to do
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
self.inQueue.push(msg_in);
|
|
411
|
+
if (self.workerRunning)
|
|
412
|
+
return;
|
|
413
|
+
self.workerRunning = true;
|
|
414
|
+
while (self.inQueue && self.inQueue.length > 0) {
|
|
415
|
+
var msg = self.inQueue.shift();
|
|
416
|
+
self.server.onMessageHook.each(callback => {
|
|
417
|
+
callback(msg, self);
|
|
418
|
+
return true;
|
|
419
|
+
});
|
|
420
|
+
if (self.protocol_handlers.hasOwnProperty(msg.msg))
|
|
421
|
+
await self.protocol_handlers[msg.msg].call(self, msg);
|
|
422
|
+
else
|
|
423
|
+
self.sendError('Bad request', msg);
|
|
424
|
+
}
|
|
425
|
+
self.workerRunning = false;
|
|
426
|
+
}
|
|
427
|
+
_eachSub(f) {
|
|
428
|
+
var self = this;
|
|
429
|
+
self._namedSubs.forEach(f);
|
|
430
|
+
self._universalSubs.forEach(f);
|
|
431
|
+
}
|
|
432
|
+
_diffCollectionViews(beforeCVs) {
|
|
433
|
+
var self = this;
|
|
434
|
+
diff_1.DiffSequence.diffMaps(beforeCVs, self.collectionViews, {
|
|
435
|
+
both: function (collectionName, leftValue, rightValue) {
|
|
436
|
+
rightValue.diff(leftValue);
|
|
437
|
+
},
|
|
438
|
+
rightOnly: function (collectionName, rightValue) {
|
|
439
|
+
rightValue.documents.forEach(function (docView, id) {
|
|
440
|
+
self.sendAdded(collectionName, id, docView.getFields());
|
|
441
|
+
});
|
|
442
|
+
},
|
|
443
|
+
leftOnly: function (collectionName, leftValue) {
|
|
444
|
+
leftValue.documents.forEach(function (doc, id) {
|
|
445
|
+
self.sendRemoved(collectionName, id);
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
// Sets the current user id in all appropriate contexts and reruns
|
|
451
|
+
// all subscriptions
|
|
452
|
+
_setUserId(userId) {
|
|
453
|
+
var self = this;
|
|
454
|
+
if (userId !== null && typeof userId !== "string")
|
|
455
|
+
throw new Error("setUserId must be called on string or null, not " +
|
|
456
|
+
typeof userId);
|
|
457
|
+
// Prevent newly-created universal subscriptions from being added to our
|
|
458
|
+
// session. They will be found below when we call startUniversalSubs.
|
|
459
|
+
//
|
|
460
|
+
// (We don't have to worry about named subscriptions, because we only add
|
|
461
|
+
// them when we process a 'sub' message. We are currently processing a
|
|
462
|
+
// 'method' message, and the method did not unblock, because it is illegal
|
|
463
|
+
// to call setUserId after unblock. Thus we cannot be concurrently adding a
|
|
464
|
+
// new named subscription).
|
|
465
|
+
self._dontStartNewUniversalSubs = true;
|
|
466
|
+
// Prevent current subs from updating our collectionViews and call their
|
|
467
|
+
// stop callbacks. This may yield.
|
|
468
|
+
self._eachSub(function (sub) {
|
|
469
|
+
sub._deactivate();
|
|
470
|
+
});
|
|
471
|
+
// All subs should now be deactivated. Stop sending messages to the client,
|
|
472
|
+
// save the state of the published collections, reset to an empty view, and
|
|
473
|
+
// update the userId.
|
|
474
|
+
self._isSending = false;
|
|
475
|
+
var beforeCVs = self.collectionViews;
|
|
476
|
+
self.collectionViews = new Map();
|
|
477
|
+
self.userId = userId;
|
|
478
|
+
// _setUserId is normally called from a Meteor method with
|
|
479
|
+
// DDP._CurrentMethodInvocation set. But DDP._CurrentMethodInvocation is not
|
|
480
|
+
// expected to be set inside a publish function, so we temporary unset it.
|
|
481
|
+
// Inside a publish function DDP._CurrentPublicationInvocation is set.
|
|
482
|
+
const oldInvocation = livedata_server_1.DDP._CurrentMethodInvocation;
|
|
483
|
+
livedata_server_1.DDP._CurrentMethodInvocation = undefined;
|
|
484
|
+
try {
|
|
485
|
+
// Save the old named subs, and reset to having no subscriptions.
|
|
486
|
+
var oldNamedSubs = self._namedSubs;
|
|
487
|
+
self._namedSubs = new Map();
|
|
488
|
+
self._universalSubs = [];
|
|
489
|
+
oldNamedSubs.forEach(function (sub, subscriptionId) {
|
|
490
|
+
var newSub = sub._recreate();
|
|
491
|
+
self._namedSubs.set(subscriptionId, newSub);
|
|
492
|
+
// nb: if the handler throws or calls this.error(), it will in fact
|
|
493
|
+
// immediately send its 'nosub'. This is OK, though.
|
|
494
|
+
newSub._runHandler();
|
|
495
|
+
});
|
|
496
|
+
// Allow newly-created universal subs to be started on our connection in
|
|
497
|
+
// parallel with the ones we're spinning up here, and spin up universal
|
|
498
|
+
// subs.
|
|
499
|
+
self._dontStartNewUniversalSubs = false;
|
|
500
|
+
self.startUniversalSubs();
|
|
501
|
+
}
|
|
502
|
+
finally {
|
|
503
|
+
livedata_server_1.DDP._CurrentMethodInvocation = oldInvocation;
|
|
504
|
+
}
|
|
505
|
+
// Start sending messages again, beginning with the diff from the previous
|
|
506
|
+
// state of the world to the current state. No yields are allowed during
|
|
507
|
+
// this diff, so that other changes cannot interleave.
|
|
508
|
+
self._isSending = true;
|
|
509
|
+
self._diffCollectionViews(beforeCVs);
|
|
510
|
+
if (self._pendingReady.length > 0) {
|
|
511
|
+
self.sendReady(self._pendingReady);
|
|
512
|
+
self._pendingReady = [];
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
async _startSubscription(handler, subId, params, name) {
|
|
516
|
+
var self = this;
|
|
517
|
+
var sub = new subscription_1.Subscription(self, handler, subId, params, name);
|
|
518
|
+
if (subId)
|
|
519
|
+
self._namedSubs.set(subId, sub);
|
|
520
|
+
else
|
|
521
|
+
self._universalSubs.push(sub);
|
|
522
|
+
await sub._runHandler();
|
|
523
|
+
}
|
|
524
|
+
// Tear down specified subscription
|
|
525
|
+
_stopSubscription(subId, error) {
|
|
526
|
+
var subName = null;
|
|
527
|
+
if (subId) {
|
|
528
|
+
var maybeSub = this._namedSubs.get(subId);
|
|
529
|
+
if (maybeSub) {
|
|
530
|
+
subName = maybeSub._name;
|
|
531
|
+
// version 1a doesn't send document deletions and relies on the clients for cleanup
|
|
532
|
+
if (this.version !== "1a")
|
|
533
|
+
maybeSub._removeAllDocuments();
|
|
534
|
+
maybeSub._deactivate();
|
|
535
|
+
this._namedSubs.delete(subId);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
var response = { msg: 'nosub', id: subId, error: undefined };
|
|
539
|
+
if (error) {
|
|
540
|
+
response.error = (0, livedata_server_1.wrapInternalException)(error, subName ? ("from sub " + subName + " id " + subId)
|
|
541
|
+
: ("from sub id " + subId));
|
|
542
|
+
}
|
|
543
|
+
this.send(response);
|
|
544
|
+
}
|
|
545
|
+
// Tear down all subscriptions. Note that this does NOT send removed or nosub
|
|
546
|
+
// messages, since we assume the client is gone.
|
|
547
|
+
_deactivateAllSubscriptions() {
|
|
548
|
+
var self = this;
|
|
549
|
+
self._namedSubs.forEach(function (sub, id) {
|
|
550
|
+
sub._deactivate();
|
|
551
|
+
});
|
|
552
|
+
self._namedSubs = new Map();
|
|
553
|
+
self._universalSubs.forEach(function (sub) {
|
|
554
|
+
sub._deactivate();
|
|
555
|
+
});
|
|
556
|
+
self._universalSubs = [];
|
|
557
|
+
}
|
|
558
|
+
// Determine the remote client's IP address, based on the
|
|
559
|
+
// HTTP_FORWARDED_COUNT environment variable representing how many
|
|
560
|
+
// proxies the server is behind.
|
|
561
|
+
_clientAddress() {
|
|
562
|
+
var self = this;
|
|
563
|
+
// For the reported client address for a connection to be correct,
|
|
564
|
+
// the developer must set the HTTP_FORWARDED_COUNT environment
|
|
565
|
+
// variable to an integer representing the number of hops they
|
|
566
|
+
// expect in the `x-forwarded-for` header. E.g., set to "1" if the
|
|
567
|
+
// server is behind one proxy.
|
|
568
|
+
//
|
|
569
|
+
// This could be computed once at startup instead of every time.
|
|
570
|
+
var httpForwardedCount = parseInt(process.env['HTTP_FORWARDED_COUNT']) || 0;
|
|
571
|
+
if (httpForwardedCount === 0)
|
|
572
|
+
return self.socket.remoteAddress;
|
|
573
|
+
let forwardedFor = self.socket.headers["x-forwarded-for"];
|
|
574
|
+
if (typeof forwardedFor !== "string")
|
|
575
|
+
return null;
|
|
576
|
+
forwardedFor = forwardedFor.trim().split(/\s*,\s*/);
|
|
577
|
+
// Typically the first value in the `x-forwarded-for` header is
|
|
578
|
+
// the original IP address of the client connecting to the first
|
|
579
|
+
// proxy. However, the end user can easily spoof the header, in
|
|
580
|
+
// which case the first value(s) will be the fake IP address from
|
|
581
|
+
// the user pretending to be a proxy reporting the original IP
|
|
582
|
+
// address value. By counting HTTP_FORWARDED_COUNT back from the
|
|
583
|
+
// end of the list, we ensure that we get the IP address being
|
|
584
|
+
// reported by *our* first proxy.
|
|
585
|
+
if (httpForwardedCount < 0 || httpForwardedCount > forwardedFor.length)
|
|
586
|
+
return null;
|
|
587
|
+
return forwardedFor[forwardedFor.length - httpForwardedCount];
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
exports.DDPSession = DDPSession;
|