diff --git a/README.md b/README.md index 847b3bf..20695f6 100644 --- a/README.md +++ b/README.md @@ -49,3 +49,8 @@ address history $ curl http://localhost:8888/address/1dice3jkpTvevsohA4Np1yP4uKzG1SRLv { "history" : [{"spend_hash": "cdf6ea4f4590fbc847855cf68af181f1398b8997081cf0cfbd14e0f2cf2808ea", "output_height": 228180, "spend_index": 0, "value": 1000000, .... ``` + +TODO +-------------- +* Add to wiki method documentation, and some link to learn about concepts +* Create a image-schema of Darkwallet ecosystem parts and how are connected \ No newline at end of file diff --git a/client/gateway.js b/client/gateway.js index 0cc1958..3eb731a 100644 --- a/client/gateway.js +++ b/client/gateway.js @@ -1,389 +1,430 @@ /** * Client to connect to a darkwallet gateway. - * - * @param {String} connect_uri Gateway websocket URI - * @param {Function} handle_connect Callback to run when connected - * @param {Function} handle_disconnect Callback to run when disconnected - * @param {Function} handle_error Callback to run on errors (except connection errors go to handle_connect first parameter). - * - * handle_* callbacks take parameters as (error, data) + * TODO camelcase methods */ -function GatewayClient(connect_uri, handle_connect, handle_disconnect, handle_error) { - var self = this; - this.handler_map = {}; - this.connected = false; - this.websocket = new WebSocket(connect_uri); - var closingCb; - this.close = function(_cb) { - self.connected = false; - self.handler_map = {}; - closingCb = _cb; - self.websocket.close(); - }; - this.websocket.onopen = function(evt) { - self.connected = true; - handle_connect(); - }; - this.websocket.onclose = function(evt) { - self.connected = false; - self.on_close(evt); - if (handle_disconnect) { - handle_disconnect(null, evt) - } - if (closingCb) { - closingCb(); - closingCb = false; - } - }; - this.websocket.onerror = function(evt) { - // TODO: should probably disconnect - if (!self.connected) { - handle_connect(evt); - } else if (handle_error) { - handle_error(evt); - } - }; - this.websocket.onmessage = function(evt) { - self._on_message(evt); - }; -} - -/** - * Get last height - * - * @param {Function} handle_fetch Callback to handle the returned height - */ -GatewayClient.prototype.fetch_last_height = function(handle_fetch) { - GatewayClient._check_function(handle_fetch); - - this.make_request("fetch_last_height", [], function(response) { - handle_fetch(response["error"], response["result"][0]); - }); -}; - -/** - * Fetch transaction - * - * @param {String} tx_hash Transaction identifier hash - * @param {Function} handle_fetch Callback to handle the JSON object - * representing the transaction - */ -GatewayClient.prototype.fetch_transaction = function(tx_hash, handle_fetch) { - GatewayClient._check_function(handle_fetch); - - this.make_request("fetch_transaction", [tx_hash], function(response) { - handle_fetch(response["error"], response["result"][0]); - }); -}; - -/** - * Fetch history - * - * @param {String} address - * @param {Function} handle_fetch Callback to handle the JSON object - * representing the history of the address - */ -GatewayClient.prototype.fetch_history = function(address, height, handle_fetch) { - height = height || 0; - GatewayClient._check_function(handle_fetch); - - this.make_request("fetch_history", [address, height], function(response) { - handle_fetch(response["error"], response["result"][0]); - }); -}; - -GatewayClient.prototype.fetch_stealth = function( - prefix, handle_fetch, from_height) -{ - GatewayClient._check_function(handle_fetch); +(function(){ + "use strict"; + + /** + * Client to connect to a darkwallet gateway. + * + * @param {String} connect_uri Gateway websocket URI + * @param {Function} handle_connect Callback to run when connected + * @param {Function} [handle_disconnect] Callback to run when disconnected + * @param {Function} [handle_error] Callback to run on errors (except connection errors go to handle_connect first parameter). + * + * handle_* callbacks take parameters as (error, data) + */ + function GatewayClient(connect_uri, handle_connect, handle_disconnect, handle_error) { + var self = this; + this.handler_map = {}; + this.connected = false; + this.websocket = new WebSocket(connect_uri); + var closingCb; + this.close = function(_cb) { + self.connected = false; + self.handler_map = {}; + closingCb = _cb; + self.websocket.close(); + }; + this.websocket.onopen = function() { + self.connected = true; + handle_connect(); + }; + this.websocket.onclose = function(evt) { + self.connected = false; + self.on_close(evt); + if (handle_disconnect) { + handle_disconnect(null, evt) + } + if (closingCb) { + closingCb(); + closingCb = false; + } + }; + this.websocket.onerror = function(evt) { + self.on_error(evt); + // TODO: should probably disconnect + if (!self.connected) { + handle_connect(evt); + } else if (handle_error) { + handle_error(evt); + } + }; + this.websocket.onmessage = onMessage.bind(this); + } - this.make_request("fetch_stealth", [prefix, from_height], - function(response) { - handle_fetch(response["error"], response["result"][0]); + /** + * Make requests to the server + * + * @param {String} command + * @param {Array} params + * @param {Function} handler + */ + function makeRequest(command, params, handler) { + checkFunction(handler); + var id = randomInteger(); + var message = JSON.stringify({ + id: id, + command: command, + params: params }); -}; - -/** - * Subscribe - * - * @param {String} address - * @param {Function} handle_fetch Callback to handle subscription result - * @param {Function} handle_update Callback to handle the JSON object - * representing for updates - */ -GatewayClient.prototype.subscribe = function( - address, handle_fetch, handle_update) -{ - var self = this; - this.make_request("subscribe_address", [address], function(response) { - handle_fetch(response["error"], response["result"][0]); - if (handle_update) { - self.handler_map["update." + address] = handle_update; - } - }); -} - -/** - * Unsubscribe - * - * @param {String} address - * @param {Function} handle_fetch Callback to handle subscription result - * representing for updates - */ - -GatewayClient.prototype.unsubscribe = function(address, handle_fetch) -{ - if (this.handler_map["update." + address]) { - delete this.handler_map["update." + address]; + this.websocket.send(message); + this.handler_map[id] = handler; } - this.make_request("unsubscribe_address", [address], function(response) { - handle_fetch(response["error"], response["result"][0]); - }); -} - -GatewayClient.prototype.fetch_block_header = function(index, handle_fetch) { - GatewayClient._check_function(handle_fetch); + /** + * (Pseudo-)Random integer generator + * + * @return {Number} Random integer + */ + function randomInteger() { + return Math.floor((Math.random() * 4294967296)); + } - this.make_request("fetch_block_header", [index], function(response) { - handle_fetch(response["error"], response["result"][0]); - }); -}; + /** + * Checks if param can be executed + * + * @param {Function} func to be checked + * + * @throws {String} Parameter is not a function + */ + function checkFunction(func) { + if(typeof func !== 'function') throw "Parameter is not a function"; + } -GatewayClient.prototype.fetch_block_transaction_hashes = function( - index, handle_fetch) -{ - GatewayClient._check_function(handle_fetch); + /** + * After triggering message event, calls to the handler of the petition + * + * @param {Object} evt event + */ + function onMessage(evt) { + var response = JSON.parse(evt.data); + var id = response.id; + // Should be a separate map entirely. This is a hack. + if (response.type == "update") + id = "update." + response.address; + if (response.type == "chan_update") + id = "chan.update." + response.thread; + // Prefer flat code over nested. + var handler = this.handler_map[id]; + if (handler) { + handler(response); + }else{ + console.log("Handler not found", id); + } + } - this.make_request("fetch_block_transaction_hashes", [index], - function(response) { - handle_fetch(response["error"], response["result"][0]); + /** + * METHODS + */ + + /** + * Get last height + * + * @param {Function} handle_fetch Callback to handle the returned height + */ + GatewayClient.prototype.fetch_last_height = function(handle_fetch) { + checkFunction(handle_fetch); + makeRequest.call(this, "fetch_last_height", [], function(response) { + handle_fetch(response.error, response.result[0]); }); -}; - -GatewayClient.prototype.fetch_spend = function(outpoint, handle_fetch) { - GatewayClient._check_function(handle_fetch); - - this.make_request("fetch_spend", [outpoint], function(response) { - handle_fetch(response["error"], response["result"][0]); - }); -}; - -GatewayClient.prototype.fetch_transaction_index = function( - tx_hash, handle_fetch) -{ - GatewayClient._check_function(handle_fetch); + }; - this.make_request("fetch_transaction_index", [tx_hash], - function(response) { - result = response["result"]; - handle_fetch(response["error"], result[0], result[1]); + /** + * Fetch transaction + * + * @param {String} tx_hash Transaction identifier hash + * @param {Function} handle_fetch Callback to handle the JSON object representing the transaction + */ + GatewayClient.prototype.fetch_transaction = function(tx_hash, handle_fetch) { + checkFunction(handle_fetch); + makeRequest.call(this, 'fetch_transaction', [tx_hash], function(response) { + handle_fetch(response.error, response.result[0]); }); -}; - -GatewayClient.prototype.fetch_block_height = function(blk_hash, handle_fetch) -{ - GatewayClient._check_function(handle_fetch); + }; - this.make_request("fetch_block_height", [blk_hash], function(response) { - handle_fetch(response["error"], response["result"][0]); - }); -}; + /** + * Fetch history + * + * @param {String} address + * @param {Number} height + * @param {Function} handle_fetch Callback to handle the JSON object representing the history of the address + */ + GatewayClient.prototype.fetch_history = function(address, height, handle_fetch) { + height = height || 0; + checkFunction(handle_fetch); + makeRequest.call(this, 'fetch_history', [address, height], function(response) { + handle_fetch(response.error, response.result[0]); + }); + }; -GatewayClient.prototype.broadcast_transaction = function( - raw_tx, handle_fetch) -{ - GatewayClient._check_function(handle_fetch); + /** + * Fetch stealth + * TODO: move params order + * + * @param {Array} prefix + * @param {Function} handle_fetch + * @param {Number} from_height + */ + GatewayClient.prototype.fetch_stealth = function(prefix, handle_fetch, from_height){ + checkFunction(handle_fetch); + makeRequest.call(this, 'fetch_stealth', [prefix, from_height], function(response) { + handle_fetch(response.error, response.result[0]); + }); + }; - this.make_request("broadcast_transaction", [raw_tx], function(response) { - handle_fetch(response["error"], response["result"][0]); - }); -}; + /** + * Subscribe + * + * @param {String} address + * @param {Function} handle_fetch Callback to handle subscription result + * @param {Function} handle_update Callback to handle the JSON object representing for updates + */ + GatewayClient.prototype.subscribe = function(address, handle_fetch, handle_update){ + checkFunction(handle_fetch); + var handle_map = this.handler_map; + makeRequest.call(this, 'subscribe_address', [address], function(response) { + handle_fetch(response.error, response.result[0]); + if (handle_update ) { + checkFunction(handle_update); + handle_map['update.' + address] = handle_update; + } + }); + }; -/** - * Renew - * - * @param {String} address - * @param {Function} handle_fetch Callback to handle subscription result - * @param {Function} handle_update Callback to handle the JSON object - * representing for updates - */ -GatewayClient.prototype.renew = function(address, handle_fetch, handle_update) -{ - var self = this; - this.make_request("renew_address", [address], function(response) { - handle_fetch(response["error"], response["result"][0]); - if (handle_update) { - self.handler_map["update."+address] = handle_update; + /** + * Unsubscribe + * + * @param {String} address + * @param {Function} handle_fetch Callback to handle subscription result representing for updates + */ + GatewayClient.prototype.unsubscribe = function(address, handle_fetch){ + checkFunction(handle_fetch); + if (this.handler_map['update.' + address]) { + delete this.handler_map['update.' + address]; } - }); -} - -/** - * Chan functionality - */ -GatewayClient.prototype.chan_post = function(section_name, thread_id, data, handle_fetch) { - GatewayClient._check_function(handle_fetch); - - this.make_request("chan_post", [section_name, thread_id, data], function(response) { - if (handle_fetch) - handle_fetch(response["error"], response["result"]); - }); -}; - -GatewayClient.prototype.chan_list = function(section_name, handle_fetch) { - GatewayClient._check_function(handle_fetch); - - this.make_request("chan_list", [section_name], function(response) { - if (handle_fetch) - handle_fetch(response["error"], response["result"]); - }); -}; + makeRequest.call(this, 'unsubscribe_address', [address], function(response) { + handle_fetch(response.error, response.result[0]); + }); + }; + /** + * Fetch block header + * + * @param {Number} index + * @param {Function} handle_fetch + */ + GatewayClient.prototype.fetch_block_header = function(index, handle_fetch) { + checkFunction(handle_fetch); + makeRequest.call(this, 'fetch_block_header', [index], function(response) { + handle_fetch(response.error, response.result[0]); + }); + }; -GatewayClient.prototype.chan_get = function(section_name, thread_id, handle_fetch) { - GatewayClient._check_function(handle_fetch); + /** + * Fetch block transaction hashes + * + * @param {Number} index + * @param {Function} handle_fetch + */ + GatewayClient.prototype.fetch_block_transaction_hashes = function(index, handle_fetch) { + checkFunction(handle_fetch); + makeRequest.call(this, "fetch_block_transaction_hashes", [index], function(response) { + handle_fetch(response.error, response.result[0]); + }); + }; - this.make_request("chan_get", [section_name, thread_id], function(response) { - if (handle_fetch) - handle_fetch(response["error"], response["result"]); - }); -}; + /** + * Fetch spend + * + * @param {Array} outpoint + * @param {Function} handle_fetch + */ + GatewayClient.prototype.fetch_spend = function(outpoint, handle_fetch) { + checkFunction(handle_fetch); + makeRequest.call(this, 'fetch_spend', [outpoint], function(response) { + handle_fetch(response.error, response.result[0]); + }); + }; -GatewayClient.prototype.chan_subscribe = function(section_name, thread_id, handle_fetch, handle_update) { - GatewayClient._check_function(handle_fetch); - var self = this; - this.make_request("chan_subscribe", [section_name, thread_id], function(response) { - if (handle_fetch) - handle_fetch(response["error"], response["result"]); - if (handle_update) { - self.handler_map["chan.update." + thread_id] = handle_update; - } + /** + * Fetch transaction index + * + * @param {String} tx_hash + * @param {Function} handle_fetch + */ + GatewayClient.prototype.fetch_transaction_index = function(tx_hash, handle_fetch){ + checkFunction(handle_fetch); + makeRequest.call(this, 'fetch_transaction_index', [tx_hash], function(response) { + var result = response.result; + handle_fetch(response["error"], result[0], result[1]); + }); + }; - }); -}; -GatewayClient.prototype.chan_unsubscribe = function(section_name, thread_id, handle_fetch, handle_update) { - GatewayClient._check_function(handle_fetch); - var self = this; - this.make_request("chan_unsubscribe", [section_name, thread_id], function(response) { - if (handle_fetch) - handle_fetch(response["error"], response["result"]); - if (self.handler_map["chan.update." + thread_id]) { - delete self.handler_map["chan.update." + thread_id] - } - }); -}; + /** + * Fetch block height + * + * @param {String} blk_hash + * @param {Function} handle_fetch + */ + GatewayClient.prototype.fetch_block_height = function(blk_hash, handle_fetch){ + checkFunction(handle_fetch); + makeRequest.call(this, 'fetch_block_height', [blk_hash], function(response) { + handle_fetch(response.error, response.result[0]); + }); + }; + /** + * Broadcast transaction + * + * @param {String} raw_tx + * @param {Function} handle_fetch + */ + GatewayClient.prototype.broadcast_transaction = function(raw_tx, handle_fetch){ + checkFunction(handle_fetch); + makeRequest.call(this, 'broadcast_transaction', [raw_tx], function(response) { + handle_fetch(response.error, response.result[0]); + }); + }; -/** - * Ticker functionality - * - * @param {String} currency, like USD, EUR... - * @param {Function} handle_fetch - */ + /** + * Renew + * + * @param {String} address + * @param {Function} handle_fetch Callback to handle subscription result + * @param {Function} handle_update Callback to handle the JSON object representing for updates + */ + GatewayClient.prototype.renew = function(address, handle_fetch, handle_update) { + var handler_map = this.handler_map; + makeRequest.call(this, 'renew_address', [address], function(response) { + handle_fetch(response.error, response.result[0]); + if (handle_update) { + checkFunction(handle_update); + handler_map['update.' + address] = handle_update; + } + }); + }; -GatewayClient.prototype.fetch_ticker = function(currency, handle_fetch) -{ - GatewayClient._check_function(handle_fetch); + /** + * Chan post + * + * @param {String} section_name + * @param {String} thread_id + * @param {String} data + * @param {Function} handle_fetch + */ + GatewayClient.prototype.chan_post = function(section_name, thread_id, data, handle_fetch) { + checkFunction(handle_fetch); + makeRequest.call(this, 'chan_post', [section_name, thread_id, data], function(response) { + handle_fetch(response.error, response.result); + }); + }; - this.make_request("fetch_ticker", [currency], function(response) { - handle_fetch(response["error"], response["result"][0]); - }); -}; + /** + * Chan list + * + * @param {String} section_name + * @param {Function} handle_fetch + */ + GatewayClient.prototype.chan_list = function(section_name, handle_fetch) { + checkFunction(handle_fetch); + makeRequest.call(this, 'chan_list', [section_name], function(response) { + handle_fetch(response.error, response.result); + }); + }; -/** - * Make requests to the server - * - * @param {String} command - * @param {Array} params - * @param {Function} handler - */ -GatewayClient.prototype.make_request = function(command, params, handler) { - GatewayClient._check_function(handler); + /** + * Chan get + * + * @param {String} section_name + * @param {String} thread_id + * @param {Function} handle_fetch + */ + GatewayClient.prototype.chan_get = function(section_name, thread_id, handle_fetch) { + checkFunction(handle_fetch); + makeRequest.call(this, 'chan_get', [section_name, thread_id], function(response) { + handle_fetch(response.error, response.result); + }); + }; - var id = GatewayClient._random_integer(); - var request = { - "id": id, - "command": command, - "params": params + /** + * Chan subscribe + * + * @param {String} section_name + * @param {String} thread_id + * @param {Function} handle_fetch + * @param {Function} handle_update + */ + GatewayClient.prototype.chan_subscribe = function(section_name, thread_id, handle_fetch, handle_update) { + checkFunction(handle_fetch); + var handler_map = this.handler_map; + makeRequest.call(this, 'chan_subscribe', [section_name, thread_id], function(response) { + handle_fetch(response.error, response.result); + if (handle_update) { + checkFunction(handle_update); + handler_map["chan.update." + thread_id] = handle_update; + } + }); }; - var message = JSON.stringify(request); - this.websocket.send(message); - this.handler_map[id] = handler; -}; -/** - * Close event handler - * - * @param {Object} evt event - */ -GatewayClient.prototype.on_close = function(evt) { -}; + /** + * Chan subscribe + * + * @param {String} section_name + * @param {String} thread_id + * @param {Function} handle_fetch + */ + GatewayClient.prototype.chan_unsubscribe = function(section_name, thread_id, handle_fetch) { + checkFunction(handle_fetch); + var handler_map = this.handler_map; + makeRequest.call(this, 'chan_unsubscribe', [section_name, thread_id], function(response) { + handle_fetch(response.error, response.result); + if (handler_map['chan.update.' + thread_id]) { + delete handler_map['chan.update.' + thread_id]; + } + }); + }; -/** - * Error event handler - * - * @param {Object} evt event - * - * @throws {Object} - */ -GatewayClient.prototype.on_error = function(evt) { - throw evt; -}; + /** + * Ticker functionality + * + * @param {String} currency Like USD, EUR... + * @param {Function} handle_fetch + */ + GatewayClient.prototype.fetch_ticker = function(currency, handle_fetch){ + checkFunction(handle_fetch); + makeRequest.call(this, 'fetch_ticker', [currency], function(response) { + handle_fetch(response.error, response.result[0]); + }); + }; -/** - * After triggering message event, calls to the handler of the petition - * - * @param {Object} evt event - * @private - */ -GatewayClient.prototype._on_message = function(evt) { - this.on_message(evt) - response = JSON.parse(evt.data); - id = response.id; - // Should be a separate map entirely. This is a hack. - if (response.type == "update") - id = "update." + response.address; - if (response.type == "chan_update") - id = "chan.update." + response.thread; - // Prefer flat code over nested. - if (!this.handler_map[id]) { - console.log("Handler not found", id); - return; - } - handler = this.handler_map[id]; - handler(response); -}; + /** + * Error event handler + * + * @param {Object} evt event + * + * @throws {Object} + */ + GatewayClient.prototype.on_error = function(evt){ + throw evt; + }; -/** - * Message event handler - * - * @param {Object} evt event - */ -GatewayClient.prototype.on_message = function(evt) { -} + /** + * Close event handler + * + * @param {Object} evt event + */ + GatewayClient.prototype.on_close = function(evt){}; -/** - * (Pseudo-)Random integer generator - * - * @return {Number} Random integer - * @private - */ -GatewayClient._random_integer = function() { - return Math.floor((Math.random() * 4294967296)); -}; + // Expose at window + window.GatewayClient = GatewayClient; -/** - * Checks if param can be executed - * - * @param {Function} Function to be checked - * - * @throws {String} Parameter is not a function - * @protected - */ -GatewayClient._check_function = function(func) { - if (typeof func !== 'function') { - throw "Parameter is not a function"; + // Expose as AMD module + if (typeof window.define === "function" && window.define.amd) { + window.define("GatewayClient", [], function() { + return window.GatewayClient; + }); } -}; +})(); \ No newline at end of file diff --git a/client/test.html b/client/test.html index 50e854a..70c7057 100644 --- a/client/test.html +++ b/client/test.html @@ -6,181 +6,278 @@ + + .block h3{ + margin-bottom: 15px; + padding-bottom: 5px; + font-size: 16px; + font-weight: bold; + border-bottom: 1px dashed #939259; + } + .error{ + color: red; + } +