mongodb-livedata-server 0.0.1

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