8fdf58971c
// FREEBIE
195 lines
6.7 KiB
JavaScript
195 lines
6.7 KiB
JavaScript
/*
|
|
* vim: ts=4:sw=4:expandtab
|
|
*/
|
|
;(function(){
|
|
'use strict';
|
|
|
|
/*
|
|
* WebSocket-Resources
|
|
*
|
|
* Create a request-response interface over websockets using the
|
|
* WebSocket-Resources sub-protocol[1].
|
|
*
|
|
* var client = new WebSocketResource(socket, function(request) {
|
|
* request.respond(200, 'OK');
|
|
* });
|
|
*
|
|
* client.sendRequest({
|
|
* verb: 'PUT',
|
|
* path: '/v1/messages',
|
|
* body: '{ some: "json" }',
|
|
* success: function(message, status, request) {...},
|
|
* error: function(message, status, request) {...}
|
|
* });
|
|
*
|
|
* 1. https://github.com/WhisperSystems/WebSocket-Resources
|
|
*
|
|
*/
|
|
|
|
var Request = function(options) {
|
|
this.verb = options.verb || options.type;
|
|
this.path = options.path || options.url;
|
|
this.body = options.body || options.data;
|
|
this.success = options.success;
|
|
this.error = options.error;
|
|
this.id = options.id;
|
|
|
|
if (this.id === undefined) {
|
|
var bits = new Uint32Array(2);
|
|
window.crypto.getRandomValues(bits);
|
|
this.id = dcodeIO.Long.fromBits(bits[0], bits[1], true);
|
|
}
|
|
|
|
if (this.body === undefined) {
|
|
this.body = null;
|
|
}
|
|
};
|
|
|
|
var IncomingWebSocketRequest = function(options) {
|
|
var request = new Request(options);
|
|
var socket = options.socket;
|
|
|
|
this.verb = request.verb;
|
|
this.path = request.path;
|
|
this.body = request.body;
|
|
|
|
this.respond = function(status, message) {
|
|
socket.send(
|
|
new textsecure.protobuf.WebSocketMessage({
|
|
type: textsecure.protobuf.WebSocketMessage.Type.RESPONSE,
|
|
response: { id: request.id, message: message, status: status }
|
|
}).encode().toArrayBuffer()
|
|
);
|
|
};
|
|
};
|
|
|
|
var outgoing = {};
|
|
var OutgoingWebSocketRequest = function(options, socket) {
|
|
var request = new Request(options);
|
|
outgoing[request.id] = request;
|
|
socket.send(
|
|
new textsecure.protobuf.WebSocketMessage({
|
|
type: textsecure.protobuf.WebSocketMessage.Type.REQUEST,
|
|
request: {
|
|
verb : request.verb,
|
|
path : request.path,
|
|
body : request.body,
|
|
id : request.id
|
|
}
|
|
}).encode().toArrayBuffer()
|
|
);
|
|
};
|
|
|
|
window.WebSocketResource = function(socket, opts) {
|
|
opts = opts || {};
|
|
var handleRequest = opts.handleRequest;
|
|
if (typeof handleRequest !== 'function') {
|
|
handleRequest = function(request) {
|
|
request.respond(404, 'Not found');
|
|
};
|
|
}
|
|
this.sendRequest = function(options) {
|
|
return new OutgoingWebSocketRequest(options, socket);
|
|
};
|
|
|
|
socket.onmessage = function(socketMessage) {
|
|
var blob = socketMessage.data;
|
|
var reader = new FileReader();
|
|
reader.onload = function() {
|
|
var message = textsecure.protobuf.WebSocketMessage.decode(reader.result);
|
|
if (message.type === textsecure.protobuf.WebSocketMessage.Type.REQUEST ) {
|
|
handleRequest(
|
|
new IncomingWebSocketRequest({
|
|
verb : message.request.verb,
|
|
path : message.request.path,
|
|
body : message.request.body,
|
|
id : message.request.id,
|
|
socket : socket
|
|
})
|
|
);
|
|
}
|
|
else if (message.type === textsecure.protobuf.WebSocketMessage.Type.RESPONSE ) {
|
|
var response = message.response;
|
|
var request = outgoing[response.id];
|
|
if (request) {
|
|
request.response = response;
|
|
var callback = request.error;
|
|
if (response.status >= 200 && response.status < 300) {
|
|
callback = request.success;
|
|
}
|
|
|
|
if (typeof callback === 'function') {
|
|
callback(response.message, response.status, request);
|
|
}
|
|
} else {
|
|
throw 'Received response for unknown request ' + message.response.id;
|
|
}
|
|
}
|
|
};
|
|
reader.readAsArrayBuffer(blob);
|
|
};
|
|
|
|
if (opts.keepalive) {
|
|
var keepalive = new KeepAlive(this, {
|
|
path : opts.keepalive.path,
|
|
disconnect : opts.keepalive.disconnect
|
|
});
|
|
var resetKeepAliveTimer = keepalive.reset.bind(keepalive);
|
|
socket.addEventListener('connect', resetKeepAliveTimer);
|
|
socket.addEventListener('message', resetKeepAliveTimer);
|
|
socket.addEventListener('close', keepalive.stop.bind(keepalive));
|
|
}
|
|
|
|
this.close = function(code, reason) {
|
|
if (!code) { code = 3000; }
|
|
socket.close(code, reason);
|
|
};
|
|
|
|
};
|
|
|
|
function KeepAlive(websocketResource, opts) {
|
|
if (websocketResource instanceof WebSocketResource) {
|
|
opts = opts || {};
|
|
this.path = opts.path;
|
|
if (this.path === undefined) {
|
|
this.path = '/';
|
|
}
|
|
this.disconnect = opts.disconnect;
|
|
if (this.disconnect === undefined) {
|
|
this.disconnect = true;
|
|
}
|
|
this.wsr = websocketResource;
|
|
} else {
|
|
throw new TypeError('KeepAlive expected a WebSocketResource');
|
|
}
|
|
}
|
|
|
|
KeepAlive.prototype = {
|
|
constructor: KeepAlive,
|
|
stop: function() {
|
|
clearTimeout(this.keepAliveTimer);
|
|
clearTimeout(this.disconnectTimer);
|
|
},
|
|
reset: function() {
|
|
clearTimeout(this.keepAliveTimer);
|
|
clearTimeout(this.disconnectTimer);
|
|
this.keepAliveTimer = setTimeout(function() {
|
|
this.wsr.sendRequest({
|
|
verb: 'GET',
|
|
path: this.path,
|
|
success: this.reset.bind(this)
|
|
});
|
|
if (this.disconnect) {
|
|
// automatically disconnect if server doesn't ack
|
|
this.disconnectTimer = setTimeout(function() {
|
|
clearTimeout(this.keepAliveTimer);
|
|
this.wsr.close(3001, 'No response to keepalive request');
|
|
}.bind(this), 1000);
|
|
} else {
|
|
this.reset();
|
|
}
|
|
}.bind(this), 55000);
|
|
},
|
|
};
|
|
|
|
}());
|