UNPKG

domotz-remote-pawn

Version:

Domotz Agent

169 lines (147 loc) 4.71 kB
"use strict"; var dgram = require ("dgram"); var errors = require ("./errors"); var packets = require ("./packets"); var normalizeError = function (error){ if (!error){ return errors.EABORT; }else if (error instanceof Error){ return errors.wrap (error.message || errors.EABORT.message); }else{ return errors.wrap (error + ""); } }; var Request = module.exports = function (address, port, retries, timeout){ this._address = address; this._port = port; this._retries = retries; this._timeout = timeout; this._socket = null; this._closed = false; this._closing = false; this._aborted = false; this._error = null; //The string is modified from the ClientRequest subclass this._prefixError = ""; this._requestTimer = this._createRetransmitter (); }; Request.prototype.abort = function (error){ if (this._closed || this._closing || this._aborted) return; this._aborted = true; var me = this; this._send (packets.error.serialize (normalizeError (error)), function() { me._close () }); }; Request.prototype.close = function (){ if (this._closed || this._closing || this._aborted) return; this._close (); }; Request.prototype._close = function (error){ if (this._closed || this._closing || !this._socket) return; //If multiples closes occur inside the same tick (because abort() and _close() //are called in the same tick) the socket throws the error "Not running" //because the socket is already closed when the second close occurs, this is //why there's a closing flag this._closing = true; //Store the error after the flag is set to true, otherwise the error could be //misused by another close if (error) this._error = error; var me = this; //Close in the next tick to allow sending files in the same tick process.nextTick (function (){ me._socket.close (); }); }; Request.prototype._initSocket = function (socket, onMessage){ var me = this; this._onCloseFn = function (){ me._closed = true; me._requestTimer.reset (); if (me._aborted) return me._onAbort (); if (me._error){ me._onError (me._error); }else{ //Transfer ended successfully me._onClose (); } }; this._socket = (socket || dgram.createSocket ("udp" + this._ipFamily)) .on ("error", function (error){ me._closed = true; me._requestTimer.reset (); me._onError (error); }) .on ("close", this._onCloseFn) .on ("message", onMessage); }; Request.prototype._sendAck = function (block){ this._send (packets.ack.serialize (block)); }; Request.prototype._sendBlock = function (block, buffer){ this._send (packets.data.serialize (block, buffer)); }; Request.prototype._sendErrorAndClose = function (obj){ this._send (packets.error.serialize (obj)); this._closeWithError (obj); }; Request.prototype._closeWithError = function (obj){ var error = new Error (this._prefixError + obj.message); if (obj.name) error.code = obj.name; this._close (error); }; Request.prototype._sendAndRetransmit = function (buffer){ //Return if the transfer was aborted from inside the stats event (server) if (this._aborted) return; this._send (buffer); var me = this; this._requestTimer.start (function (){ me._send (buffer); }); }; Request.prototype._send = function (buffer, cb){ if (this._closed || this._closing) return; this._socket.send (buffer, 0, buffer.length, this._port, this._address, cb); }; Request.prototype._createRetransmitter = function (){ return new Retransmitter (this); }; var Retransmitter = function (request){ this._request = request; this._timer = null; this._pending = this._request._retries; }; Retransmitter.prototype.reset = function (){ if (!this._timer) return; clearTimeout (this._timer); this._pending = this._request._retries; this._timer = null; }; Retransmitter.prototype.start = function (fn){ var me = this; this._timer = setTimeout (function (){ if (!me._pending){ //No more retries me._request._close (new Error (errors.ETIME.message)); }else{ me._pending--; fn (); //Try again me.start (fn); } }, this._request._timeout); }; Request.Helper = function (rinfo, family){ this._rinfo = rinfo; this._socket = dgram.createSocket ("udp" + family); }; Request.Helper.prototype.abort = function (error){ this.sendErrorAndClose (normalizeError (error)); }; Request.Helper.prototype.sendErrorAndClose = function (obj){ var buffer = packets.error.serialize (obj); var me = this; this._socket.send (buffer, 0, buffer.length, this._rinfo.port, this._rinfo.address, function (){ me._socket.close (); }); };