Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Different approach to connecting users with each other

  • Loading branch information...
commit 799abca9bc4593240b12272c53c3894cc21487f0 1 parent da29dc3
@aduston aduston authored
View
11 conversations/channels.js
@@ -1,11 +0,0 @@
-exports.inviteChannel = function(userID) {
- return "invite" + userID;
-}
-
-exports.invitationResponseChannel = function(token) {
- return "invres" + token;
-}
-
-exports.invitationConfirmationChannel = function(token) {
- return "invcon" + token;
-}
View
53 conversations/inviteecontroller.js
@@ -1,53 +0,0 @@
-var pubsub = require('../singletonpubsub.js'),
-channels = require('./channels.js'),
-_und = require('underscore');
-
-/**
- * @constructor
- */
-var InviteeController = function(userID, socket) {
- this.userID_ = userID;
- this.socket_ = socket;
-};
-
-InviteeController.prototype.listenForInvitations = function() {
- pubsub.subscribe(
- channels.inviteChannel(this.userID_),
- _und.bind(this.invitationReceived_, this));
-};
-
-InviteeController.prototype.invitationReceived_ = function(token) {
- this.socket_.emit("invitationReceived", { token: token });
-};
-
-InviteeController.prototype.invitationResponseReceived = function(data) {
- var that = this;
- pubsub.publish(
- channels.invitationResponseChannel(data.token),
- data.response);
- if (data.response == InviteeResponse.ACCEPTED) {
- var confirmationChannel =
- channels.invitationConfirmationChannel(data.token);
- var unsubscribe = function() {
- pubsub.unsubscribe(confirmationChannel);
- };
- pubsub.subscribe(
- confirmationChannel,
- function(accepted) {
- that.invitationConfirmationReceived_(data.token, accepted);
- unsubscribe();
- });
- setTimeout(unsubscribe, 4000);
- }
-};
-
-InviteeController.prototype.invitationConfirmationReceived_ =
- function(token, accepted)
-{
- this.socket_.emit(
- "invitationConfirmation",
- { token: token,
- accepted: accepted });
-};
-
-module.exports = exports = InviteeController;
View
14 conversations/inviteeresponse.js
@@ -1,14 +0,0 @@
-/**
- *
- * @enum {string}
- */
-var InviteeResponse = {
- TIMEOUT: 'timeout',
- CONFLICT: 'conflict',
- ACCEPTED: 'accepted',
- REJECTED: 'rejected'
-};
-
-if (module) {
- module.exports = exports = InviteeResponse;
-}
View
143 conversations/invitercontroller.js
@@ -1,143 +0,0 @@
-var pubsub = require('../singletonpubsub.js'),
-WaitingUser = require('../models/waitinguser.js'),
-_und = require('underscore'),
-InviterResponse = require('./inviterresponse.js');
-
-var PING_THRESHOLD = 8;
-
-var InviterController = function(userID) {
- /**
- * @type {ObjectId}
- */
- this.userID_ = userID;
-}
-
-/**
- * @param {Array.<Array.<string>>} languagePairs
- * @param {function(InviterResponse)} callback
- */
-InviterController.prototype.makeInvitations =
- function(languagePairs, callback)
-{
- console.log("makeInvitations");
- this.callback_ = callback;
- var userList = [];
- var numQueriesExecuted = 0;
- var pingTimeout = new Date(new Date().getTime() - PING_THRESHOLD * 1000);
- var that = this;
- _und.each(
- languagePairs,
- function(lp) {
- WaitingUser
- .where('languages').elemMatch({
- foreignLanguage: lp[0],
- nativeLanguage: lp[1]
- })
- .where('lastPing').gte(pingTimeout)
- .run(function(err, results) {
- userList.concat(results);
- numQueriesExecuted++;
- if (numQueriesExecuted == languagePairs.length) {
- that.processAndInviteUserList_(userList);
- }
- });
- });
-};
-
-InviterController.prototype.processAndInviteUserList_ =
- function(rawUserList)
-{
- if (rawUserList.length == 0) {
- this.callback_(InviterResponse.ZERO_USERS);
- return;
- }
- rawUserList = _und.sortBy(
- rawUserList,
- function(user) { return user.userID; });
- rawUserList = _und.uniq(
- rawUserList, true,
- function(user) { return user.userID; });
- // see http://www.mongodb.org/display/DOCS/Optimizing+Object+IDs#OptimizingObjectIDs-Sortbyidtosortbyinsertiontime
- rawUserList = _und.sortBy(
- rawUserList,
- function(user) { return user._id; });
- this.inviteUserList_(rawUserList);
-};
-
-InviterController.prototype.inviteUserList_ = function(userIDList) {
- console.log("inviteUserList_");
- console.log(userIDList);
- var that = this;
- var accepted = false;
- var numResponses = 0;
- var atLeastOneConflict = false;
- this.iterateUsers_(userIDList, function(userID) {
- that.inviteUser_(userID, function(response, invitedCallback) {
- numResponses++;
- if (response == InviteeResponse.CONFLICT) {
- atLeastOneConflict = true;
- }
- else if (response == InviteeResponse.ACCEPTED) {
- if (!accepted) {
- invitedCallback(true);
- accepted = true;
- that.callback_(InviterResponse.MATCH);
- }
- else {
- invitedCallback(false);
- }
- }
- if (numResponses == userIDList.length && !accepted) {
- if (atLeastOneConflict) {
- that.callback_(InviterResponse.CONFLICT);
- }
- else {
- that.callback_(InviterResponse.NONE);
- }
- }
- });
- });
-};
-
-InviterController.prototype.iterateUsers_ =
- function(userList, fn, opt_curIndex)
-{
- var curIndex = opt_curIndex || 0;
- fn(userList[curIndex].userID);
- setTimeout(
- _und.bind(this.iterateUsers_, this, userList, fn, curIndex + 1),
- 250);
-};
-
-/**
- * @param {ObjectId} userID
- * @param {function(InviteeResponse, function(boolean)=)} callback
- * Called to indicate whether the invited user got the slot or
- * not.
- */
-InviterController.prototype.inviteUser_ = function(userID, callback) {
- var token = [this.userID_.toString(), userID.toString()].join(':');
- var timedOut = false;
- pubsub.publish(inviteChannel(userID), token);
- var confirm = function(accepted) {
- pubsub.publish(invitationConfirmationChannel(token), accepted);
- };
- pubsub.subscribe(invitationResponseChannel(token),
- function(inviteeResponse) {
- if (timedOut) {
- confirm(false);
- }
- else {
- callback(inviteeReponse, confirm);
- }
- });
- setTimeout(
- function() {
- timedOut = true;
- pubsub.unsubscribe(invitationResponseChannel(token));
- callback(InviteeResponse.TIMEOUT);
- },
- 5000);
-};
-
-module.exports = exports = InviterController;
View
21 conversations/inviterresponse.js
@@ -1,21 +0,0 @@
-if (typeof module == 'undefined') {
- goog.provide('ll.InviterResponse');
-}
-
-/**
- *
- * @enum {string}
- */
-var InviterResponse = {
- MATCH: 'match',
- CONFLICT: 'conflict',
- NONE: 'none',
- ZERO_USERS: 'zerousers'
-};
-
-if (typeof module != 'undefined') {
- module.exports = exports = InviterResponse;
-}
-else {
- ll.InviterResponse = InviterResponse;
-}
View
12 models/match.js
@@ -0,0 +1,12 @@
+var mongoose = require('mongoose');
+
+var MatchSchema = new mongoose.Schema({
+ user0: mongoose.Schema.ObjectId,
+ user1: mongoose.Schema.ObjectId,
+ user0LastPing: Date,
+ user1LastPing: Date,
+ dateStarted: Date
+});
+
+var Match = exports = module.exports =
+ mongoose.model("match", MatchSchema);
View
23 models/user.js
@@ -6,18 +6,19 @@ var UserSchema = new mongoose.Schema({
email: { type: String, required: false, index: { unique: true, sparse: true }},
hashedPassword: String,
salt: String,
- fb: { type: {
- id: String,
- username: String,
- displayName: String,
- lastName: String,
- firstName: String,
- middleName: String,
- gender: String,
- profileURL: String,
- },
- required: false
+ fb: {
+ type: {
+ id: String,
+ username: String,
+ displayName: String,
+ lastName: String,
+ firstName: String,
+ middleName: String,
+ gender: String,
+ profileURL: String,
},
+ required: false
+ },
nativeLanguages: [String],
foreignLanguages: [String]
});
View
70 models/waitinguser.js
@@ -6,37 +6,83 @@ var LanguagePairSchema = new mongoose.Schema({
nativeLanguage: String
});
+/**
+ * Uses userID as _id.
+ */
var WaitingUserSchema = new mongoose.Schema({
- userID: { type: mongoose.Schema.ObjectId, unique: true },
- lastPing: Date,
+ waitStart: { type: Date, index: true },
languages: [LanguagePairSchema]
});
WaitingUserSchema.index(
- { lastPing: -1,
- 'languages.foreignLanguage': 1,
+ { 'languages.foreignLanguage': 1,
'languages.nativeLanguage': 1});
-WaitingUserSchema.statics.ping =
+/**
+ * @param {Array.<Array>} languagePairs Array of pairs.
+ * @param {function(?WaitingUser)} callback
+ */
+function nextAvailableUser(languagePairs, callback) {
+ if (languagePairs.length == 0) {
+ callback(null);
+ }
+ else {
+ var pingTimeout = new Date(
+ new Date().getTime() - PING_THRESHOLD * 1000);
+ var languagePair = languagePairs.pop();
+ this.findOneAndRemove(
+ {
+ languages: {
+ '$elemMatch': {
+ foreignLanguage: languagePair[1],
+ nativeLanguage: languagePair[0]
+ }
+ }
+ },
+ { sort: { waitStart: -1 } },
+ function(doc) {
+ if (doc) {
+ callback(doc);
+ }
+ else {
+ nextAvailableUser(languagePairs, callback);
+ }
+ });
+ }
+}
+
+WaitingUserSchema.statics.nextAvailableUser = nextAvailableUser;
+
+/**
+ * Stops waiting
+ */
+WaitingUserSchema.statics.stop = function(userID) {
+ this.remove({ _id: mongoose.Types.ObjectId(userID) });
+};
+
+/**
+ * Starts waiting
+ */
+WaitingUserSchema.statics.start =
function(userID, languagePairs, callback)
{
- languagePairs = _und.map(
+ var modelLanguagePairs = _und.map(
languagePairs,
function(pair) {
return {
foreignLanguage: pair[1],
nativeLanguage: pair[0]
};
- });
+ });
this.update(
- { userID: userID },
- { lastPing: new Date(),
- languages: languagePairs },
- { upsert: true},
+ { _id: userID },
+ { waitStart: new Date(),
+ languagePairs: modelLanguagePairs },
+ { upsert: true },
function(err, numAffected) {
callback();
});
-};
+}
var WaitingUser = exports = module.exports =
mongoose.model("waitinguser", WaitingUserSchema);
View
6 public/js/conversations/waitingRoutine.js
@@ -24,12 +24,6 @@ ll.WaitingRoutine.prototype.start = function(socket, engagementCallback) {
this.waiting_ = true;
this.socket_ = socket;
this.engagementCallback_ = engagementCallback;
- this.invitationListener_ = ll.InvitationListener.getInstance();
- this.invitationMaker_ = ll.InvitationMaker.getInstance();
- this.invitationListener_.listen(
- socket,
- goog.bind(this.invitationReceived_, this));
- this.attemptToEngage_();
};
ll.WaitingRoutine.prototype.attemptToEngage_ = function() {
View
70 routes/conversations.js
@@ -5,8 +5,7 @@ WaitingUser = require('../models/waitinguser'),
_und = require('underscore'),
settings = require('../settings'),
socketio = require('socket.io'),
-InviteeController = require('../conversations/inviteecontroller.js'),
-InviterController = require('../conversations/invitercontroller.js');
+pubsub = require('../singletonpubsub');
function updateConversationStats(user) {
var date = new Date();
@@ -16,35 +15,58 @@ function updateConversationStats(user) {
});
}
-function addMakeInvitationListener(socket, userID) {
- console.log("Adding makeInvitationsListener");
- socket.on(
- 'makeInvitations',
- function(languagePairs, callback) {
- console.log("makeInvitations called");
- var inviterController = new InviterController(userID);
- inviterController.makeInvitations(
- languagePairs, callback);
- });
+function waitingChannel(userID) {
+ return userID + "_waiting";
}
-function addReceiveInvitationListener(socket, userID) {
- var inviteeController = new InviteeController(userID);
- inviteeController.listenForInvitations(socket);
- socket.on(
- "invitationResponse",
- _und.bind(
- inviteeController.invitationResponseReceived,
- inviteeController));
+function socketStarted(socket, userID, languagePairs) {
+ socket.on('disconnect', function() {
+ stopWaiting(socket, userID);
+ });
+ WaitingUser.nextAvailableUser(
+ languagePairs,
+ function(matchedUser) {
+ if (matchedUser) {
+ stopWaiting(socket, userID);
+ startMatch(socket, userID, matchedUser._id);
+ }
+ else {
+ startWaiting(socket, userID, languagePairs);
+ }
+ });
}
-function socketStarted(socket, userID, languagePairs) {
- WaitingUser.ping(userID, languagePairs, function() {
- addMakeInvitationListener(socket, userID);
- addReceiveInvitationListener(socket, userID);
+function startMatch(socket, userID, matchedUserID) {
+ var match = new Match();
+ match.user0 = mongoose.Type.ObjectId(userID);
+ match.user1 = matchedUserID;
+ match.dateStarted = new Date();
+ match.save(function(err) {
+ socket.emit("matchStarted", match._id + '');
+ pubsub.publish(
+ waitingChannel(matchedUserID + ''),
+ match._id + '');
});
}
+function stopWaiting(socket, userID) {
+ WaitingUser.stop(userID);
+ pubsub.unsubscribe(waitingChannel(userID));
+}
+
+function startWaiting(socket, userID, languagePairs) {
+ pubsub.subscribe(
+ waitingChannel(userID),
+ function(matchID) {
+ stopWaiting(socket, userID);
+ socket.emit("matchStarted", matchID + '');
+ });
+ WaitingUser.start(
+ mongoose.Types.ObjectId(userID),
+ languagePairs,
+ function() {});
+}
+
module.exports = function(app, io) {
app.get(
'/conversations', decorators.ensureUserLanguages,

0 comments on commit 799abca

Please sign in to comment.
Something went wrong with that request. Please try again.