Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
543ba40
chore(gitignore): include bower components
PatrickJS Dec 29, 2014
dce5cde
chore(bower): include angular with mocks
PatrickJS Dec 29, 2014
3c84c75
chore: include karma conf
PatrickJS Dec 29, 2014
1784963
chore: build script
PatrickJS Dec 29, 2014
5c55414
chore(package.json): update devDependencies
PatrickJS Dec 29, 2014
8aad475
chore: allow test.sh/dist.sh with npm
PatrickJS Dec 29, 2014
6d820d9
chore(test): rename file and add tests
PatrickJS Dec 29, 2014
64fe2fc
feat: websocket mock
PatrickJS Dec 29, 2014
6ab6b64
refactor: $webSocket api
PatrickJS Dec 29, 2014
d0e8ac7
chore: build files
PatrickJS Dec 29, 2014
b54897b
chore(version): bump to 1.0.0
PatrickJS Dec 29, 2014
fcf6dd3
refactor(test): update to jasmine 2 from 1.3
PatrickJS Dec 29, 2014
f245e87
docs(readme): update with new api
PatrickJS Dec 29, 2014
905eeca
refactor(test): use more jasmine 2 api
PatrickJS Dec 29, 2014
ed817f5
chore(build): update build with map
PatrickJS Dec 29, 2014
53a0a0a
chore(dist): uglifyjs options
PatrickJS Dec 29, 2014
b0bd913
chore(bower): main as min
PatrickJS Dec 29, 2014
ec374f5
fix(test): test min
PatrickJS Dec 29, 2014
2977634
docs(readme): status and name
PatrickJS Dec 29, 2014
b82d796
chore: remove gruntfile and legacy module
PatrickJS Dec 29, 2014
4a35d4e
chore(.travis.yml): allow bower files
PatrickJS Dec 29, 2014
f13666e
chore: allow chrome/firefox in travis
PatrickJS Dec 29, 2014
571f0c3
docs(readme): update with badges and todo
PatrickJS Dec 29, 2014
05eff6d
docs(readme): include npm badge
PatrickJS Dec 29, 2014
c33acf8
docs(readme): bower version
PatrickJS Dec 29, 2014
4f4a4b7
docs(readme): update api, todo, Manual Tests
PatrickJS Dec 29, 2014
65c3c9e
feat($MockWebSocket): protocols and ssl
PatrickJS Dec 29, 2014
a987914
feat($WebSocket): protocols and ssl
PatrickJS Dec 29, 2014
57b953a
refactor(example): update with new api
PatrickJS Dec 29, 2014
2e302e7
refactor(*): rename $webSocket to $websocket
PatrickJS Dec 29, 2014
1223254
feat(*): legacy (ie8) support?
PatrickJS Dec 29, 2014
ae113eb
feat($WebSocketBackend): allow for commonjs ws
PatrickJS Dec 29, 2014
50294be
refactor($WebSocket): return this and readyState
PatrickJS Dec 29, 2014
7a48f93
chore(build): update build for 1.0.0
PatrickJS Dec 29, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
chore: build files
  • Loading branch information
PatrickJS committed Dec 29, 2014
commit d0e8ac72c88e7a584620d24d9326f2bccaaead17
90 changes: 90 additions & 0 deletions dist/angular-websocket-mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
(function() {

function $WebSocketBackend() {
var connectQueue = [];
var pendingConnects = [];
var closeQueue = [];
var pendingCloses = [];
var sendQueue = [];
var pendingSends = [];


function $MockWebSocket(url) {

}

$MockWebSocket.prototype.send = function (msg) {
pendingSends.push(msg);
};

$MockWebSocket.prototype.close = function () {
pendingCloses.push(true);
};


this.createWebSocketBackend = function (url) {
pendingConnects.push(url);

return new $MockWebSocket(url);
};

this.flush = function () {
var url, msg;
while (url = pendingConnects.shift()) {
var i = connectQueue.indexOf(url);
if (i > -1) {
connectQueue.splice(i, 1);
}
}

while (pendingCloses.shift()) {
closeQueue.shift();
}

while (msg = pendingSends.shift()) {
var j;
sendQueue.forEach(function(pending, i) {
if (pending.message === msg.message) {
j = i;
}
});

if (j > -1) {
sendQueue.splice(j, 1);
}
}
};

this.expectConnect = function (url) {
connectQueue.push(url);
};

this.expectClose = function () {
closeQueue.push(true);
};

this.expectSend = function (msg) {
sendQueue.push(msg);
};

this.verifyNoOutstandingExpectation = function () {
if (connectQueue.length || closeQueue.length || sendQueue.length) {
throw new Error('Requests waiting to be flushed');
}
};

this.verifyNoOutstandingRequest = function () {
if (pendingConnects.length || pendingCloses.length || pendingSends.length) {
throw new Error('Requests waiting to be processed');
}
};

} // end $WebSocketBackend

angular.module('ngWebSocketMock', [])
.service('WebSocketBackend', $WebSocketBackend)
.service('$webSocketBackend', $WebSocketBackend);

angular.module('angular-websocket-mock', ['ngWebSocketMock']);

}());
262 changes: 262 additions & 0 deletions dist/angular-websocket.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
(function() {

var noop = function() {};
var objectFreeze = (Object.freeze) ? Object.freeze : noop;
var objectDefineProperty = Object.defineProperty;
var isString = angular.isString;
var isFunction = angular.isFunction;
var isDefined = angular.isDefined;

$WebSocketProvider.$inject = ['$rootScope', '$q', '$timeout', '$webSocketBackend'];
function $WebSocketProvider($rootScope, $q, $timeout, $webSocketBackend) {

function safeDigest(autoApply) {
if (autoApply && !$rootScope.$$phase) {
$rootScope.$apply();
}
}

function $WebSocket(url) {
this.url = url;
this._reconnectAttempts = 0;
this.initialTimeout = 500; // 500ms
this.maxTimeout = 5 * 60 * 1000; // 5 minutes
this.sendQueue = [];
this.onOpenCallbacks = [];
this.onMessageCallbacks = [];
this.onErrorCallbacks = [];
this.onCloseCallbacks = [];
objectFreeze(this._readyStateConstants);

this._connect();
}

$WebSocket.prototype._readyStateConstants = {
'CONNECTING': 0,
'OPEN': 1,
'CLOSING': 2,
'CLOSED': 3,
'RECONNECT_ABORTED': 4
};

$WebSocket.prototype._reconnectableStatusCodes = [
4000
];

$WebSocket.prototype.close = function (force) {
if (force || !this.socket.bufferedAmount) {
this.socket.close();
}
};

$WebSocket.prototype._connect = function (force) {
if (force || !this.socket || this.socket.readyState !== 1) {
this.socket = $webSocketBackend.createWebSocketBackend(this.url);
this.socket.onopen = this._onOpenHandler.bind(this);
this.socket.onmessage = this._onMessageHandler.bind(this);
this.socket.onerror = this._onErrorHandler.bind(this);
this.socket.onclose = this._onCloseHandler.bind(this);
}
};

$WebSocket.prototype.fireQueue = function () {
while (this.sendQueue.length && this.socket.readyState === 1) {
var data = this.sendQueue.shift();

this.socket.send(
isString(data.message) ? data.message : JSON.stringify(data.message)
);
data.deferred.resolve();
}
};

$WebSocket.prototype.notifyOpenCallbacks = function () {
for (var i = 0; i < this.onOpenCallbacks.length; i++) {
this.onOpenCallbacks[i].call(this);
}
};

$WebSocket.prototype.notifyCloseCallbacks = function () {
for (var i = 0; i < this.onCloseCallbacks.length; i++) {
this.onCloseCallbacks[i].call(this);
}
};
$WebSocket.prototype.notifyErrorCallbacks = function (event) {
for (var i = 0; i < this.onErrorCallbacks.length; i++) {
this.onErrorCallbacks[i].call(this, event);
}
};

$WebSocket.prototype.onMessage = function (callback, options) {
if (!isFunction(callback)) {
throw new Error('Callback must be a function');
}

if (options && isDefined(options.filter) && !isString(options.filter) && !(options.filter instanceof RegExp)) {
throw new Error('Pattern must be a string or regular expression');
}

this.onMessageCallbacks.push({
fn: callback,
pattern: options? options.filter : undefined,
autoApply: options? options.autoApply : true
});
};

$WebSocket.prototype._onMessageHandler = function (message) {
var pattern;
var socket = this;
var currentCallback;
for (var i = 0; i < socket.onMessageCallbacks.length; i++) {
currentCallback = socket.onMessageCallbacks[i];
pattern = currentCallback.pattern;
if (pattern) {
if (isString(pattern) && message.data === pattern) {
currentCallback.fn.call(this, message);
safeDigest(currentCallback.autoApply);
}
else if (pattern instanceof RegExp && pattern.exec(message.data)) {
currentCallback.fn.call(this, message);
safeDigest(currentCallback.autoApply);
}
}
else {
currentCallback.fn.call(this, message);
safeDigest(currentCallback.autoApply);
}
}
};

$WebSocket.prototype.onClose = function (cb) {
this.onCloseCallbacks.push(cb);
};

$WebSocket.prototype.onOpen = function (cb) {
this.onOpenCallbacks.push(cb);
};

$WebSocket.prototype._onOpenHandler = function () {
this._reconnectAttempts = 0;
this.notifyOpenCallbacks();
this.fireQueue();
}
;
$WebSocket.prototype.onError = function (cb) {
this.onErrorCallbacks.push(cb);
};

$WebSocket.prototype._onErrorHandler = function (event) {
this.notifyErrorCallbacks(event);
};

$WebSocket.prototype._onCloseHandler = function (event) {
this.notifyCloseCallbacks();
if (this._reconnectableStatusCodes.indexOf(event.code) > -1) {
this.reconnect();
}
};

$WebSocket.prototype.send = function (data) {
var deferred = $q.defer();
var socket = this;
var promise = cancelableify(deferred.promise);

if (socket.readyState === socket._readyStateConstants.RECONNECT_ABORTED) {
deferred.reject('Socket connection has been closed');
}
else {
this.sendQueue.push({
message: data,
deferred: deferred
});
this.fireQueue();
}

// Credit goes to @btford
function cancelableify(promise) {
promise.cancel = cancel;
var then = promise.then;
promise.then = function() {
var newPromise = then.apply(this, arguments);
return cancelableify(newPromise);
};
return promise;
}

function cancel(reason) {
socket.sendQueue.splice(socket.sendQueue.indexOf(data), 1);
deferred.reject(reason);
return this;
}

return promise;
};

$WebSocket.prototype.reconnect = function () {
this.close();
$timeout(angular.bind(this, function() {
this._connect();
}), this._getBackoffDelay(++this._reconnectAttempts), true);

};
// Exponential Backoff Formula by Prof. Douglas Thain
// http://dthain.blogspot.co.uk/2009/02/exponential-backoff-in-distributed.html
$WebSocket.prototype._getBackoffDelay = function(attempt) {
var R = Math.random() + 1;
var T = this.initialTimeout;
var F = 2;
var N = attempt;
var M = this.maxTimeout;

return Math.floor(Math.min(R * T * Math.pow(F, N), M));
};

$WebSocket.prototype._setInternalState = function(state) {
if (Math.floor(state) !== state || state < 0 || state > 4) {
throw new Error('state must be an integer between 0 and 4, got: ' + state);
}

this._internalConnectionState = state;

angular.forEach(this.sendQueue, function(pending) {
pending.deferred.reject('Message cancelled due to closed socket connection');
});
};

if (objectDefineProperty) {
objectDefineProperty($WebSocket.prototype, 'readyState', {
get: function() {
return this._internalConnectionState || this.socket.readyState;
},
set: function() {
throw new Error('The readyState property is read-only');
}
});
}
return function(url) {
return new $WebSocket(url);
};
}

$WebSocketBackend.$inject = ['$window'];
function $WebSocketBackend($window) {
this.createWebSocketBackend = function (url) {
var match = /wss?:\/\//.exec(url);

if (!match) {
throw new Error('Invalid url provided');
}

return new $window.WebSocket(url);
};
}

angular.module('ngWebSocket', [])
.factory('$webSocket', $WebSocketProvider)
.factory('WebSocket', $WebSocketProvider)
.service('$webSocketBackend', $WebSocketBackend)
.service('WebSocketBackend', $WebSocketBackend);

angular.module('angular-websocket', ['ngWebSocket']);

}());
1 change: 1 addition & 0 deletions dist/angular-websocket.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.