You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			256 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			JavaScript
		
	
			
		
		
	
	
			256 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			JavaScript
		
	
| (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/signalapp/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 handleArrayBuffer = function(buffer) {
 | |
|         var message = textsecure.protobuf.WebSocketMessage.decode(buffer);
 | |
|         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;
 | |
|           }
 | |
|         }
 | |
|       };
 | |
| 
 | |
|       if (blob instanceof ArrayBuffer) {
 | |
|         handleArrayBuffer(blob);
 | |
|       } else {
 | |
|         var reader = new FileReader();
 | |
|         reader.onload = function() {
 | |
|           handleArrayBuffer(reader.result);
 | |
|         };
 | |
|         reader.readAsArrayBuffer(blob);
 | |
|       }
 | |
|     };
 | |
| 
 | |
|     if (opts.keepalive) {
 | |
|       this.keepalive = new KeepAlive(this, {
 | |
|         path: opts.keepalive.path,
 | |
|         disconnect: opts.keepalive.disconnect,
 | |
|       });
 | |
|       var resetKeepAliveTimer = this.keepalive.reset.bind(this.keepalive);
 | |
|       socket.addEventListener('open', resetKeepAliveTimer);
 | |
|       socket.addEventListener('message', resetKeepAliveTimer);
 | |
|       socket.addEventListener(
 | |
|         'close',
 | |
|         this.keepalive.stop.bind(this.keepalive)
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     socket.addEventListener(
 | |
|       'close',
 | |
|       function() {
 | |
|         this.closed = true;
 | |
|       }.bind(this)
 | |
|     );
 | |
| 
 | |
|     this.close = function(code, reason) {
 | |
|       if (this.closed) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       console.log('WebSocketResource.close()');
 | |
|       if (!code) {
 | |
|         code = 3000;
 | |
|       }
 | |
|       if (this.keepalive) {
 | |
|         this.keepalive.stop();
 | |
|       }
 | |
| 
 | |
|       socket.close(code, reason);
 | |
|       socket.onmessage = null;
 | |
| 
 | |
|       // On linux the socket can wait a long time to emit its close event if we've
 | |
|       //   lost the internet connection. On the order of minutes. This speeds that
 | |
|       //   process up.
 | |
|       setTimeout(
 | |
|         function() {
 | |
|           if (this.closed) {
 | |
|             return;
 | |
|           }
 | |
|           this.closed = true;
 | |
| 
 | |
|           console.log('Dispatching our own socket close event');
 | |
|           var ev = new Event('close');
 | |
|           ev.code = code;
 | |
|           ev.reason = reason;
 | |
|           this.dispatchEvent(ev);
 | |
|         }.bind(this),
 | |
|         1000
 | |
|       );
 | |
|     };
 | |
|   };
 | |
|   window.WebSocketResource.prototype = new textsecure.EventTarget();
 | |
| 
 | |
|   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() {
 | |
|           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();
 | |
|           }
 | |
|           console.log('Sending a keepalive message');
 | |
|           this.wsr.sendRequest({
 | |
|             verb: 'GET',
 | |
|             path: this.path,
 | |
|             success: this.reset.bind(this),
 | |
|           });
 | |
|         }.bind(this),
 | |
|         55000
 | |
|       );
 | |
|     },
 | |
|   };
 | |
| })();
 |