Fix Conflicts:

index.html
This commit is contained in:
Gustavo Cortez 2014-06-02 22:19:15 -03:00
commit 7aebf032ef
16 changed files with 225 additions and 165 deletions

View File

@ -24,7 +24,7 @@ grunt shell --target=dev
Open Copay:
```
node app.js
npm start
```
Then visit localhost:3000 in your browser.
@ -33,16 +33,16 @@ Then visit localhost:3000 in your browser.
## Running copay
To run on a different port:
```
PORT=3001 node app.js
PORT=3001 npm start
```
To open up five different instances to test 3-of-5 multisig with yourself, then run this in 5 different terminals:
```
PORT=3001 node app.js
PORT=3002 node app.js
PORT=3003 node app.js
PORT=3004 node app.js
PORT=3005 node app.js
PORT=3001 npm start
PORT=3002 npm start
PORT=3003 npm start
PORT=3004 npm start
PORT=3005 npm start
```
To open n different instances more easily, just run:
@ -51,12 +51,22 @@ n=5
node launch.js $n &
```
To require Copay as a module for use within you application:
```js
require('copay').start(3000, function(location) {
console.log('Copay server running at: ' + location);
});
```
## Configuration
Default configuration can be found in the config.js file.
See config.js for more info on configuration options.
# About Copay
General
@ -64,22 +74,22 @@ General
*Copay* implements a multisig wallet using p2sh addresses. It supports multiple wallet configurations, such as 3-of-5
(3 required signatures from 5 participant peers) or 2-of-3. To create a multisig wallet shared between multiple participants,
*Copay* needs the public keys of all the wallet participants. Those public keys are incorporated into the
*Copay* needs the public keys of all the wallet participants. Those public keys are incorporated into the
wallet configuration and are combined to generate a payment address with which funds can be sent into the wallet.
To unlock the payment and spend the wallet's funds, a quorum of participant signatures must be collected
To unlock the payment and spend the wallet's funds, a quorum of participant signatures must be collected
and assembled in the transaction. The funds cannot be spent without at least the minimum number of
signatures required by the wallet configuration (2 of 3, 3 of 5, 6 of 6, etc).
Each participant manages their own private key, and that private key is never transmitted anywhere.
Once a transaction proposal is created, the proposal is distributed among the
wallet participants for each participant to sign the transaction locally.
Once the transaction is signed, the last signing participant will broadcast the
signatures required by the wallet configuration (2 of 3, 3 of 5, 6 of 6, etc).
Each participant manages their own private key, and that private key is never transmitted anywhere.
Once a transaction proposal is created, the proposal is distributed among the
wallet participants for each participant to sign the transaction locally.
Once the transaction is signed, the last signing participant will broadcast the
transaction to the Bitcoin network using a public API (defaults to the Insight API).
*Copay* also implements BIP32 to generate new addresses for the peers. The public key each participant contributes
to the wallet is a BIP32 extended public key. As additional public keys are needed for wallet operations (to produce
new addresses to receive payments into the wallet, for example) new public keys can be derived from the participants'
original extended public keys. Each participant keeps their own private keys locally. Private keys are not shared.
*Copay* also implements BIP32 to generate new addresses for the peers. The public key each participant contributes
to the wallet is a BIP32 extended public key. As additional public keys are needed for wallet operations (to produce
new addresses to receive payments into the wallet, for example) new public keys can be derived from the participants'
original extended public keys. Each participant keeps their own private keys locally. Private keys are not shared.
Private keys are used to sign transaction proposals to make a payment from the shared wallet.
Serverless web
@ -88,17 +98,17 @@ Serverless web
JavaScript. For persistent storage, the client browser's *localStorage* is used. Locally stored data is
encrypted using a password provided by the local user. Data kept in browser local storage should be
backed up for safekeeping using one of the methods provided by *Copay*, such as downloading the data into a file.
Without a proper backup of the user's private key data, all funds stored in the
wallet may be lost or inaccessible if the browser's localStorage is deleted, the browser uninstalled,
Without a proper backup of the user's private key data, all funds stored in the
wallet may be lost or inaccessible if the browser's localStorage is deleted, the browser uninstalled,
the local hard disk fails, etc.
Peer communications
-------------------
*Copay* uses peer-to-peer (p2p) networking to communicate between wallet participants. Participants exchange transaction
proposals, public keys, nicknames and information about the wallet configuration. Private keys are *not* shared with anyone.
*Copay* uses peer-to-peer (p2p) networking to communicate between wallet participants. Participants exchange transaction
proposals, public keys, nicknames and information about the wallet configuration. Private keys are *not* shared with anyone.
*Copay* network communications use the webRTC protocol. A p2p facilitator server is needed to enable the peers to find each other.
*Copay* uses the open-sourced *peerjs* server implementation for p2p discovery. Wallet participants can use a
*Copay* uses the open-sourced *peerjs* server implementation for p2p discovery. Wallet participants can use a
public peerjs server or install their own. Once the peers find each other, a true p2p connection is established between the
peers and there is no further flow of information to the p2p discovery server.
@ -107,19 +117,19 @@ certificate.
Security model
--------------
On top of webRTC, *Copay* peers authenticate as part of the "wallet ring"(WR) using an identity
key and a network key.
On top of webRTC, *Copay* peers authenticate as part of the "wallet ring"(WR) using an identity
key and a network key.
The *identity key* is a ECDSA public key derived from the participant's extended public
The *identity key* is a ECDSA public key derived from the participant's extended public
key using a specific BIP32 branch. This special public key is never used for Bitcoin address creation, and
should only be known by members of the WR.
In *Copay* this special public key is named *copayerId*. The copayerId is hashed and the hash is used to
should only be known by members of the WR.
In *Copay* this special public key is named *copayerId*. The copayerId is hashed and the hash is used to
register with the peerjs server. Registering with a hash avoids disclosing the copayerId to parties outside of the WR.
Peer discovery is accomplished using only the hashes of the WR members' copayerIds. All members of the WR
know the full copayerIds of all the other members of the WR.
The *network key* is a random key generated and distributed among the wallet members during wallet creation.
The network key is stored by each peer in the wallet configuration. The network key is used in establishing a CCM/AES
The *network key* is a random key generated and distributed among the wallet members during wallet creation.
The network key is stored by each peer in the wallet configuration. The network key is used in establishing a CCM/AES
authenticated encrypted channel between all members of the WR, on top of webRTC. Use of this
*network key* prevents man-in-the-middle attacks from a compromised peerjs server.
@ -137,16 +147,6 @@ The string is encoded using Bitcoin's Base58Check encoding, to prevent transmiss
Peer Authentication
-------------------
It is important to note that - except for private keys - *all data* in the wallet is shared with *all members of the wallet*.
Private keys are never shared with anyone and are never sent over the network. There are no *private* messages between
It is important to note that - except for private keys - *all data* in the wallet is shared with *all members of the wallet*.
Private keys are never shared with anyone and are never sent over the network. There are no *private* messages between
individual members of the wallet. All members of a wallet see everything that happens in that wallet.

21
app.js
View File

@ -1,12 +1,15 @@
var express=require("express");
var http=require("http");
var express = require('express');
var http = require('http');
var app = express();
var app=express();
app.start = function(port, callback) {
var port = process.env.PORT || 3000;
app.set("port", port);
app.use(express.static(__dirname));
app.set('port', port);
app.use(express.static(__dirname));
app.listen(port, function(){
console.log("Listening at: http://localhost:" + port);
});
app.listen(port, function() {
callback('http://localhost:' + port);
});
};
module.exports = app;

View File

@ -14,7 +14,7 @@
<body ng-cloak class="ng-cloak">
<div id="wrap">
<div data-ng-init="init()" data-ng-controller="HeaderController">
<div class="header">
<div class="header">
<div class="header-content">
<div class="large-3 medium-3 small-3 columns">
<span class="logo"></span>
@ -23,15 +23,15 @@
<div class="large-4 medium-4 columns line-dashed-v">
<a href="#/addresses" class="has-tip" tooltip-placement="bottom" tooltip="{{$root.wallet.id}}">
<strong><span>{{$root.wallet.getName()}}</span></strong>
</a>
</a>
<a class="button radius small-icon" title="Manual Refresh"
ng-disabled="$root.loading"
ng-click="refresh()"><i class="fi-refresh"></i></a>
<a class="button radius small-icon" title="Signout"
<a class="button radius small-icon" title="Signout"
ng-click="signout()"><i class="fi-power"></i></a>
</div>
<div class="large-4 medium-4 columns line-dashed-v">
Balance:
Balance:
<span ng-if="$root.updatingBalance">
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
@ -48,7 +48,7 @@
<i class="fi-bitcoin"></i>
</span>
</div>
</div>
</div>
@ -63,14 +63,14 @@
<section class="top-bar-section {{isCollapsed && 'hide_menu' || 'show_menu'}}">
<ul>
<li data-ng-repeat="item in menu" ui-route="/{{item.link}}" class="text-center" data-ng-class="{active: isActive(item)}">
<a href="{{item.link}}" ng-click="toggleCollapse()"> <i class="{{item.icon}}"></i> {{item.title}}
<a href="{{item.link}}" ng-click="toggleCollapse()"> <i class="{{item.icon}}"></i> {{item.title}}
<span class="label alert round" ng-if="item.link=='#/transactions' && $root.pendingTxCount > 0">{{$root.pendingTxCount}}</span>
</a>
</li>
</ul>
</section>
</nav>
</div>
<div class="row" ng-if="updateVersion">
@ -95,19 +95,19 @@
<div ng-if='$root.wallet && !$root.wallet.publicKeyRing.isComplete() && !loading'>
<div class="medium-12 small-12 columns">
<div class="alert-box secondary radius" data-alert>
<i class="fi-info"></i>
Not all copayers have joined your wallet yet.
<i class="fi-info"></i>
Not all copayers have joined your wallet yet.
<span ng-show="$root.wallet.publicKeyRing.totalCopayers - $root.wallet.publicKeyRing.registeredCopayers()>1">
{{$root.wallet.publicKeyRing.totalCopayers - $root.wallet.publicKeyRing.registeredCopayers() }} people have
{{$root.wallet.publicKeyRing.totalCopayers - $root.wallet.publicKeyRing.registeredCopayers() }} people have
</span>
<span ng-show="$root.wallet.publicKeyRing.totalCopayers - $root.wallet.publicKeyRing.registeredCopayers()==1">
One person has
One person has
</span>
yet to join.
yet to join.
</div>
</div>
<div class="medium-12 small-12 columns ">
<div class="medium-12 small-12 columns ">
<div class="panel radius m30v">
<h3 class="m15b">Share this secret with your other copayers
<small> for them to join your wallet</small>
@ -141,7 +141,7 @@
<link rel="stylesheet" ng-href="{{theme}}">
<div class="row" ng-show="!$root.wallet">
<div class="large-12 columns text-right">
Copay
Copay
<small>v{{version}}</small>
</div>
</div>
@ -159,18 +159,18 @@
</div>
<div class="large-9 medium-9 small-9 columns">
<a href="#/addresses" > </a>
<div class="bottom-copay"
<div class="bottom-copay"
ng-repeat="c in $root.wallet.getRegisteredPeerIds()" class="has-tip" tooltip-popup-delay="1000" tooltip-placement="top" tooltip="{{c.nick}}">
<video ng-if="$root.videoInfo[c.peerId]"
<video ng-if="$root.videoInfo[c.peerId]"
avatar peer="{{c}}"
autoplay
ng-class="($root.wallet.getOnlinePeerIDs().indexOf(c.peerId) != -1) ? 'online' : 'offline'"
ng-src="{{getVideoURL(c.peerId)}}"
></video>
<img ng-if="!$root.videoInfo[c.peerId]"
<img ng-if="!$root.videoInfo[c.peerId]"
avatar peer="{{c}}"
ng-class="($root.wallet.getOnlinePeerIDs().indexOf(c.peerId) != -1) ? 'online' : 'offline'"
src="./img/satoshi.gif"
src="./img/satoshi.gif"
/>
</div>
</div>
@ -182,7 +182,7 @@
<div class="signin" ng-controller="SigninController">
<div data-alert class="alert-box info radius" ng-show="loading && !failure">
<i class="size-21 fi-bitcoin-circle icon-rotate spinner"></i>
Authenticating and Looking for peers...
Authenticating and looking for peers...
</div>
<div class="alert-box error radius" ng-show="failure">
Oops, we had an error! Looks like you are already connected to this wallet,
@ -235,7 +235,7 @@
</div> <!-- End !loading -->
</div>
</script>
<script type="text/ng-template" id="import.html">
<div ng-controller="ImportController">
<div data-alert class="alert-box info radius" ng-show="loading">
@ -347,7 +347,7 @@
</div>
</div>
</script>
<!-- ADDRESS -->
<script type="text/ng-template" id="addresses.html">
<div class="addresses" ng-controller="AddressesController">
@ -355,11 +355,11 @@
<div class="row">
<div class="large-9 medium-12 columns" ng-if="addresses[0]">
<div class="large-8 medium-8 columns" ng-init="showAll=0">
<a class="panel radius db" ng-repeat="addr in addresses | limitAddress:showAll"
ng-click="selectAddress(addr)"
<a class="panel radius db" ng-repeat="addr in addresses | limitAddress:showAll"
ng-click="selectAddress(addr)"
ng-class="{selected : addr.address == selectedAddr.address}">
<span>{{addr.address}}</span>
<span>{{addr.address}}</span>
<small ng-if="addr.isChange">change</small>
<span class="right">
@ -379,7 +379,7 @@
</span>
</a>
<a class="secondary radius" ng-click="showAll=!showAll" ng-show="(addresses|withoutFunds) > 1">
<a class="secondary radius" ng-click="showAll=!showAll" ng-show="(addresses|withoutFunds) > 1">
<span ng-if="!showAll">Show all</span>
<span ng-if="showAll">Show less</span>
</a>
@ -409,9 +409,9 @@
<button class="secondary radius expandi new-address" ng-click="newAddr()"
ng-disabled="loading" loading="Creating"> Create </button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</script>
<!-- TRANSACTIONS -->
@ -439,7 +439,7 @@
</div>
</div>
<div class="tx-copayers">
<div class="tx-copayers">
<div class="box-copayers" ng-repeat="(cId, actions) in tx.peerActions">
<figure class="left">
@ -467,17 +467,17 @@
<div class="text-center" style="margin-right:16px; color:#999; font-size:12px">
{{$root.wallet.publicKeyRing.nicknameForCopayer(cId)}}
</div>
</div>
</div>
</div>
<div class="row m10">
<div class="large-5 medium-5 columns" ng-show="!tx.sentTs">
<div ng-show="!tx.signedByUs && !tx.rejectedByUs && !tx.finallyRejected && tx.missingSignatures">
<div ng-show="!tx.signedByUs && !tx.rejectedByUs && !tx.finallyRejected && tx.missingSignatures">
<button class="secondary radius m10r" ng-click="sign(tx.ntxid)" ng-disabled="loading" loading="Signing">
<i class="fi-check"></i> Sign
</button>
<button class="warning radius" ng-click="reject(tx.ntxid)" ng-disabled="loading" loading="Rejecting">
<i class="fi-x" ></i> Reject
<i class="fi-x" ></i> Reject
</button>
</div>
<div ng-show="!tx.missingSignatures && !tx.sentTs">
@ -486,7 +486,7 @@
</button>
</div>
</div>
<div class="large-7 medium-7 columns text-right">
<div ng-show="tx.finallyRejected" class="text-warning m10b">
Transaction finally rejected
@ -496,20 +496,20 @@
<strong>Sent</strong> <span class="text-gray" am-time-ago="tx.sentTs"></span>
</div>
<div class="ellipsis small">
Transaction ID:
Transaction ID:
<a href="http://{{getShortNetworkName()}}.insight.is/tx/{{tx.sentTxid}}" target="blank">
{{tx.sentTxid}}
</a>
</div>
</div>
<p class="text-gray m5b" ng-show="!tx.finallyRejected && tx.missingSignatures==1">
<p class="text-gray m5b" ng-show="!tx.finallyRejected && tx.missingSignatures==1">
One signature missing
</p>
<p class="text-gray m5b" ng-show="!tx.finallyRejected && tx.missingSignatures>1">
<p class="text-gray m5b" ng-show="!tx.finallyRejected && tx.missingSignatures>1">
{{tx.missingSignatures}} signatures missing</p>
<div class="ellipsis small text-gray">
<div class="ellipsis small text-gray">
<strong>Fee:</strong> <i class="fi-bitcoin"></i> {{tx.fee}}
<strong>Proposal ID:</strong> {{tx.ntxid}}
<strong>Proposal ID:</strong> {{tx.ntxid}}
</div>
</div>
</div>
@ -572,7 +572,7 @@
</div>
</div>
</div>
</div>
</div>
</div>
</script>
@ -626,11 +626,11 @@
<div class="row">
<div class="large-6 medium-6 columns">
<div class="row collapse">
<label for="amount">Amount
<label for="amount">Amount
<small ng-hide="!sendForm.amount.$pristine">required</small>
<small class="is-valid" ng-show="!sendForm.amount.$invalid && !sendForm.amount.$pristine">valid!</small>
<small class="has-error" ng-show="sendForm.amount.$invalid && !sendForm.amount.$pristine">
not valid.</small>
not valid.</small>
<small ng-show="notEnoughAmount">{{notEnoughAmount}}</small>
</label>
<div class="small-9 columns">
@ -648,7 +648,7 @@
<div class="large-5 columns">
<button type="submit" class="button secondary radius text-center" ng-disabled="sendForm.$invalid || loading" loading="Sending">
Send
</button>
</button>
</div>
</div>
</form>
@ -732,10 +732,10 @@
<script type="text/ng-template" id="unsupported.html">
<h2 class="text-center">Browser unsupported</h2>
<h3 class="text-center">
Copay uses webRTC for peer-to-peer communications,
but your browser does not support it.
Please use
a current version of Google Chrome, Mozilla Firefox, or Opera.
Copay uses webRTC for peer-to-peer communications,
but your browser does not support it.
Please use
a current version of Google Chrome, Mozilla Firefox, or Opera.
<br><br>
For more information
@ -753,7 +753,7 @@ on supported browsers please check <a href="http://www.webrtc.org/">http://www.w
<script src="config.js"></script>
<script src="js/shell.js"></script>
<script src="lib/angular/angular.min.js"></script>
<script src="lib/moment/moment.js"></script>
<script src="lib/angular-moment/angular-moment.js"></script>

View File

@ -59,4 +59,3 @@ angular.module('copay.video', []);
angular.module('copay.import', []);
angular.module('copay.passphrase', []);
angular.module('copay.settings', []);

View File

@ -6,7 +6,15 @@ angular.module('copay.import').controller('ImportController',
var reader = new FileReader();
var _importBackup = function(encryptedObj) {
Passphrase.getBase64Async($scope.password, function(passphrase){
$rootScope.wallet = walletFactory.fromEncryptedObj(encryptedObj, passphrase);
var w = walletFactory.fromEncryptedObj(encryptedObj, passphrase);
if (!w) {
$scope.loading = false;
$rootScope.$flashMessage = { message: 'Wrong password', type: 'error'};
$rootScope.$digest();
return;
}
$rootScope.wallet = w;
controllerUtils.startNetwork($rootScope.wallet);
});
};
@ -33,6 +41,7 @@ angular.module('copay.import').controller('ImportController',
var password = form.password.$modelValue;
if (!backupFile && !backupText) {
$scope.loading = false;
$rootScope.$flashMessage = { message: 'Please, select your backup file or paste the text', type: 'error'};
$scope.loading = false;
return;

View File

@ -23,17 +23,15 @@ angular.module('copay.signin').controller('SigninController',
console.log('## Obtaining passphrase...');
Passphrase.getBase64Async(password, function(passphrase){
console.log('## Done.');
console.log('## Passphrase obtained');
var w = walletFactory.open($scope.selectedWalletId, { passphrase: passphrase});
if (!w) {
$scope.loading = $scope.failure = false;
$rootScope.$flashMessage = { message: 'Bad password or connection error', type: 'error'};
$rootScope.$flashMessage = { message: 'Wrong password', type: 'error'};
$rootScope.$digest();
return;
}
console.log('[signin.js.49]'); //TODO
installStartupHandlers(w);
console.log('[signin.js.52]'); //TODO
controllerUtils.startNetwork(w);
});
};

View File

@ -63,9 +63,7 @@ Wallet.prototype.seedCopayer = function(pubKey) {
Wallet.prototype.connectToAll = function() {
console.log('[Wallet.js.57]'); //TODO
var all = this.publicKeyRing.getAllCopayerIds();
console.log('[Wallet.js.58] connecting'); //TODO
this.network.connectToCopayers(all);
if (this.seededCopayerId) {
@ -224,8 +222,8 @@ Wallet.prototype.netStart = function() {
self.log('[Wallet.js.132:openError:] GOT openError'); //TODO
self.emit('openError');
});
net.on('error', function(){
self.emit('connectionError'); // Bubble the error
net.on('error', function() {
self.emit('connectionError');
});
net.on('close', function() {
self.emit('close');

View File

@ -39,14 +39,11 @@ WalletFactory.prototype.log = function(){
WalletFactory.prototype._checkRead = function(walletId) {
var s = this.storage;
var ret =
(
s.get(walletId, 'publicKeyRing') &&
s.get(walletId, 'txProposals') &&
s.get(walletId, 'opts') &&
s.get(walletId, 'privateKey')
)?true:false;
;
return ret?true:false;
s.get(walletId, 'privateKey');
return !!ret;
};
WalletFactory.prototype.fromObj = function(obj) {
@ -60,8 +57,9 @@ WalletFactory.prototype.fromObj = function(obj) {
WalletFactory.prototype.fromEncryptedObj = function(base64, password) {
this.storage._setPassphrase(password);
var walletObj = this.storage.import(base64);
var w= this.fromObj(walletObj);
w.store();
if (!walletObj) return false;
var w = this.fromObj(walletObj);
if (!w) return false;
return w;
};
@ -149,7 +147,6 @@ WalletFactory.prototype.open = function(walletId, opts) {
var w = this.read(walletId);
if (w) {
this._checkVersion(w.version);
w.store();

View File

@ -269,7 +269,6 @@ Network.prototype._setupPeerHandlers = function(openCallback) {
self._checkAnyPeer();
});
p.on('connection', function(dataConn) {
console.log('### NEW INBOUND CONNECTION %d/%d', self.connectedPeers.length, self.maxPeers);
if (self.connectedPeers.length >= self.maxPeers) {

View File

@ -3,6 +3,7 @@
var imports = require('soop').imports();
var id = 0;
function Storage(opts) {
opts = opts || {};
@ -33,12 +34,16 @@ Storage.prototype._encryptObj = function(obj) {
};
Storage.prototype._decrypt = function(base64) {
var decryptedStr=null;
var decrypted = CryptoJS.AES.decrypt(base64, this._getPassphrase());
if (decrypted)
decryptedStr = decrypted.toString(CryptoJS.enc.Utf8);
var decryptedStr = null;
try {
var decrypted = CryptoJS.AES.decrypt(base64, this._getPassphrase());
if (decrypted)
decryptedStr = decrypted.toString(CryptoJS.enc.Utf8);
} catch (e) {
console.log('Error while decrypting ' + base64);
return null;
}
return decryptedStr;
};
@ -49,22 +54,16 @@ Storage.prototype._decryptObj = function(base64) {
Storage.prototype._read = function(k) {
var ret;
try {
ret = localStorage.getItem(k);
if (ret){
ret = this._decrypt(ret);
ret = ret.toString(CryptoJS.enc.Utf8);
ret = JSON.parse(ret);
}
} catch (e) {
console.log('Error while decrypting: '+e);
return null;
};
ret = localStorage.getItem(k);
if (!ret) return null;
ret = this._decrypt(ret);
if (!ret) return null;
ret = ret.toString(CryptoJS.enc.Utf8);
ret = JSON.parse(ret);
return ret;
};
Storage.prototype._write = function(k,v) {
Storage.prototype._write = function(k, v) {
v = JSON.stringify(v);
v = this._encrypt(v);
@ -78,7 +77,7 @@ Storage.prototype.getGlobal = function(k) {
};
// set value for key
Storage.prototype.setGlobal = function(k,v) {
Storage.prototype.setGlobal = function(k, v) {
localStorage.setItem(k, JSON.stringify(v));
};
@ -92,46 +91,45 @@ Storage.prototype._key = function(walletId, k) {
};
// get value by key
Storage.prototype.get = function(walletId, k) {
var ret = this._read(this._key(walletId,k));
var ret = this._read(this._key(walletId, k));
return ret;
};
// set value for key
Storage.prototype.set = function(walletId, k,v) {
this._write(this._key(walletId,k), v);
Storage.prototype.set = function(walletId, k, v) {
this._write(this._key(walletId, k), v);
};
// remove value for key
Storage.prototype.remove = function(walletId, k) {
this.removeGlobal(this._key(walletId,k));
this.removeGlobal(this._key(walletId, k));
};
Storage.prototype.setName = function(walletId, name) {
this.setGlobal('nameFor::'+walletId, name);
this.setGlobal('nameFor::' + walletId, name);
};
Storage.prototype.getName = function(walletId) {
return this.getGlobal('nameFor::'+walletId);
return this.getGlobal('nameFor::' + walletId);
};
Storage.prototype.getWalletIds = function() {
var walletIds = [];
var uniq = {};
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
var split = key.split('::');
if (split.length == 2) {
var key = localStorage.key(i);
var split = key.split('::');
if (split.length == 2) {
var walletId = split[0];
if (walletId === 'nameFor') continue;
if (typeof uniq[walletId] === 'undefined' ) {
if (typeof uniq[walletId] === 'undefined') {
walletIds.push(walletId);
uniq[walletId] = 1;
}
}
}
}
}
return walletIds;
};
@ -140,9 +138,9 @@ Storage.prototype.getWallets = function() {
var uniq = {};
var ids = this.getWalletIds();
for (var i in ids){
for (var i in ids) {
wallets.push({
id:ids[i],
id: ids[i],
name: this.getName(ids[i]),
});
}

View File

@ -62,35 +62,29 @@ angular.module('copay.controllerUtils')
$rootScope.wallet = w;
$location.path('addresses');
video.setOwnPeer(myPeerID, w, handlePeerVideo);
console.log('# Done ready handler');
});
w.on('publicKeyRingUpdated', function(dontDigest) {
console.log('[start publicKeyRing handler]'); //TODO
root.setSocketHandlers();
root.updateAddressList();
if (!dontDigest) {
console.log('[pkr digest]');
$rootScope.$digest();
console.log('[done digest]');
}
});
w.on('txProposalsUpdated', function(dontDigest) {
root.updateTxs({onlyPending:true});
root.updateBalance(function(){
if (!dontDigest) {
console.log('[txp digest]');
$rootScope.$digest();
console.log('[done digest]');
}
});
});
w.on('openError', root.onErrorDigest);
w.on('connectionError', root.onErrorDigest);
w.on('connect', function(peerID) {
if (peerID) {
video.callPeer(peerID, handlePeerVideo);
}
console.log('[digest]');
$rootScope.$digest();
});
w.on('disconnect', function(peerID) {
@ -129,7 +123,6 @@ angular.module('copay.controllerUtils')
$rootScope.availableBalance = safeBalance;
root.updateAddressList();
$rootScope.updatingBalance = false;
console.log('Done updating balance.'); //TODO
return cb?cb():null;
});
};
@ -145,7 +138,6 @@ angular.module('copay.controllerUtils')
var inT = w.getTxProposals().sort(function(t1, t2) { return t1.createdTs < t2.createdTs });
var txs = [];
console.log('[START LOOP]'); //TODO
inT.forEach(function(i, index){
if (opts.skip && (index < opts.skip[0] || index >= opts.skip[1])) {
return txs.push(null);

60
js/shell.js Normal file
View File

@ -0,0 +1,60 @@
/*
** copay-shell integration
*/
(function() {
/*
** This is a monkey patch for when Copay is running from
** within Copay-Shell (atom-shell). Since the renderer (the frontend)
** receives context from Node.js, we get a `module.exports` contruct
** available to us. Because of this, some libs (specifically Moment.js)
** attempt to assume their CommonJS form and bind to this. This causes
** there to be no references in the window to these libs, so let's trick
** the renderer into thinking that we are _not_ in a CommonJS environment.
*/
if (typeof module !== 'undefined') module = { exports: null };
// are we running in copay shell?
if (process && process.type === 'renderer') initCopayShellBindings();
function controller(name) {
return angular.element(
document.querySelectorAll(
'[ng-controller="' + name + '"], [data-ng-controller="' + name + '"]'
)
).scope();
};
function initCopayShellBindings() {
var ipc = require('ipc');
ipc.on('address:create', function(data) {
location.href = '#/addresses';
controller('AddressesController').newAddr();
});
ipc.on('transactions:send', function(data) {
location.href = '#/send';
});
ipc.on('transactions:all', function(data) {
location.href = '#/transactions';
controller('TransactionsController').show();
});
ipc.on('transactions:pending', function(data) {
location.href = '#/transactions';
controller('TransactionsController').show(true);
});
ipc.on('backup:download', function(data) {
});
ipc.on('backup:email', function(data) {
});
};
})();

View File

@ -26,7 +26,6 @@ var DEFAULT_PORT = process.env.DEFAULT_PORT || 3000;
for (var i=0; i<N; i++) {
var port =(i+DEFAULT_PORT);
console.log('Simulating copayer #'+(i+1)+' at http://localhost:'+port);
var command = 'PORT='+port+' node app.js &'
var command = 'PORT='+port+' npm start &'
exec(command, puts);
}

View File

@ -17,7 +17,9 @@
"bugs": {
"url": "https://github.com/bitpay/copay/issues"
},
"main": "app.js",
"scripts": {
"start": "node server.js",
"test": "mocha"
},
"homepage": "https://github.com/bitpay/copay",

6
server.js Normal file
View File

@ -0,0 +1,6 @@
var server = require('./app');
var port = process.env.PORT || 3000;
server.start(port, function(loc){
console.log('Listening at: ' + loc);
});