Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

ably

Package Overview
Dependencies
Maintainers
1
Versions
187
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ably - npm Package Compare versions

Comparing version 0.9.0-beta.6 to 0.9.0-beta.7

5

browser/lib/transport/xhrrequest.js

@@ -110,6 +110,7 @@ var XHRRequest = (function() {

if(!accept)
if(!accept) {
headers['accept'] = 'application/json';
else if(accept != 'application/json')
} else if(accept.indexOf('application/json') === -1) {
responseType = 'arraybuffer';
}

@@ -116,0 +117,0 @@ if(body) {

36

common/lib/client/auth.js
var Auth = (function() {
var msgpack = Platform.msgPack;
var msgpack = Platform.msgpack;
var MAX_TOKENOBJECT_LENGTH = Math.pow(2, 17);
var MAX_TOKENSTRING_LENGTH = 384;
function noop() {}

@@ -306,3 +308,3 @@ function random() { return ('000000' + Math.floor(Math.random() * 1E16)).slice(-16); }

tokenRequestCallback = function(params, cb) {
var authHeaders = Utils.mixin({accept: 'application/json'}, authOptions.authHeaders),
var authHeaders = Utils.mixin({accept: 'application/json, text/plain'}, authOptions.authHeaders),
authParams = Utils.mixin(params, authOptions.authParams);

@@ -317,7 +319,22 @@ var authUrlRequestCallback = function(err, body, headers, unpacked) {

if(BufferUtils.isBuffer(body)) body = body.toString();
if(headers['content-type'] && headers['content-type'].indexOf('application/json') > -1) {
var contentType = headers['content-type'];
if(!contentType) {
cb(new ErrorInfo('authUrl response is missing a content-type header', 40170, 401));
return;
}
var json = contentType.indexOf('application/json') > -1,
text = contentType.indexOf('text/plain') > -1;
if(!json && !text) {
cb(new ErrorInfo('authUrl responded with unacceptable content-type ' + contentType + ', should be either text/plain or application/json', 40170, 401));
return;
}
if(json) {
if(body.length > MAX_TOKENOBJECT_LENGTH) {
cb(new ErrorInfo('authUrl response exceeded max permitted length', 40170, 401));
return;
}
try {
body = JSON.parse(body);
} catch(e) {
cb(new ErrorInfo('Unexpected error processing authURL response; err = ' + e.message, 40000, 400));
cb(new ErrorInfo('Unexpected error processing authURL response; err = ' + e.message, 40170, 401));
return;

@@ -399,3 +416,7 @@ }

if(typeof(tokenRequestOrDetails) === 'string') {
callback(null, {token: tokenRequestOrDetails});
if(tokenRequestOrDetails.length > MAX_TOKENSTRING_LENGTH) {
callback(new ErrorInfo('Token string exceeded max permitted length (was ' + tokenRequestOrDetails.length + ' bytes)', 40170, 401));
} else {
callback(null, {token: tokenRequestOrDetails});
}
return;

@@ -409,2 +430,7 @@ }

}
var objectSize = JSON.stringify(tokenRequestOrDetails).length;
if(objectSize > MAX_TOKENOBJECT_LENGTH) {
callback(new ErrorInfo('Token request/details object exceeded max permitted stringified size (was ' + objectSize + ' bytes)', 40170, 401));
return;
}
if('issued' in tokenRequestOrDetails) {

@@ -411,0 +437,0 @@ /* a tokenDetails object */

@@ -279,4 +279,7 @@ var RealtimeChannel = (function() {

case actions.ATTACHED:
this.attachSerial = message.channelSerial;
if(this.state === 'attached') {
if(!message.hasFlag('RESUMED')) {
/* On a loss of continuity, the presence set needs to be re-synced */
this.presence.onAttached(message.hasFlag('HAS_PRESENCE'))
var change = new ChannelStateChange(this.state, this.state, false, message.error);

@@ -286,3 +289,3 @@ this.emit('update', change);

} else {
this.setAttached(message);
this.notifyState('attached', message.error, message.hasFlag('RESUMED'), message.hasFlag('HAS_PRESENCE'));
}

@@ -395,17 +398,5 @@ break;

RealtimeChannel.prototype.setAttached = function(message) {
Logger.logAction(Logger.LOG_MINOR, 'RealtimeChannel.setAttached', 'activating channel; name = ' + this.name + '; message flags = ' + message.flags);
RealtimeChannel.prototype.onAttached = function() {
Logger.logAction(Logger.LOG_MINOR, 'RealtimeChannel.onAttached', 'activating channel; name = ' + this.name);
/* Remember the channel serial at the moment of attaching in
* order to support untilAttach flag for history retrieval */
this.attachSerial = message.channelSerial;
/* update any presence included with this message */
if(message.presence)
this.presence.setPresence(message.presence, false);
/* ensure we don't transition multiple times */
if(this.state != 'attaching')
return;
var pendingEvents = this.pendingEvents, pendingCount = pendingEvents.length;

@@ -424,12 +415,5 @@ if(pendingCount) {

}
var syncInProgress = message.hasFlag('HAS_PRESENCE');
var resumed = message.hasFlag('RESUMED');
if(syncInProgress) {
this.presence.awaitSync();
}
this.setInProgress(syncOp, syncInProgress);
this.notifyState('attached', message.reason, resumed);
};
RealtimeChannel.prototype.notifyState = function(state, reason, resumed) {
RealtimeChannel.prototype.notifyState = function(state, reason, resumed, hasPresence) {
Logger.logAction(Logger.LOG_MICRO, 'RealtimeChannel.notifyState', 'name = ' + this.name + ', current state = ' + this.state + ', notifying state ' + state);

@@ -441,8 +425,5 @@ this.clearStateTimer();

}
this.presence.actOnChannelState(state, reason);
this.presence.actOnChannelState(state, hasPresence, reason);
if(state !== 'attached' && state !== 'attaching') {
this.failPendingMessages(reason || RealtimeChannel.invalidStateError(state));
if(state === 'detached' || state === 'failed') {
this.presence._clearMyMembers();
}
}

@@ -463,2 +444,4 @@ if(state === 'suspended' && this.connectionManager.state.sendEvents) {

if(state === 'attached') {
this.onAttached();
this.setInProgress(syncOp, hasPresence);
this.setInProgress(statechangeOp, false);

@@ -465,0 +448,0 @@ } else if(state === 'detached' || state === 'failed' || state === 'suspended') {

@@ -24,2 +24,3 @@ var RealtimePresence = (function() {

case 'attached':
case 'suspended':
action();

@@ -188,2 +189,16 @@ break;

/* Special-case the suspended state: can still get (stale) presence set if waitForSync is false */
if(this.channel.state === 'suspended') {
if(waitForSync) {
callback(ErrorInfo.fromValues({
statusCode: 400,
code: 91005,
message: 'Presence state is out of sync due to channel being in the SUSPENDED state'
}));
} else {
returnMembers(this.members);
}
return;
}
var self = this;

@@ -276,3 +291,14 @@ waitAttached(this.channel, callback, function() {

RealtimePresence.prototype.setAttached = function() {
RealtimePresence.prototype.onAttached = function(hasPresence) {
Logger.logAction(Logger.LOG_MINOR, 'RealtimePresence.onAttached()', 'channel = ' + this.channel.name + ', hasPresence = ' + hasPresence);
if(hasPresence) {
this.members.startSync();
} else {
this._synthesizeLeaves(this.members.values());
this.members.clear();
this._ensureMyMembersPresent();
}
/* NB this must be after the _ensureMyMembersPresent call, which may add items to pendingPresence */
var pendingPresence = this.pendingPresence,

@@ -285,3 +311,3 @@ pendingPresCount = pendingPresence.length;

var multicaster = Multicaster();
Logger.logAction(Logger.LOG_MICRO, 'RealtimePresence.setAttached', 'sending ' + pendingPresCount + ' queued presence messages');
Logger.logAction(Logger.LOG_MICRO, 'RealtimePresence.onAttached', 'sending ' + pendingPresCount + ' queued presence messages');
for(var i = 0; i < pendingPresCount; i++) {

@@ -296,12 +322,14 @@ var event = pendingPresence[i];

RealtimePresence.prototype.actOnChannelState = function(state, err) {
RealtimePresence.prototype.actOnChannelState = function(state, hasPresence, err) {
switch(state) {
case 'attached':
this.setAttached();
this.onAttached(hasPresence);
break;
case 'detached':
case 'failed':
this._clearMyMembers();
this.members.clear();
/* falls through */
case 'suspended':
this.failPendingPresence(err);
this.members.clear();
break;

@@ -331,4 +359,5 @@ }

var msg = 'Presence auto-re-enter failed: ' + err.toString();
var wrappedErr = new ErrorInfo(msg, 91004, 400);
Logger.logAction(Logger.LOG_ERROR, 'RealtimePresence._ensureMyMembersPresent()', msg);
var change = new ChannelStateChange(self.channel.state, self.channel.state, true, err);
var change = new ChannelStateChange(self.channel.state, self.channel.state, true, wrappedErr);
self.channel.emit('update', change);

@@ -348,5 +377,15 @@ }

RealtimePresence.prototype.awaitSync = function() {
Logger.logAction(Logger.LOG_MINOR, 'PresenceMap.awaitSync()', 'channel = ' + this.channel.name);
this.members.startSync();
RealtimePresence.prototype._synthesizeLeaves = function(items) {
var subscriptions = this.subscriptions;
Utils.arrForEach(items, function(item) {
var presence = PresenceMessage.fromValues({
action: 'leave',
connectionId: item.connectionId,
clientId: item.clientId,
data: item.data,
encoding: item.encoding,
timestamp: Utils.now()
});
subscriptions.emit('leave', presence);
});
};

@@ -522,3 +561,4 @@

/* any members that were present at the start of the sync,
* and have not been seen in sync, can be removed */
* and have not been seen in sync, can be removed, and leave events emitted */
this.presence._synthesizeLeaves(Utils.valuesArray(this.residualMembers));
for(var memberKey in this.residualMembers) {

@@ -525,0 +565,0 @@ delete map[memberKey];

@@ -6,2 +6,4 @@ var Stats = (function() {

this.data = (values && values.data) || 0;
this.failed = (values && values.failed) || 0;
this.refused = (values && values.refused) || 0;
}

@@ -8,0 +10,0 @@

"use strict";
var Crypto = (function() {
var crypto = require('crypto');
var hexy = require('hexy');
var util = require('util');

@@ -204,4 +203,2 @@

Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.encrypt()', '');
//console.log('encrypt: plaintext:');
//console.log(hexy.hexy(plaintext));
var plaintextLength = plaintext.length,

@@ -212,4 +209,2 @@ paddedLength = getPaddedLength(plaintextLength),

var ciphertext = Buffer.concat([iv, toBuffer(cipherOut)]);
//console.log('encrypt: ciphertext:');
//console.log(hexy.hexy(ciphertext));
return callback(null, ciphertext);

@@ -219,4 +214,2 @@ };

CBCCipher.prototype.decrypt = function(ciphertext) {
//console.log('decrypt: ciphertext:');
//console.log(hexy.hexy(ciphertext));
var blockLength = this.blockLength,

@@ -228,4 +221,2 @@ decryptCipher = crypto.createDecipheriv(this.algorithm, this.key, ciphertext.slice(0, blockLength)),

plaintext = Buffer.concat([plaintext, toBuffer(final)]);
//console.log('decrypt: plaintext:');
//console.log(hexy.hexy(plaintext));
return plaintext;

@@ -232,0 +223,0 @@ };

{
"name": "ably",
"description": "Realtime client library for Ably.io, the realtime messaging service",
"version": "0.9.0-beta.6",
"version": "0.9.0-beta.7",
"main": "./nodejs/index.js",

@@ -14,5 +14,3 @@ "typings": "ably.ts.d",

"dependencies": {
"async": "git://github.com/ably-forks/async#requirejs",
"hexy": "~0.2",
"msgpack-js": "git://github.com/paddybyers/msgpack-js.git",
"msgpack-js": "git://github.com/ably-forks/msgpack-js.git",
"request": "^2.74.0",

@@ -22,2 +20,3 @@ "ws": "^1.1.1"

"devDependencies": {
"async": "git://github.com/ably-forks/async#requirejs",
"ejs": "~2.3",

@@ -35,2 +34,3 @@ "express": "~4.12",

"grunt-zip": "~0.16",
"hexy": "~0.2",
"karma": "git://github.com/ably-forks/karma.git#ably-js-custom",

@@ -37,0 +37,0 @@ "karma-chrome-launcher": "~0.1",

@@ -493,2 +493,6 @@ # [Ably](https://www.ably.io)

#### Browser-specific issues
* ["Unable to parse request body" error when publishing large messages from old versions of Internet Explorer](https://support.ably.io/solution/articles/3000062360-ably-js-unable-to-parse-request-body-error-when-publishing-large-messages-from-old-browsers)
## Contributing

@@ -495,0 +499,0 @@

@@ -20,4 +20,2 @@ {

"40013": "Invalid message data or encoding",
"40014": "Resource disposed",
"40020": "Batch error",

@@ -34,10 +32,6 @@ /* 401 codes */

"40120": "application disabled",
"40130": "key error (unspecified)",
"40130": "key disabled",
"40131": "key revoked",
"40132": "key expired",
"40133": "key disabled",
"40140": "token error (unspecified)",
"40141": "token revoked",
"40142": "token expired",
"40143": "token unrecognised",
"40140": "token expired",
"40150": "connection blocked (limits exceeded)",

@@ -105,8 +99,6 @@ "40160": "operation not permitted with provided capability",

"90006": "unable to recover channel (unbounded request)",
"90007": "channel operation failed (no response from server)",
"91000": "unable to enter presence channel (no clientId)",
"91001": "unable to enter presence channel (invalid channel state)",
"91002": "unable to leave presence channel that is not entered",
"91003": "unable to enter presence channel (maximum member limit exceeded)",
"91100": "member implicitly left presence channel (connection closed)"
}
{
"_post_apps": "/* JSON body using in POST sandbox-rest.ably.io/apps request to set up the Test app */",
"post_apps": {
"limits": { "presence": { "maxMembers": 250 } },
"keys": [

@@ -6,0 +5,0 @@ {},

@@ -378,3 +378,3 @@ "use strict";

/* RSA4c
/* RSA4c, RSA4e
* Try to connect with an authCallback that fails in various ways (calling back with an error, calling back with nothing, timing out, etc) should go to disconnected, not failed, and wrapped in a 80019 error code

@@ -414,2 +414,10 @@ */

exports.authCallback_too_long_string = authCallback_failures({authCallback: function(tokenParams, callback) {
var token = '';
for(var i=0; i<390; i++) {
token = token + 'a';
}
callback(null, token);
}});
exports.authUrl_timeout = authCallback_failures({

@@ -424,2 +432,6 @@ authUrl: helper.unroutableAddress,

exports.authUrl_wrong_content_type = authCallback_failures({
authUrl: 'http://example.com/'
});
/*

@@ -426,0 +438,0 @@ * Check state change reason is propogated during a disconnect

@@ -7,2 +7,3 @@ "use strict";

utils = helper.Utils,
createPM = Ably.Realtime.ProtocolMessage.fromDeserialized,
closeAndFinish = helper.closeAndFinish,

@@ -23,3 +24,2 @@ monitorConnection = helper.monitorConnection;

var rest, authToken, authToken2;

@@ -1442,6 +1442,7 @@ var testClientId = 'testclient', testClientId2 = 'testclient2';

/* Check the RTP5c/RTP17 auto-re-enter functionality by injecting a member
* into the private _myMembers set while suspended. Expect on re-attach and
* sync that member to be sent to realtime and, with luck, make its way into
* the normal presence set */
/* RTP5c2, RTP17
* Test the auto-re-enter functionality by injecting a member into the
* private _myMembers set while suspended. Expect on re-attach and sync that
* member to be sent to realtime and, with luck, make its way into the normal
* presence set */
exports.presence_auto_reenter = function(test) {

@@ -1487,3 +1488,2 @@ test.expect(7);

if(channel.presence.syncComplete()) {
channel.presence.awaitSync();
channel.sync();

@@ -1519,2 +1519,69 @@ }

/* RTP5c3
* Test failed presence auto-re-entering */
exports.presence_failed_auto_reenter = function(test) {
test.expect(5);
var channelName = "presence_failed_auto_reenter",
realtime, channel, token;
async.series([
function(cb) {
/* Request a token without the capabilities to be in the presence set */
var tokenParams = {clientId: 'me', capability: {}};
tokenParams.capability[channelName] = ['publish', 'subscribe'];
rest.auth.requestToken(tokenParams, function(err, tokenDetails) {
token = tokenDetails;
cb(err);
});
},
function(cb) {
realtime = helper.AblyRealtime({tokenDetails: token});
channel = realtime.channels.get(channelName);
realtime.connection.once('connected', function() { cb(); });
},
function(cb) { channel.attach(cb); },
function(cb) {
channel.presence.get(function(err, members) {
test.equal(members.length, 0, 'Check no-one in presence set');
cb();
});
},
function(cb) {
/* inject an additional member into the myMember set, then force a suspended state */
var connId = realtime.connection.connectionManager.connectionId;
channel.presence._myMembers.put({
action: 'enter',
clientId: 'me',
connectionId: connId,
id: connId + ':0:0'
});
helper.becomeSuspended(realtime, cb);
},
function(cb) {
realtime.connection.connect();
channel.once('attached', function() { cb(); });
},
function(cb) {
/* The channel will now try to auto-re-enter the me client, which will result in... */
channel.once(function(channelStateChange) {
test.equal(this.event, 'update', 'Check get an update event');
test.equal(channelStateChange.current, 'attached', 'Check still attached')
test.equal(channelStateChange.reason && channelStateChange.reason.code, 91004, 'Check error code')
cb();
})
},
function(cb) {
channel.presence.get(function(err, members) {
test.equal(members.length, 0, 'Check no-one in presence set');
cb();
});
}
], function(err) {
if(err) {
test.ok(false, helper.displayError(err));
}
closeAndFinish(test, realtime);
});
};
/* Enter ten clients while attaching, finish the attach, check they were all entered correctly */

@@ -1562,3 +1629,230 @@ exports.multiple_pending = function(test) {

/* RTP19
* Check that a LEAVE message is published for anyone in the local presence
* set but missing from a sync */
exports.leave_published_for_member_missing_from_sync = function(test) {
test.expect(6);
var realtime = helper.AblyRealtime(),
continuousClientId = 'continuous',
goneClientId = 'gone',
continuousRealtime = helper.AblyRealtime({clientId: continuousClientId}),
channelName = 'leave_published_for_member_missing_from_sync',
channel = realtime.channels.get(channelName),
continuousChannel = continuousRealtime.channels.get(channelName);
monitorConnection(test, realtime);
monitorConnection(test, continuousRealtime);
async.series([
function(cb) { continuousRealtime.connection.whenState('connected', function() { cb(); }); },
function(cb) { continuousChannel.attach(cb); },
function(cb) { continuousChannel.presence.enter(cb); },
function(cb) { realtime.connection.whenState('connected', function() { cb(); }); },
function(cb) { channel.attach(cb); },
function(cb) {
channel.presence.get({waitForSync: true}, function(err, members) {
test.equal(members && members.length, 1, 'Check one member present');
cb(err);
});
},
function(cb) {
/* Inject an additional member locally */
channel.onMessage({
"action": 14,
"id": "messageid:0",
"connectionId": "connid",
"timestamp": utils.now(),
"presence": [{
"clientId": goneClientId,
"action": 'enter'
}]});
channel.presence.get(function(err, members) {
test.equal(members && members.length, 2, 'Check two members present');
cb(err);
});
},
function(cb) {
channel.presence.subscribe(function(presmsg) {
test.equal(presmsg.action, 'leave', 'Check action was leave');
test.equal(presmsg.clientId, goneClientId, 'Check goneClient has left');
cb();
});
channel.sync();
},
function(cb) {
channel.presence.get({waitForSync: true}, function(err, members) {
test.equal(members && members.length, 1, 'Check back to one member present');
test.equal(members && members[0] && members[0].clientId, continuousClientId, 'check cont still present')
cb(err);
});
},
], function(err) {
if(err) {
test.ok(false, helper.displayError(err));
return;
}
closeAndFinish(test, [realtime, continuousRealtime]);
});
};
/* RTP19a
* Check that a LEAVE message is published for anyone in the local presence
* set if get an ATTACHED with no HAS_PRESENCE */
exports.leave_published_for_members_on_presenceless_attached = function(test) {
test.expect(4);
var realtime = helper.AblyRealtime(),
channelName = 'leave_published_for_members_on_presenceless_attached',
channel = realtime.channels.get(channelName),
fakeClientId = 'faker';
monitorConnection(test, realtime);
async.series([
function(cb) { realtime.connection.whenState('connected', function() { cb(); }); },
function(cb) { channel.attach(cb); },
function(cb) {
/* Inject a member locally */
channel.onMessage({
"action": 14,
"id": "messageid:0",
"connectionId": "connid",
"timestamp": utils.now(),
"presence": [{
"clientId": fakeClientId,
"action": 'enter'
}]});
channel.presence.get(function(err, members) {
test.equal(members && members.length, 1, 'Check one member present');
cb(err);
});
},
function(cb) {
channel.presence.subscribe(function(presmsg) {
test.equal(presmsg.action, 'leave', 'Check action was leave');
test.equal(presmsg.clientId, fakeClientId, 'Check fake client has left');
cb();
});
/* Inject an ATTACHED with RESUMED and HAS_PRESENCE both false */
channel.onMessage(createPM({
"action": 11,
"channelSerial": channel.attachSerial,
"flags": 0
}));
},
function(cb) {
channel.presence.get(function(err, members) {
test.equal(members && members.length, 0, 'Check no members present');
cb(err);
});
},
], function(err) {
if(err) {
test.ok(false, helper.displayError(err));
return;
}
closeAndFinish(test, realtime);
});
};
/* RTP5f; RTP11d
* Check that on ATTACHED -> SUSPENDED -> ATTACHED, members map is preserved
* and only members that changedbetween ATTACHED stats should result in
* presence events */
exports.suspended_preserves_presence = function(test) {
test.expect(8);
var mainRealtime = helper.AblyRealtime({clientId: 'main'}),
continuousRealtime = helper.AblyRealtime({clientId: 'continuous'}),
leavesRealtime = helper.AblyRealtime({clientId: 'leaves'}),
channelName = 'suspended_preserves_presence',
mainChannel = mainRealtime.channels.get(channelName);
monitorConnection(test, continuousRealtime);
monitorConnection(test, leavesRealtime);
var enter = function(rt, outerCb) {
var channel = rt.channels.get(channelName);
async.series([
function(cb) { rt.connection.whenState('connected', function() { cb(); }); },
function(cb) { channel.attach(cb); },
function(cb) { channel.presence.enter(cb); }
], outerCb);
};
var waitFor = function(expectedClientId) {
return function(cb) {
var presenceHandler = function(presmsg) {
if(expectedClientId == presmsg.clientId) {
mainChannel.presence.unsubscribe(presenceHandler);
cb();
}
};
mainChannel.presence.subscribe(presenceHandler);
};
};
async.series([
function(cb) {
enter(mainRealtime, cb);
},
function(cb) {
waitFor('continuous')(cb);
enter(continuousRealtime, function(err) { if(err) cb(err); });
},
function(cb) {
waitFor('leaves')(cb);
enter(leavesRealtime, function(err) { if(err) cb(err); });
},
function(cb) {
mainChannel.presence.get(function(err, members) {
test.equal(members.length, 3, 'Check all three expected members here');
cb(err);
});
},
function(cb) {
helper.becomeSuspended(mainRealtime, cb);
},
function(cb) {
mainChannel.presence.get(function(err) {
/* Check RTP11d: get() returns an error by default */
test.ok(err, 'Check error returned by get() while suspended');
test.equal(err && err.code, 91005, 'Check error code for get() while suspende');
cb();
});
},
function(cb) {
mainChannel.presence.get({ waitForSync: false }, function(err, members) {
/* Check RTP11d: get() works while suspended if waitForSync: false */
test.ok(!err, 'Check no error returned by get() while suspended if waitForSync: false');
test.equal(members && members.length, 3, 'Check all three expected members here');
cb(err);
});
},
function(cb) {
leavesRealtime.connection.whenState('closed', function() { cb(); });
leavesRealtime.close();
},
function(cb) {
mainChannel.presence.subscribe(function(presmsg) {
test.equal(presmsg.clientId, 'leaves', 'Check the only presmsg we get is a leave from leaves');
test.equal(presmsg.action, 'leave', 'Check the only presmsg we get is a leave from leaves');
cb();
});
/* Don't need to reattach explicitly; should be done automatically on connected */
mainRealtime.connect();
},
function(cb) {
/* Wait a bit to make sure we don't receive any other presence messages */
setTimeout(cb, 1000);
},
function(cb) {
mainChannel.presence.get(function(err, members) {
test.equal(members && members.length, 2, 'Check two expected members here');
cb(err);
});
}
], function(err) {
if(err) {
test.ok(false, helper.displayError(err));
}
closeAndFinish(test, [mainRealtime, continuousRealtime, leavesRealtime]);
});
};
return module.exports = helper.withTimeout(exports);
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc