add browser example.

This commit is contained in:
Christopher Jeffrey 2016-06-05 20:10:44 -07:00
parent a9dce51b3a
commit dc436d10e2
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
5 changed files with 778 additions and 0 deletions

282
browser/index.html Normal file
View File

@ -0,0 +1,282 @@
<!doctype html>
<html>
<head>
<title>bcoin</title>
<style>
html { height: 100%; }
body { height: 90%; padding: 20px; }
h1 {
font: 3em monospace;
margin: 10px;
padding: 0;
}
small {
margin: 10px;
width: 50%;
display: block;
}
.log {
padding: 5px;
margin-left: 10px;
overflow-y: scroll;
border: 1px solid black;
white-space: pre-wrap;
height: 40%;
width: 40%;
font: 1em monospace;
margin-top: 10px;
}
.wallet {
padding: 5px;
margin-left: 5px;
margin-top: 10px;
font: 1em monospace;
}
.send {
padding: 5px;
margin-left: 5px;
margin-top: 10px;
font: 1em monospace;
}
#newaddr{
display: block;
margin-left: 10px;
}
.tx {
float: right;
font: 1em monospace;
padding: 5px;
border: 1px solid black;
margin-top: 10px;
}
a {
text-decoration: none;
}
.floating {
font: 1em monospace;
white-space: pre-wrap;
position: absolute;
display: none;
padding: 5px;
background: white;
border: 1px solid black;
width: 40%;
height: 30%;
top: 50%;
left: 50%;
margin-left: -20%;
margin-top: -15%;
overflow-y: scroll;
}
</style>
<script src="/bcoin.js"></script>
</head>
<body>
<h1>BCoin, the browser full node</h1>
<small>Welcome. Your machine is currently validating the segnet4 blockchain.
The blocks and wallet are stored on your local disk with indexed DB. You are
connecting to the actual bitcoin P2P network via a websocket-&gt;tcp proxy.
Enjoy. (See the <a href="https://github.com/bcoin-org/bcoin">bcoin repo</a> for
more bitcoin magic).</small>
<div class="tx">
Last 20 Blocks/TXs:
<div id="tx"></div>
</div>
<div id="log" class="log"></div>
<div id="wallet" class="wallet"></div>
<form id="send" class="send" action="#">
<input type="text" name="address" id="address" placeholder="Address">
<input type="text" name="amount" id="amount" placeholder="Amount (BTC)">
<input type="submit" value="Send">
</form>
<input type="button" id="newaddr" value="New Address">
<div id="floating" class="floating"></div>
<script>
;(function() {
var utils = bcoin.utils;
var body = document.getElementsByTagName('body')[0];
var log = document.getElementById('log');
var wdiv = document.getElementById('wallet');
var tdiv = document.getElementById('tx');
var floating = document.getElementById('floating');
var send = document.getElementById('send');
var newaddr = document.getElementById('newaddr');
var items = [];
var scrollback = 0;
var logger, node;
body.onmouseup = function() {
floating.style.display = 'none';
};
floating.onmouseup = function(ev) {
ev.stopPropagation();
return false;
};
function show(obj) {
floating.innerHTML = escape(utils.inspectify(obj, false));
floating.style.display = 'block';
}
logger = {
debug: function(args) {
if (++scrollback > 1000) {
log.innerHTML = '';
scrollback = 1;
}
var msg = utils.format(args, false);
log.innerHTML += '<span style="color:blue;">' + utils.now() + '</span> ';
log.innerHTML += escape(msg) + '\n';
log.scrollTop = log.scrollHeight;
},
error: function(err) {
if (++scrollback > 1000) {
log.innerHTML = '';
scrollback = 1;
}
var msg = (err.message + '').replace(/^ *Error: */, '');
log.innerHTML += '<span style="color:blue;">' + utils.now() + '</span> ';
log.innerHTML += '<span style="color:red;">[Error]</span> ';
log.innerHTML += escape(msg) + '\n';
log.scrollTop = log.scrollHeight;
}
};
send.onsubmit = function(ev) {
var value = document.getElementById('amount').value;
var address = document.getElementById('address').value;
value = utils.satoshi(value);
node.wallet.createTX({}, [{address: address,value:value}], function(err, tx) {
if (err)
return bcoin.error(err);
node.wallet.sign(tx, function(err) {
if (err)
return bcoin.error(err);
node.sendTX(tx, function(err) {
if (err)
return bcoin.error(err);
show(tx);
});
});
});
ev.preventDefault();
ev.stopPropagation();
return false;
};
newaddr.onmouseup = function() {
node.wallet.createReceive(function(err) {
if (err)
throw err;
formatWallet(node.wallet);
});
};
function kb(size) {
size /= 1000;
return size.toFixed(2) + 'kb';
}
function create(html) {
var el = document.createElement('div');
el.innerHTML = html;
return el.firstChild;
}
function escape(html, encode) {
return html
.replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function addItem(tx) {
var el;
if (items.length === 20) {
el = items.shift();
tdiv.removeChild(el);
}
el = create('<a style="display:block;" href="#'
+ tx.rhash + '">' + tx.rhash + ' (' + tx.height
+ ' - ' + kb(tx.getSize()) + ')</a>');
tdiv.appendChild(el);
el.onmouseup = function(ev) {
show(tx);
ev.stopPropagation();
return false;
};
items.push(el);
}
function formatWallet(wallet) {
var html = '';
var key = wallet.master.toJSON();
html += '<b>Wallet</b><br>';
html += 'Current Address (p2wpkh): <b>' + wallet.getAddress() + '</b><br>';
html += 'Current Address (p2wpkh behind p2sh): <b>' + wallet.getProgramAddress() + '</b><br>';
html += 'Extended Private Key: <b>' + key.xprivkey + '</b><br>';
html += 'Mnemonic: <b>' + key.phrase + '</b><br>';
wallet.getBalance(function(err, balance) {
if (balance) {
html += 'Confirmed Balance: <b>' + utils.btc(balance.confirmed) + '</b><br>';
html += 'Unconfirmed Balance: <b>' + utils.btc(balance.unconfirmed) + '</b><br>';
html += 'Balance: <b>' + utils.btc(balance.total) + '</b><br>';
}
wallet.getHistory(function(err, txs) {
html += 'TXs:\n';
wdiv.innerHTML = html;
if (txs) {
txs.forEach(function(tx) {
var el = create('<a style="display:block;" href="#' + tx.rhash + '">' + tx.rhash + '</a>');
wdiv.appendChild(el);
el.onclick = function(ev) {
show(tx);
ev.stopPropagation();
return false;
};
});
}
});
});
}
bcoin.set({
network: 'segnet4',
debug: true,
// db: 'memory',
logger: logger,
useWorkers: true
});
node = new bcoin.fullnode();
node.on('error', function(err) {
bcoin.error(err);
});
node.on('block', addItem);
node.mempool.on('tx', addItem);
node.open(function(err) {
if (err)
throw err;
node.startSync();
formatWallet(node.wallet);
node.wallet.on('update', function() {
formatWallet(node.wallet);
});
});
})();
</script>
</body>
</html>

136
browser/proxysocket.js Normal file
View File

@ -0,0 +1,136 @@
var bcoin = require('../lib/bcoin/env');
var utils = require('../lib/bcoin/utils');
var BufferWriter = require('../lib/bcoin/writer');
var assert = utils.assert;
var EventEmitter = require('events').EventEmitter;
var IOClient = require('socket.io-client');
function ProxySocket(uri) {
var self = this;
if (!(this instanceof ProxySocket))
return new ProxySocket(uri);
EventEmitter.call(this);
this.info = null;
this.socket = new IOClient(uri, { reconnection: false });
this.sendBuffer = [];
this.snonce = null;
this.closed = false;
this.socket.on('info', function(info) {
if (self.closed)
return;
self.info = info;
if (info.pow) {
self.snonce = new Buffer(info.snonce, 'hex');
self.target = new Buffer(info.target, 'hex');
}
self.emit('info', info);
});
this.socket.on('error', function(err) {
console.error(err);
});
this.socket.on('tcp connect', function() {
if (self.closed)
return;
self.emit('connect');
});
this.socket.on('tcp data', function(data) {
self.emit('data', new Buffer(data, 'hex'));
});
this.socket.on('tcp close', function(data) {
if (self.closed)
return;
self.closed = true;
self.emit('close');
});
this.socket.on('tcp error', function(err) {
self.emit('error', new Error(err));
});
this.socket.on('close', function() {
if (self.closed)
return;
self.closed = true;
self.emit('close');
});
}
utils.inherits(ProxySocket, EventEmitter);
ProxySocket.prototype.connect = function connect(port, host) {
var nonce = 0;
var i, pow;
if (this.closed) {
this.sendBuffer.length = 0;
return;
}
if (!this.info)
return this.once('info', connect.bind(this, port, host));
if (this.info.pow) {
bcoin.debug(
'Solving proof of work to create socket (%d, %s) -- please wait.',
port, host);
pow = new BufferWriter();
pow.writeU32(nonce);
pow.writeBytes(this.snonce);
pow.writeU32(port);
pow.writeString(host, 'ascii');
pow = pow.render();
do {
nonce++;
assert(nonce <= 0xffffffff, 'Could not create socket.');
pow.writeUInt32LE(nonce, 0, true);
} while (utils.cmp(utils.dsha256(pow), this.target) >= 0);
bcoin.debug('Solved proof of work: %d', nonce);
}
this.socket.emit('tcp connect', port, host, nonce);
for (i = 0; i < this.sendBuffer.length; i++)
this.write(this.sendBuffer[i]);
this.sendBuffer.length = 0;
};
ProxySocket.prototype.write = function write(data) {
if (!this.info) {
this.sendBuffer.push(data);
return true;
}
this.socket.emit('tcp data', data.toString('hex'));
return true;
};
ProxySocket.prototype.destroy = function destroy() {
if (this.closed)
return;
this.closed = true;
this.socket.disconnect();
};
ProxySocket.connect = function connect(uri, port, host) {
var socket = new ProxySocket(uri);
socket.connect(port, host);
return socket;
};
module.exports = ProxySocket;

31
browser/server.js Normal file
View File

@ -0,0 +1,31 @@
var HTTPBase = require('../lib/bcoin/http/base');
var fs = require('fs');
var server = new HTTPBase();
var proxy = require('./wsproxy')({
pow: process.argv.indexOf('--pow') !== -1
});
var index = fs.readFileSync(__dirname + '/index.html');
var bcoin = fs.readFileSync(__dirname + '/bcoin.js');
var worker = fs.readFileSync(__dirname + '/../lib/bcoin/worker.js');
server.get('/favicon.ico', function(req, res, next, send) {
send(404, '', 'text');
});
server.get('/', function(req, res, next, send) {
send(200, index, 'html');
});
server.get('/bcoin.js', function(req, res, next, send) {
send(200, bcoin, 'js');
});
server.get('/bcoin-worker.js', function(req, res, next, send) {
send(200, worker, 'js');
});
proxy.attach(server.server);
server.listen(8080);

197
browser/setimmediate.js Normal file
View File

@ -0,0 +1,197 @@
/*!
* Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, and Domenic Denicola
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
(function (global, undefined) {
"use strict";
if (global.setImmediate) {
return;
}
var nextHandle = 1; // Spec says greater than zero
var tasksByHandle = {};
var currentlyRunningATask = false;
var doc = global.document;
var setImmediate;
function addFromSetImmediateArguments(args) {
tasksByHandle[nextHandle] = partiallyApplied.apply(undefined, args);
return nextHandle++;
}
// This function accepts the same arguments as setImmediate, but
// returns a function that requires no arguments.
function partiallyApplied(handler) {
var args = [].slice.call(arguments, 1);
return function() {
if (typeof handler === "function") {
handler.apply(undefined, args);
} else {
(new Function("" + handler))();
}
};
}
function runIfPresent(handle) {
// From the spec: "Wait until any invocations of this algorithm started before this one have completed."
// So if we're currently running a task, we'll need to delay this invocation.
if (currentlyRunningATask) {
// Delay by doing a setTimeout. setImmediate was tried instead, but in Firefox 7 it generated a
// "too much recursion" error.
setTimeout(partiallyApplied(runIfPresent, handle), 0);
} else {
var task = tasksByHandle[handle];
if (task) {
currentlyRunningATask = true;
try {
task();
} finally {
clearImmediate(handle);
currentlyRunningATask = false;
}
}
}
}
function clearImmediate(handle) {
delete tasksByHandle[handle];
}
function installNextTickImplementation() {
setImmediate = function() {
var handle = addFromSetImmediateArguments(arguments);
process.nextTick(partiallyApplied(runIfPresent, handle));
return handle;
};
}
function canUsePostMessage() {
// The test against `importScripts` prevents this implementation from being installed inside a web worker,
// where `global.postMessage` means something completely different and can't be used for this purpose.
if (global.postMessage && !global.importScripts) {
var postMessageIsAsynchronous = true;
var oldOnMessage = global.onmessage;
global.onmessage = function() {
postMessageIsAsynchronous = false;
};
global.postMessage("", "*");
global.onmessage = oldOnMessage;
return postMessageIsAsynchronous;
}
}
function installPostMessageImplementation() {
// Installs an event handler on `global` for the `message` event: see
// * https://developer.mozilla.org/en/DOM/window.postMessage
// * http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages
var messagePrefix = "setImmediate$" + Math.random() + "$";
var onGlobalMessage = function(event) {
if (event.source === global &&
typeof event.data === "string" &&
event.data.indexOf(messagePrefix) === 0) {
runIfPresent(+event.data.slice(messagePrefix.length));
}
};
if (global.addEventListener) {
global.addEventListener("message", onGlobalMessage, false);
} else {
global.attachEvent("onmessage", onGlobalMessage);
}
setImmediate = function() {
var handle = addFromSetImmediateArguments(arguments);
global.postMessage(messagePrefix + handle, "*");
return handle;
};
}
function installMessageChannelImplementation() {
var channel = new MessageChannel();
channel.port1.onmessage = function(event) {
var handle = event.data;
runIfPresent(handle);
};
setImmediate = function() {
var handle = addFromSetImmediateArguments(arguments);
channel.port2.postMessage(handle);
return handle;
};
}
function installReadyStateChangeImplementation() {
var html = doc.documentElement;
setImmediate = function() {
var handle = addFromSetImmediateArguments(arguments);
// Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
// into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
var script = doc.createElement("script");
script.onreadystatechange = function () {
runIfPresent(handle);
script.onreadystatechange = null;
html.removeChild(script);
script = null;
};
html.appendChild(script);
return handle;
};
}
function installSetTimeoutImplementation() {
setImmediate = function() {
var handle = addFromSetImmediateArguments(arguments);
setTimeout(partiallyApplied(runIfPresent, handle), 0);
return handle;
};
}
// If supported, we should attach to the prototype of global, since that is where setTimeout et al. live.
var attachTo = Object.getPrototypeOf && Object.getPrototypeOf(global);
attachTo = attachTo && attachTo.setTimeout ? attachTo : global;
// Don't get fooled by e.g. browserify environments.
if ({}.toString.call(global.process) === "[object process]") {
// For Node.js before 0.9
installNextTickImplementation();
} else if (canUsePostMessage()) {
// For non-IE10 modern browsers
installPostMessageImplementation();
} else if (global.MessageChannel) {
// For web workers, where supported
installMessageChannelImplementation();
} else if (doc && "onreadystatechange" in doc.createElement("script")) {
// For IE 68
installReadyStateChangeImplementation();
} else {
// For older browsers
installSetTimeoutImplementation();
}
attachTo.setImmediate = setImmediate;
attachTo.clearImmediate = clearImmediate;
}(typeof self === "undefined" ? typeof global === "undefined" ? this : global : self));

132
browser/wsproxy.js Normal file
View File

@ -0,0 +1,132 @@
var http = require('http');
var net = require('net');
var IOServer = require('socket.io');
var utils = require('../lib/bcoin/utils');
var IP = require('../lib/bcoin/ip');
var network = require('../lib/bcoin/protocol/network');
var BufferWriter = require('../lib/bcoin/writer');
var ports = [];
var i, type;
for (i = 0; i < network.types.length; i++) {
type = network.types[i];
ports.push(network[type].port);
}
module.exports = function wsproxy(options) {
var target, io;
if (!options)
options = {};
target = new Buffer(
'0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'hex');
io = new IOServer();
io.on('error', function(err) {
utils.error(err.stack + '');
});
io.on('connection', function(ws) {
var snonce = utils.nonce().toArrayLike(Buffer, 'be', 8);
var socket, pow;
ws.emit('info', {
pow: !!options.pow,
snonce: snonce.toString('hex'),
target: target.toString('hex')
});
ws.on('error', function(err) {
utils.error(err.stack + '');
});
ws.on('tcp connect', function(port, host, nonce) {
if (socket)
return;
if (options.pow) {
if (!utils.isNumber(port)
|| typeof host !== 'string'
|| !utils.isNumber(nonce)) {
utils.error('Client did not solve proof of work.');
ws.emit('tcp close');
ws.disconnect();
return;
}
pow = new BufferWriter();
pow.writeU32(nonce);
pow.writeBytes(snonce);
pow.writeU32(port);
pow.writeString(host, 'ascii');
pow = pow.render();
if (utils.cmp(utils.dsha256(pow), target) >= 0) {
utils.error('Client did not solve proof of work.');
ws.emit('tcp close');
ws.disconnect();
return;
}
}
if (IP.isPrivate(host)) {
utils.error('Client is trying to connect to a private ip.');
ws.emit('tcp close');
ws.disconnect();
return;
}
if (ports.indexOf(port) === -1) {
utils.error('Client is trying to connect to a non-bitcoin port.');
ws.emit('tcp close');
ws.disconnect();
return;
}
try {
socket = net.connect(port, host);
utils.error('Connecting to %s:%d.', host, port);
} catch (e) {
utils.error('Closing %s:%d.', host, port);
ws.emit('tcp close');
ws.disconnect();
return;
}
socket.on('connect', function() {
ws.emit('tcp connect');
});
socket.on('data', function(data) {
ws.emit('tcp data', data.toString('hex'));
});
socket.on('error', function(err) {
ws.emit('tcp error', err.message);
});
socket.on('close', function() {
utils.error('Closing %s:%d.', host, port);
ws.emit('tcp close');
ws.disconnect();
});
ws.on('tcp data', function(data) {
socket.write(new Buffer(data, 'hex'));
});
ws.on('disconnect', function() {
socket.destroy();
});
ws.on('close', function() {
socket.destroy();
});
});
});
return io;
};