groupme
Version:
A Node Module for interacting with the GroupMe API. Supports both querying the Stateless API and receiving Websocket-based push messages, and provides a CLI.
217 lines (182 loc) • 6.7 kB
JavaScript
/************************************************************************
* Internal Node Modules
***********************************************************************/
var events = require('events');
var util = require('util');
/************************************************************************
* External Dependencies
***********************************************************************/
var WebSocketClient = require('websocket').client; //Websockets
/************************************************************************
* Internal Dependencies
***********************************************************************/
var Constants = require('./Constants');
/************************************************************************
* Private Constants
***********************************************************************/
//These are internal states, and also messages that can be emitted:
const STATE_DISCONNECTED = "disconnected";
const STATE_PENDING = "pending";
const STATE_CONNECTED = "connected";
/*
access_token [required] - your application API token for authentication purposes
userid [required] - your userid, used to subscribe to your push messages
groupids [optional] - an array of groupids to get partial updates from
*/
function IncomingStream(access_token, userid, groupids) {
this.access_token = access_token;
this.userid = userid;
this.groupids = groupids;
this.client = new WebSocketClient();
this.connection = null;
this.socketId = 1;
this.state = STATE_DISCONNECTED;
var self = this;
var connectionInitHelper = function(connection) {
connection.on('close', function(reasonCode, description) {
self.emit('status', 'Websocket Disconnected.', description);
this.connection = null;
self.setState(STATE_DISCONNECTED);
});
connection.on('error', function(error) {
self.handleError("Websocket experienced error.", error);
self.setState(STATE_DISCONNECTED);
})
connection.on('message', function(msg) {
if (msg.type === 'utf8' && msg.utf8Data) {
self.emit('status', 'Received:', msg);
each(JSON.parse(msg.utf8Data), function(data) {
if (data["channel"] == "/meta/handshake") {
self.handshakeResponse(data);
} else if (data["channel"] == "/meta/subscribe") {
self.subscribeResponse(data);
} else {
self.emit('message', data);
}
});
} else {
self.emit('error','Received a non-utf8 message.', msg);
}
});
return connection;
}
this.client.on('connect', function(connection) {
self.setState(STATE_PENDING);
self.connection = connectionInitHelper(connection);
self.emit('status', 'Websocket Connected');
self.handshake();
});
this.client.on('connectFailed', function(err) {
self.setState(STATE_DISCONNECTED);
self.connection = null;
self.emit('error', 'Connection Failed', err);
});
};
util.inherits(IncomingStream, events.EventEmitter);
/************************************************************************
* Externally-used API
***********************************************************************/
IncomingStream.prototype.connect = function() {
this.client.connect(Constants.WEBSOCKETS_BASEURL);
};
IncomingStream.prototype.disconnect = function() {
if (this.connection)
this.connection.close();
this.emit('status', 'Sending disconnect request to server.')
}
/************************************************************************
* Internal functions to handle handshaking
***********************************************************************/
IncomingStream.prototype.handleError = function(str, payload) {
self.emit('error', str, payload);
}
IncomingStream.prototype.setState = function(state) {
this.state = state;
this.emit(state);
}
IncomingStream.prototype.handshake = function() {
var data = {
channel: '/meta/handshake',
version: '1.0',
supportedConnectionTypes: ['websocket','callback-polling']
};
this.send([data]);
};
IncomingStream.prototype.handshakeResponse = function(data) {
if (data["successful"]) {
this.emit('status', 'Handshake succeeded!');
this.clientId = data["clientId"];
this.subscribeUser();
} else {
this.emit('error','Handshake failed!');
}
}
// Sends a request to listen for any messages to posed to
// the given userid.
// Without this, we would receive no messages.
IncomingStream.prototype.subscribeUser = function() {
var data = {
channel: '/meta/subscribe',
clientId: this.clientId,
subscription: '/user/' + this.userid,
ext: {
access_token: this.access_token,
timestamp: Date.now()
}
};
this.send([data]);
}
// Catches the response
IncomingStream.prototype.subscribeResponse = function(data) {
if (data["successful"]) {
this.startListening();
if (data["subscription"] == '/user/' + this.userid && this.groupids) {
this.subscribeGroups();
}
} else {
this.emit('error', 'Subscribing to user or group failed!');
}
}
// Sends a request to listen for in-progress typing notifications
// to the list of this.group_ids
IncomingStream.prototype.subscribeGroups = function() {
var self = this;
each(this.groupids, function(groupid) {
var data = {
channel: '/meta/subscribe',
clientId: self.clientId,
subscription: '/group/' + groupid,
ext: {
access_token: self.access_token,
timestamp: Date.now()
}
};
self.send([data]);
})
}
// Sends a request to listen
// Note: This is actually only necessary when you poll.
IncomingStream.prototype.startListening = function() {
var data = {
channel: '/meta/connect',
clientId: this.clientId,
connectionType: 'websocket',
};
this.send([data]);
this.setState(STATE_CONNECTED);
}
// Sends a json object to the server
// Appends a globally unique identifier
IncomingStream.prototype.send = function(data) {
var self = this;
each(data, function(d) {
self.socketId++;
d.id = self.socketId;
});
this.emit('status', 'Sending: ', data);
this.connection.send(JSON.stringify(data));
};
var each = function(obj, iterator, context) {
obj.forEach(iterator, context);
};
module.exports = IncomingStream;