645 lines
20 KiB
JavaScript
645 lines
20 KiB
JavaScript
![]() |
// Firechat is a simple, easily-extensible data layer for multi-user,
|
||
|
// multi-room chat, built entirely on [Firebase](https://firebase.com).
|
||
|
//
|
||
|
// The Firechat object is the primary conduit for all underlying data events.
|
||
|
// It exposes a number of methods for binding event listeners, creating,
|
||
|
// entering, or leaving chat rooms, initiating chats, sending messages,
|
||
|
// and moderator actions such as warning, kicking, or suspending users.
|
||
|
//
|
||
|
// Firechat.js 1.0.0
|
||
|
// https://firebase.com
|
||
|
// (c) 2014 Firebase
|
||
|
// License: MIT
|
||
|
|
||
|
// Setup
|
||
|
// --------------
|
||
|
(function(Firebase) {
|
||
|
|
||
|
// Establish a reference to the `window` object, and save the previous value
|
||
|
// of the `Firechat` variable.
|
||
|
var root = this,
|
||
|
previousFirechat = root.Firechat;
|
||
|
|
||
|
function Firechat(firebaseRef, options) {
|
||
|
|
||
|
// Instantiate a new connection to Firebase.
|
||
|
this._firebase = firebaseRef;
|
||
|
|
||
|
// User-specific instance variables.
|
||
|
this._user = null;
|
||
|
this._userId = null;
|
||
|
this._userName = null;
|
||
|
this._isModerator = false;
|
||
|
|
||
|
// A unique id generated for each session.
|
||
|
this._sessionId = null;
|
||
|
|
||
|
// A mapping of event IDs to an array of callbacks.
|
||
|
this._events = {};
|
||
|
|
||
|
// A mapping of room IDs to a boolean indicating presence.
|
||
|
this._rooms = {};
|
||
|
|
||
|
// A mapping of operations to re-queue on disconnect.
|
||
|
this._presenceBits = {};
|
||
|
|
||
|
// Commonly-used Firebase references.
|
||
|
this._userRef = null;
|
||
|
this._messageRef = this._firebase.child('room-messages');
|
||
|
this._roomRef = this._firebase.child('room-metadata');
|
||
|
this._privateRoomRef = this._firebase.child('room-private-metadata');
|
||
|
this._moderatorsRef = this._firebase.child('moderators');
|
||
|
this._suspensionsRef = this._firebase.child('suspensions');
|
||
|
this._usersOnlineRef = this._firebase.child('user-names-online');
|
||
|
|
||
|
// Setup and establish default options.
|
||
|
this._options = options || {};
|
||
|
|
||
|
// The number of historical messages to load per room.
|
||
|
this._options.numMaxMessages = this._options.numMaxMessages || 50;
|
||
|
}
|
||
|
|
||
|
// Run Firechat in *noConflict* mode, returning the `Firechat` variable to
|
||
|
// its previous owner, and returning a reference to the Firechat object.
|
||
|
Firechat.noConflict = function noConflict() {
|
||
|
root.Firechat = previousFirechat;
|
||
|
return Firechat;
|
||
|
};
|
||
|
|
||
|
// Export the Firechat object as a global.
|
||
|
root.Firechat = Firechat;
|
||
|
|
||
|
// Firechat Internal Methods
|
||
|
// --------------
|
||
|
Firechat.prototype = {
|
||
|
|
||
|
// Load the initial metadata for the user's account and set initial state.
|
||
|
_loadUserMetadata: function(onComplete) {
|
||
|
var self = this;
|
||
|
|
||
|
// Update the user record with a default name on user's first visit.
|
||
|
this._userRef.transaction(function(current) {
|
||
|
if (!current || !current.id || !current.name) {
|
||
|
return {
|
||
|
id: self._userId,
|
||
|
name: self._userName
|
||
|
};
|
||
|
}
|
||
|
}, function(error, committed, snapshot) {
|
||
|
self._user = snapshot.val();
|
||
|
self._moderatorsRef.child(self._userId).once('value', function(snapshot) {
|
||
|
self._isModerator = !!snapshot.val();
|
||
|
root.setTimeout(onComplete, 0);
|
||
|
});
|
||
|
});
|
||
|
},
|
||
|
|
||
|
// Initialize Firebase listeners and callbacks for the supported bindings.
|
||
|
_setupDataEvents: function() {
|
||
|
// Monitor connection state so we can requeue disconnect operations if need be.
|
||
|
this._firebase.root().child('.info/connected').on('value', function(snapshot) {
|
||
|
if (snapshot.val() === true) {
|
||
|
// We're connected (or reconnected)! Set up our presence state.
|
||
|
for (var i = 0; i < this._presenceBits; i++) {
|
||
|
var op = this._presenceBits[i],
|
||
|
ref = this._firebase.root().child(op.ref);
|
||
|
|
||
|
ref.onDisconnect().set(op.offlineValue);
|
||
|
ref.set(op.onlineValue);
|
||
|
}
|
||
|
}
|
||
|
}, this);
|
||
|
|
||
|
// Generate a unique session id for the visit.
|
||
|
var sessionRef = this._userRef.child('sessions').push();
|
||
|
this._sessionId = sessionRef.name();
|
||
|
this._queuePresenceOperation(sessionRef, true, null);
|
||
|
|
||
|
// Register our username in the public user listing.
|
||
|
var usernameRef = this._usersOnlineRef.child(this._userName.toLowerCase());
|
||
|
var usernameSessionRef = usernameRef.child(this._sessionId);
|
||
|
this._queuePresenceOperation(usernameSessionRef, {
|
||
|
id: this._userId,
|
||
|
name: this._userName
|
||
|
}, null);
|
||
|
|
||
|
// Listen for state changes for the given user.
|
||
|
this._userRef.on('value', this._onUpdateUser, this);
|
||
|
|
||
|
// Listen for chat invitations from other users.
|
||
|
this._userRef.child('invites').on('child_added', this._onFirechatInvite, this);
|
||
|
|
||
|
// Listen for messages from moderators and adminstrators.
|
||
|
this._userRef.child('notifications').on('child_added', this._onNotification, this);
|
||
|
},
|
||
|
|
||
|
// Append the new callback to our list of event handlers.
|
||
|
_addEventCallback: function(eventId, callback) {
|
||
|
this._events[eventId] = this._events[eventId] || [];
|
||
|
this._events[eventId].push(callback);
|
||
|
},
|
||
|
|
||
|
// Retrieve the list of event handlers for a given event id.
|
||
|
_getEventCallbacks: function(eventId) {
|
||
|
if (this._events.hasOwnProperty(eventId)) {
|
||
|
return this._events[eventId];
|
||
|
}
|
||
|
return [];
|
||
|
},
|
||
|
|
||
|
// Invoke each of the event handlers for a given event id with specified data.
|
||
|
_invokeEventCallbacks: function(eventId) {
|
||
|
var args = [],
|
||
|
callbacks = this._getEventCallbacks(eventId);
|
||
|
|
||
|
Array.prototype.push.apply(args, arguments);
|
||
|
args = args.slice(1);
|
||
|
|
||
|
for (var i = 0; i < callbacks.length; i += 1) {
|
||
|
callbacks[i].apply(null, args);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// Keep track of on-disconnect events so they can be requeued if we disconnect the reconnect.
|
||
|
_queuePresenceOperation: function(ref, onlineValue, offlineValue) {
|
||
|
ref.onDisconnect().set(offlineValue);
|
||
|
ref.set(onlineValue);
|
||
|
this._presenceBits[ref.toString()] = {
|
||
|
ref: ref,
|
||
|
onlineValue: onlineValue,
|
||
|
offlineValue: offlineValue
|
||
|
};
|
||
|
},
|
||
|
|
||
|
// Remove an on-disconnect event from firing upon future disconnect and reconnect.
|
||
|
_removePresenceOperation: function(path, value) {
|
||
|
var ref = new Firebase(path);
|
||
|
ref.onDisconnect().cancel();
|
||
|
ref.set(value);
|
||
|
delete this._presenceBits[path];
|
||
|
},
|
||
|
|
||
|
// Event to monitor current user state.
|
||
|
_onUpdateUser: function(snapshot) {
|
||
|
this._user = snapshot.val();
|
||
|
this._invokeEventCallbacks('user-update', this._user);
|
||
|
},
|
||
|
|
||
|
// Event to monitor current auth + user state.
|
||
|
_onAuthRequired: function() {
|
||
|
this._invokeEventCallbacks('auth-required');
|
||
|
},
|
||
|
|
||
|
// Events to monitor room entry / exit and messages additional / removal.
|
||
|
_onEnterRoom: function(room) {
|
||
|
this._invokeEventCallbacks('room-enter', room);
|
||
|
},
|
||
|
_onNewMessage: function(roomId, snapshot) {
|
||
|
var message = snapshot.val();
|
||
|
message.id = snapshot.name();
|
||
|
this._invokeEventCallbacks('message-add', roomId, message);
|
||
|
},
|
||
|
_onRemoveMessage: function(roomId, snapshot) {
|
||
|
var messageId = snapshot.name();
|
||
|
this._invokeEventCallbacks('message-remove', roomId, messageId);
|
||
|
},
|
||
|
_onLeaveRoom: function(roomId) {
|
||
|
this._invokeEventCallbacks('room-exit', roomId);
|
||
|
},
|
||
|
|
||
|
// Event to listen for notifications from administrators and moderators.
|
||
|
_onNotification: function(snapshot) {
|
||
|
var notification = snapshot.val();
|
||
|
if (!notification.read) {
|
||
|
if (notification.notificationType !== 'suspension' || notification.data.suspendedUntil < new Date().getTime()) {
|
||
|
snapshot.ref().child('read').set(true);
|
||
|
}
|
||
|
this._invokeEventCallbacks('notification', notification);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// Events to monitor chat invitations and invitation replies.
|
||
|
_onFirechatInvite: function(snapshot) {
|
||
|
var self = this,
|
||
|
invite = snapshot.val();
|
||
|
|
||
|
// Skip invites we've already responded to.
|
||
|
if (invite.status) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
invite.id = invite.id || snapshot.name();
|
||
|
self.getRoom(invite.roomId, function(room) {
|
||
|
invite.toRoomName = room.name;
|
||
|
self._invokeEventCallbacks('room-invite', invite);
|
||
|
});
|
||
|
},
|
||
|
_onFirechatInviteResponse: function(snapshot) {
|
||
|
var self = this,
|
||
|
invite = snapshot.val();
|
||
|
|
||
|
invite.id = invite.id || snapshot.name();
|
||
|
this._invokeEventCallbacks('room-invite-response', invite);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Firechat External Methods
|
||
|
// --------------
|
||
|
|
||
|
// Initialize the library and setup data listeners.
|
||
|
Firechat.prototype.setUser = function(userId, userName, callback) {
|
||
|
var self = this;
|
||
|
|
||
|
self._firebase.root().child('.info/authenticated').on('value', function(snapshot) {
|
||
|
var authenticated = snapshot.val();
|
||
|
if (authenticated) {
|
||
|
self._firebase.root().child('.info/authenticated').off();
|
||
|
|
||
|
self._userId = userId.toString();
|
||
|
self._userName = userName.toString();
|
||
|
self._userRef = self._firebase.child('users').child(self._userId);
|
||
|
self._loadUserMetadata(function() {
|
||
|
root.setTimeout(function() {
|
||
|
callback(self._user);
|
||
|
self._setupDataEvents();
|
||
|
}, 0);
|
||
|
});
|
||
|
} else {
|
||
|
self.warn('Firechat requires an authenticated Firebase reference. Pass an authenticated reference before loading.');
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
// Resumes the previous session by automatically entering rooms.
|
||
|
Firechat.prototype.resumeSession = function() {
|
||
|
this._userRef.child('rooms').once('value', function(snapshot) {
|
||
|
var rooms = snapshot.val();
|
||
|
for (var roomId in rooms) {
|
||
|
this.enterRoom(rooms[roomId].id);
|
||
|
}
|
||
|
}, /* onError */ function(){}, /* context */ this);
|
||
|
};
|
||
|
|
||
|
// Callback registration. Supports each of the following events:
|
||
|
Firechat.prototype.on = function(eventType, cb) {
|
||
|
this._addEventCallback(eventType, cb);
|
||
|
};
|
||
|
|
||
|
// Create and automatically enter a new chat room.
|
||
|
Firechat.prototype.createRoom = function(roomName, roomType, callback) {
|
||
|
var self = this,
|
||
|
newRoomRef = this._roomRef.push();
|
||
|
|
||
|
var newRoom = {
|
||
|
id: newRoomRef.name(),
|
||
|
name: roomName,
|
||
|
type: roomType || 'public',
|
||
|
createdByUserId: this._userId,
|
||
|
createdAt: Firebase.ServerValue.TIMESTAMP
|
||
|
};
|
||
|
|
||
|
if (roomType === 'private') {
|
||
|
newRoom.authorizedUsers = {};
|
||
|
newRoom.authorizedUsers[this._userId] = true;
|
||
|
}
|
||
|
|
||
|
newRoomRef.set(newRoom, function(error) {
|
||
|
if (!error) {
|
||
|
self.enterRoom(newRoomRef.name());
|
||
|
}
|
||
|
if (callback) {
|
||
|
callback(newRoomRef.name());
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
// Enter a chat room.
|
||
|
Firechat.prototype.enterRoom = function(roomId) {
|
||
|
var self = this;
|
||
|
self.getRoom(roomId, function(room) {
|
||
|
var roomName = room.name;
|
||
|
|
||
|
if (!roomId || !roomName) return;
|
||
|
|
||
|
// Skip if we're already in this room.
|
||
|
if (self._rooms[roomId]) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
self._rooms[roomId] = true;
|
||
|
|
||
|
if (self._user) {
|
||
|
// Save entering this room to resume the session again later.
|
||
|
self._userRef.child('rooms').child(roomId).set({
|
||
|
id: roomId,
|
||
|
name: roomName,
|
||
|
active: true
|
||
|
});
|
||
|
|
||
|
// Set presence bit for the room and queue it for removal on disconnect.
|
||
|
var presenceRef = self._firebase.child('room-users').child(roomId).child(self._userId).child(self._sessionId);
|
||
|
self._queuePresenceOperation(presenceRef, {
|
||
|
id: self._userId,
|
||
|
name: self._userName
|
||
|
}, null);
|
||
|
}
|
||
|
|
||
|
// Invoke our callbacks before we start listening for new messages.
|
||
|
self._onEnterRoom({ id: roomId, name: roomName });
|
||
|
|
||
|
// Setup message listeners
|
||
|
self._roomRef.child(roomId).once('value', function(snapshot) {
|
||
|
self._messageRef.child(roomId).limit(self._options.numMaxMessages).on('child_added', function(snapshot) {
|
||
|
self._onNewMessage(roomId, snapshot);
|
||
|
}, /* onCancel */ function() {
|
||
|
// Turns out we don't have permission to access these messages.
|
||
|
self.leaveRoom(roomId);
|
||
|
}, /* context */ self);
|
||
|
|
||
|
self._messageRef.child(roomId).limit(self._options.numMaxMessages).on('child_removed', function(snapshot) {
|
||
|
self._onRemoveMessage(roomId, snapshot);
|
||
|
}, /* onCancel */ function(){}, /* context */ self);
|
||
|
}, /* onFailure */ function(){}, self);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
// Leave a chat room.
|
||
|
Firechat.prototype.leaveRoom = function(roomId) {
|
||
|
var self = this,
|
||
|
userRoomRef = self._firebase.child('room-users').child(roomId);
|
||
|
|
||
|
// Remove listener for new messages to this room.
|
||
|
self._messageRef.child(roomId).off();
|
||
|
|
||
|
if (self._user) {
|
||
|
var presenceRef = userRoomRef.child(self._userId).child(self._sessionId);
|
||
|
|
||
|
// Remove presence bit for the room and cancel on-disconnect removal.
|
||
|
self._removePresenceOperation(presenceRef.toString(), null);
|
||
|
|
||
|
// Remove session bit for the room.
|
||
|
self._userRef.child('rooms').child(roomId).remove();
|
||
|
}
|
||
|
|
||
|
delete self._rooms[roomId];
|
||
|
|
||
|
// Invoke event callbacks for the room-exit event.
|
||
|
self._onLeaveRoom(roomId);
|
||
|
};
|
||
|
|
||
|
Firechat.prototype.sendMessage = function(roomId, messageContent, messageType, cb) {
|
||
|
var self = this,
|
||
|
message = {
|
||
|
userId: self._userId,
|
||
|
name: self._userName,
|
||
|
timestamp: Firebase.ServerValue.TIMESTAMP,
|
||
|
message: messageContent,
|
||
|
type: messageType || 'default'
|
||
|
},
|
||
|
newMessageRef;
|
||
|
|
||
|
if (!self._user) {
|
||
|
self._onAuthRequired();
|
||
|
if (cb) {
|
||
|
cb(new Error('Not authenticated or user not set!'));
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
newMessageRef = self._messageRef.child(roomId).push();
|
||
|
newMessageRef.setWithPriority(message, Firebase.ServerValue.TIMESTAMP, cb);
|
||
|
};
|
||
|
|
||
|
Firechat.prototype.deleteMessage = function(roomId, messageId, cb) {
|
||
|
var self = this;
|
||
|
|
||
|
self._messageRef.child(roomId).child(messageId).remove(cb);
|
||
|
};
|
||
|
|
||
|
// Mute or unmute a given user by id. This list will be stored internally and
|
||
|
// all messages from the muted clients will be filtered client-side after
|
||
|
// receipt of each new message.
|
||
|
Firechat.prototype.toggleUserMute = function(userId, cb) {
|
||
|
var self = this;
|
||
|
|
||
|
if (!self._user) {
|
||
|
self._onAuthRequired();
|
||
|
if (cb) {
|
||
|
cb(new Error('Not authenticated or user not set!'));
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
self._userRef.child('muted').child(userId).transaction(function(isMuted) {
|
||
|
return (isMuted) ? null : true;
|
||
|
}, cb);
|
||
|
};
|
||
|
|
||
|
// Send a moderator notification to a specific user.
|
||
|
Firechat.prototype.sendSuperuserNotification = function(userId, notificationType, data, cb) {
|
||
|
var self = this,
|
||
|
userNotificationsRef = self._firebase.child('users').child(userId).child('notifications');
|
||
|
|
||
|
userNotificationsRef.push({
|
||
|
fromUserId: self._userId,
|
||
|
timestamp: Firebase.ServerValue.TIMESTAMP,
|
||
|
notificationType: notificationType,
|
||
|
data: data || {}
|
||
|
}, cb);
|
||
|
};
|
||
|
|
||
|
// Warn a user for violating the terms of service or being abusive.
|
||
|
Firechat.prototype.warnUser = function(userId) {
|
||
|
var self = this;
|
||
|
|
||
|
self.sendSuperuserNotification(userId, 'warning');
|
||
|
};
|
||
|
|
||
|
// Suspend a user by putting the user into read-only mode for a period.
|
||
|
Firechat.prototype.suspendUser = function(userId, timeLengthSeconds, cb) {
|
||
|
var self = this,
|
||
|
suspendedUntil = new Date().getTime() + 1000*timeLengthSeconds;
|
||
|
|
||
|
self._suspensionsRef.child(userId).set(suspendedUntil, function(error) {
|
||
|
if (error && cb) {
|
||
|
return cb(error);
|
||
|
} else {
|
||
|
self.sendSuperuserNotification(userId, 'suspension', {
|
||
|
suspendedUntil: suspendedUntil
|
||
|
});
|
||
|
return cb(null);
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
// Invite a user to a specific chat room.
|
||
|
Firechat.prototype.inviteUser = function(userId, roomId) {
|
||
|
var self = this,
|
||
|
sendInvite = function() {
|
||
|
var inviteRef = self._firebase.child('users').child(userId).child('invites').push();
|
||
|
inviteRef.set({
|
||
|
id: inviteRef.name(),
|
||
|
fromUserId: self._userId,
|
||
|
fromUserName: self._userName,
|
||
|
roomId: roomId
|
||
|
});
|
||
|
|
||
|
// Handle listen unauth / failure in case we're kicked.
|
||
|
inviteRef.on('value', self._onFirechatInviteResponse, function(){}, self);
|
||
|
};
|
||
|
|
||
|
if (!self._user) {
|
||
|
self._onAuthRequired();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
self.getRoom(roomId, function(room) {
|
||
|
if (room.type === 'private') {
|
||
|
var authorizedUserRef = self._roomRef.child(roomId).child('authorizedUsers');
|
||
|
authorizedUserRef.child(userId).set(true, function(error) {
|
||
|
if (!error) {
|
||
|
sendInvite();
|
||
|
}
|
||
|
});
|
||
|
} else {
|
||
|
sendInvite();
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
Firechat.prototype.acceptInvite = function(inviteId, cb) {
|
||
|
var self = this;
|
||
|
|
||
|
self._userRef.child('invites').child(inviteId).once('value', function(snapshot) {
|
||
|
var invite = snapshot.val();
|
||
|
if (invite === null && cb) {
|
||
|
return cb(new Error('acceptInvite(' + inviteId + '): invalid invite id'));
|
||
|
} else {
|
||
|
self.enterRoom(invite.roomId);
|
||
|
self._userRef.child('invites').child(inviteId).update({
|
||
|
'status': 'accepted',
|
||
|
'toUserName': self._userName
|
||
|
}, cb);
|
||
|
}
|
||
|
}, self);
|
||
|
};
|
||
|
|
||
|
Firechat.prototype.declineInvite = function(inviteId, cb) {
|
||
|
var self = this,
|
||
|
updates = {
|
||
|
'status': 'declined',
|
||
|
'toUserName': self._userName
|
||
|
};
|
||
|
|
||
|
self._userRef.child('invites').child(inviteId).update(updates, cb);
|
||
|
};
|
||
|
|
||
|
Firechat.prototype.getRoomList = function(cb) {
|
||
|
var self = this;
|
||
|
|
||
|
self._roomRef.once('value', function(snapshot) {
|
||
|
cb(snapshot.val());
|
||
|
});
|
||
|
};
|
||
|
|
||
|
Firechat.prototype.getUsersByRoom = function() {
|
||
|
var self = this,
|
||
|
roomId = arguments[0],
|
||
|
query = self._firebase.child('room-users').child(roomId),
|
||
|
cb = arguments[arguments.length - 1],
|
||
|
limit = null;
|
||
|
|
||
|
if (arguments.length > 2) {
|
||
|
limit = arguments[1];
|
||
|
}
|
||
|
|
||
|
query = (limit) ? query.limit(limit) : query;
|
||
|
|
||
|
query.once('value', function(snapshot) {
|
||
|
var usernames = snapshot.val() || {},
|
||
|
usernamesUnique = {};
|
||
|
|
||
|
for (var username in usernames) {
|
||
|
for (var session in usernames[username]) {
|
||
|
// Skip all other sessions for this user as we only need one.
|
||
|
usernamesUnique[username] = usernames[username][session];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
root.setTimeout(function() {
|
||
|
cb(usernamesUnique);
|
||
|
}, 0);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
Firechat.prototype.getUsersByPrefix = function(prefix, startAt, endAt, limit, cb) {
|
||
|
var self = this,
|
||
|
query = this._usersOnlineRef,
|
||
|
prefixLower = prefix.toLowerCase();
|
||
|
|
||
|
if (startAt) {
|
||
|
query = query.startAt(null, startAt);
|
||
|
} else if (endAt) {
|
||
|
query = query.endAt(null, endAt);
|
||
|
} else {
|
||
|
query = (prefixLower) ? query.startAt(null, prefixLower) : query.startAt();
|
||
|
}
|
||
|
|
||
|
query = (limit) ? query.limit(limit) : query;
|
||
|
|
||
|
query.once('value', function(snapshot) {
|
||
|
var usernames = snapshot.val() || {},
|
||
|
usernamesFiltered = {};
|
||
|
|
||
|
for (var userNameKey in usernames) {
|
||
|
var sessions = usernames[userNameKey],
|
||
|
userName, userId, usernameClean;
|
||
|
|
||
|
// Grab the user data from the first registered active session.
|
||
|
for (var sessionId in sessions) {
|
||
|
userName = sessions[sessionId].name;
|
||
|
userId = sessions[sessionId].id;
|
||
|
|
||
|
// Skip all other sessions for this user as we only need one.
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Filter out any usernames that don't match our prefix and break.
|
||
|
if ((prefix.length > 0) && (userName.toLowerCase().indexOf(prefixLower) !== 0))
|
||
|
continue;
|
||
|
|
||
|
usernamesFiltered[userName] = {
|
||
|
name: userName,
|
||
|
id: userId
|
||
|
};
|
||
|
}
|
||
|
|
||
|
root.setTimeout(function() {
|
||
|
cb(usernamesFiltered);
|
||
|
}, 0);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
// Miscellaneous helper methods.
|
||
|
Firechat.prototype.getRoom = function(roomId, callback) {
|
||
|
this._roomRef.child(roomId).once('value', function(snapshot) {
|
||
|
callback(snapshot.val());
|
||
|
});
|
||
|
};
|
||
|
|
||
|
Firechat.prototype.userIsModerator = function() {
|
||
|
return this._isModerator;
|
||
|
};
|
||
|
|
||
|
Firechat.prototype.warn = function(msg) {
|
||
|
if (console) {
|
||
|
msg = 'Firechat Warning: ' + msg;
|
||
|
if (typeof console.warn === 'function') {
|
||
|
console.warn(msg);
|
||
|
} else if (typeof console.log === 'function') {
|
||
|
console.log(msg);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
})(Firebase);
|