Skip to content

Commit b5cd66d

Browse files
committed
Merge branch 'pr-1611' into devel
2 parents bc8d745 + 6a2c952 commit b5cd66d

36 files changed

+714
-217
lines changed

History.md

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
client code changes; server only code changes will not cause the page
77
to reload.
88

9+
* Add `Meteor.onConnection` and add `this.connection` to method
10+
invocations and publish functions. These can be used to store data
11+
associated with individual clients between subscriptions and method
12+
calls. See http://docs.meteor.com/#meteor_onconnection for details.
13+
914
* Bundler failures cause non-zero exit code in `meteor run`. #1515
1015

1116
* Fix `meteor run` with settings files containing non-ASCII characters. #1497

docs/client/api.html

+47
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ <h2 id="publishandsubscribe"><span>Publish and subscribe</span></h2>
175175
{{> api_box subscription_error}}
176176
{{> api_box subscription_stop}}
177177

178+
{{> api_box subscription_connection}}
179+
178180
{{> api_box subscribe}}
179181

180182
When you subscribe to a record set, it tells the server to send records to the
@@ -276,6 +278,7 @@ <h2 id="methods_header"><span>Methods</span></h2>
276278
begin running.
277279
* `userId`: the id of the current user.
278280
* `setUserId`: a function that associates the current client with a user.
281+
* `connection`: on the server, the [connection](#meteor_onconnection) this method call was received on.
279282

280283
Calling `methods` on the client defines *stub* functions associated with
281284
server methods of the same name. You don't have to define a stub for
@@ -328,6 +331,8 @@ <h2 id="methods_header"><span>Methods</span></h2>
328331
returns. However, you can change this by calling `this.unblock`. This
329332
will allow the N+1th invocation to start running in a new fiber.
330333

334+
{{> api_box method_invocation_connection}}
335+
331336
{{> api_box error}}
332337

333338
If you want to return an error from a method, throw an exception. Methods can
@@ -462,6 +467,47 @@ <h2 id="connections"><span>Server connections</span></h2>
462467
This can be used to save battery on mobile devices when real time
463468
updates are not required.
464469

470+
471+
{{> api_box onConnection}}
472+
473+
`onConnection` returns an object with a single method `stop`. Calling
474+
`stop` unregisters the callback, so that this callback will no longer
475+
be called on new connections.
476+
477+
The callback is called with a single argument, the server-side
478+
`connection` representing the connection from the client. This object
479+
contains the following fields:
480+
481+
<dl class="objdesc">
482+
{{#dtdd name="id" type="String"}}
483+
A globally unique id for this connection.
484+
{{/dtdd}}
485+
486+
{{#dtdd name="close" type="Function"}}
487+
Close this DDP connection. The client is free to reconnect, but will
488+
receive a different connection with a new `id` if it does.
489+
{{/dtdd}}
490+
491+
{{#dtdd name="onClose" type="Function"}}
492+
Register a callback to be called when the connection is closed. If the
493+
connection is already closed, the callback will be called immediately.
494+
{{/dtdd}}
495+
</dl>
496+
497+
{{#note}}
498+
Currently when a client reconnects to the server (such as after
499+
temporarily losing its Internet connection), it will get a new
500+
connection each time. The `onConnection` callbacks will be called
501+
again, and the new connection will have a new connection `id`.
502+
503+
In the future, when client reconnection is fully implemented,
504+
reconnecting from the client will reconnect to the same connection on
505+
the server: the `onConnection` callback won't be called for that
506+
connection again, and the connection will still have the same
507+
connection `id`.
508+
{{/note}}
509+
510+
465511
{{> api_box connect}}
466512

467513
To call methods on another Meteor application or subscribe to its data
@@ -496,6 +542,7 @@ <h2 id="connections"><span>Server connections</span></h2>
496542
`Meteor.apply`, you are using a connection back to that default
497543
server.
498544

545+
499546
<h2 id="collections"><span>Collections</span></h2>
500547

501548
Meteor stores data in *collections*. To get started, declare a

docs/client/api.js

+27
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,14 @@ Template.api.subscription_userId = {
318318
};
319319

320320

321+
Template.api.subscription_connection = {
322+
id: "publish_connection",
323+
name: "<i>this</i>.connection",
324+
locus: "Server",
325+
descr: ["Access inside the publish function. The incoming [connection](#meteor_onconnection) for this subscription."]
326+
};
327+
328+
321329
Template.api.subscribe = {
322330
id: "meteor_subscribe",
323331
name: "Meteor.subscribe(name [, arg1, arg2, ... ] [, callbacks])",
@@ -381,6 +389,13 @@ Template.api.method_invocation_isSimulation = {
381389
descr: ["Access inside a method invocation. Boolean value, true if this invocation is a stub."]
382390
};
383391

392+
Template.api.method_invocation_connection = {
393+
id: "method_connection",
394+
name: "<i>this</i>.connection",
395+
locus: "Server",
396+
descr: ["Access inside a method invocation. The [connection](#meteor_onconnection) this method was received on. `null` if the method is not associated with a connection, eg. a server initiated method call."]
397+
};
398+
384399
Template.api.error = {
385400
id: "meteor_error",
386401
name: "new Meteor.Error(error, reason, details)",
@@ -479,6 +494,18 @@ Template.api.connect = {
479494
]
480495
};
481496

497+
Template.api.onConnection = {
498+
id: "meteor_onconnection",
499+
name: "Meteor.onConnection(callback)",
500+
locus: "Server",
501+
descr: ["Register a callback to be called when a new DDP connection is made to the server."],
502+
args: [
503+
{name: "callback",
504+
type: "function",
505+
descr: "The function to call when a new DDP connection is established."}
506+
]
507+
};
508+
482509
// onAutopublish
483510

484511
Template.api.meteor_collection = {

docs/client/docs.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ var toc = [
122122
{instance: "this", name: "ready", id: "publish_ready"},
123123
{instance: "this", name: "onStop", id: "publish_onstop"},
124124
{instance: "this", name: "error", id: "publish_error"},
125-
{instance: "this", name: "stop", id: "publish_stop"}
125+
{instance: "this", name: "stop", id: "publish_stop"},
126+
{instance: "this", name: "connection", id: "publish_connection"}
126127
],
127128
"Meteor.subscribe"
128129
],
@@ -132,7 +133,8 @@ var toc = [
132133
{instance: "this", name: "userId", id: "method_userId"},
133134
{instance: "this", name: "setUserId", id: "method_setUserId"},
134135
{instance: "this", name: "isSimulation", id: "method_issimulation"},
135-
{instance: "this", name: "unblock", id: "method_unblock"}
136+
{instance: "this", name: "unblock", id: "method_unblock"},
137+
{instance: "this", name: "connection", id: "method_connection"}
136138
],
137139
"Meteor.Error",
138140
"Meteor.call",
@@ -143,6 +145,7 @@ var toc = [
143145
"Meteor.status",
144146
"Meteor.reconnect",
145147
"Meteor.disconnect",
148+
"Meteor.onConnection",
146149
"DDP.connect"
147150
],
148151

packages/accounts-base/accounts_server.js

+94-6
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,14 @@ Meteor.methods({
7878
var result = tryAllLoginHandlers(options);
7979
if (result !== null) {
8080
this.setUserId(result.id);
81-
this._setLoginToken(result.token);
81+
Accounts._setLoginToken(this.connection.id, result.token);
8282
}
8383
return result;
8484
},
8585

8686
logout: function() {
87-
var token = this._getLoginToken();
88-
this._setLoginToken(null);
87+
var token = Accounts._getLoginToken(this.connection.id);
88+
Accounts._setLoginToken(this.connection.id, null);
8989
if (token && this.userId)
9090
removeLoginToken(this.userId, token);
9191
this.setUserId(null);
@@ -139,11 +139,101 @@ Meteor.methods({
139139
}
140140
});
141141

142+
///
143+
/// ACCOUNT DATA
144+
///
145+
146+
// connectionId -> {connection, loginToken, srpChallenge}
147+
var accountData = {};
148+
149+
Accounts._getAccountData = function (connectionId, field) {
150+
var data = accountData[connectionId];
151+
return data && data[field];
152+
};
153+
154+
Accounts._setAccountData = function (connectionId, field, value) {
155+
var data = accountData[connectionId];
156+
157+
// safety belt. shouldn't happen. accountData is set in onConnection,
158+
// we don't have a connectionId until it is set.
159+
if (!data)
160+
return;
161+
162+
if (value === undefined)
163+
delete data[field];
164+
else
165+
data[field] = value;
166+
};
167+
168+
Meteor.server.onConnection(function (connection) {
169+
accountData[connection.id] = {connection: connection};
170+
connection.onClose(function () {
171+
removeConnectionFromToken(connection.id);
172+
delete accountData[connection.id];
173+
});
174+
});
175+
176+
142177
///
143178
/// RECONNECT TOKENS
144179
///
145180
/// support reconnecting using a meteor login token
146181

182+
// token -> list of connection ids
183+
var connectionsByLoginToken = {};
184+
185+
// test hook
186+
Accounts._getTokenConnections = function (token) {
187+
return connectionsByLoginToken[token];
188+
};
189+
190+
// Remove the connection from the list of open connections for the token.
191+
var removeConnectionFromToken = function (connectionId) {
192+
var token = Accounts._getLoginToken(connectionId);
193+
if (token) {
194+
connectionsByLoginToken[token] = _.without(
195+
connectionsByLoginToken[token],
196+
connectionId
197+
);
198+
if (_.isEmpty(connectionsByLoginToken[token]))
199+
delete connectionsByLoginToken[token];
200+
}
201+
};
202+
203+
Accounts._getLoginToken = function (connectionId) {
204+
return Accounts._getAccountData(connectionId, 'loginToken');
205+
};
206+
207+
Accounts._setLoginToken = function (connectionId, newToken) {
208+
removeConnectionFromToken(connectionId);
209+
210+
Accounts._setAccountData(connectionId, 'loginToken', newToken);
211+
212+
if (newToken) {
213+
if (! _.has(connectionsByLoginToken, newToken))
214+
connectionsByLoginToken[newToken] = [];
215+
connectionsByLoginToken[newToken].push(connectionId);
216+
}
217+
};
218+
219+
// Close all open connections associated with any of the tokens in
220+
// `tokens`.
221+
var closeConnectionsForTokens = function (tokens) {
222+
_.each(tokens, function (token) {
223+
if (_.has(connectionsByLoginToken, token)) {
224+
// safety belt. close should defer potentially yielding callbacks.
225+
Meteor._noYieldsAllowed(function () {
226+
_.each(connectionsByLoginToken[token], function (connectionId) {
227+
var connection = Accounts._getAccountData(connectionId, 'connection');
228+
if (connection)
229+
connection.close();
230+
});
231+
});
232+
}
233+
});
234+
};
235+
236+
147237
// Login handler for resume tokens.
148238
Accounts.registerLoginHandler(function(options) {
149239
if (!options.resume)
@@ -646,9 +736,7 @@ Meteor.startup(function () {
646736
///
647737

648738
var closeTokensForUser = function (userTokens) {
649-
Meteor.server._closeAllForTokens(_.map(userTokens, function (token) {
650-
return token.token;
651-
}));
739+
closeConnectionsForTokens(_.pluck(userTokens, "token"));
652740
};
653741

654742
// Like _.difference, but uses EJSON.equals to compute which values to return.

packages/accounts-base/accounts_tests.js

+22
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,25 @@ Tinytest.addAsync('accounts - expire numeric token', function (test, onComplete)
208208
});
209209
Accounts._expireTokens(new Date(), result.id);
210210
});
211+
212+
213+
Tinytest.addAsync(
214+
'accounts - connection data cleaned up',
215+
function (test, onComplete) {
216+
makeTestConnection(
217+
test,
218+
function (clientConn, serverConn) {
219+
// onClose callbacks are called in order, so we run after the
220+
// close callback in accounts.
221+
serverConn.onClose(function () {
222+
test.isFalse(Accounts._getAccountData(serverConn.id, 'connection'));
223+
onComplete();
224+
});
225+
226+
test.isTrue(Accounts._getAccountData(serverConn.id, 'connection'));
227+
serverConn.close();
228+
},
229+
onComplete
230+
);
231+
}
232+
);

packages/accounts-base/package.js

+1
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,6 @@ Package.on_test(function (api) {
4545
api.use('accounts-base');
4646
api.use('tinytest');
4747
api.use('random');
48+
api.use('test-helpers');
4849
api.add_files('accounts_tests.js', 'server');
4950
});

0 commit comments

Comments
 (0)