3 changed files with 273 additions and 1 deletions
@ -0,0 +1,92 @@ |
|||
'use strict'; |
|||
|
|||
var _ = require('lodash'); |
|||
|
|||
var URL = require('url'); |
|||
var Address = require('./address'); |
|||
|
|||
var URI = function(arg, knownArgs) { |
|||
this.extras = {}; |
|||
this.knownArgs = knownArgs || []; |
|||
this.address = this.network = this.amount = this.message = null; |
|||
|
|||
if (typeof(arg) == 'string') { |
|||
var params = URI.parse(arg); |
|||
this._fromObject(params); |
|||
} else if (typeof(arg) == 'object') { |
|||
this._fromObject(arg); |
|||
} else { |
|||
throw new TypeError('Unrecognized data format.'); |
|||
} |
|||
}; |
|||
|
|||
URI.isValid = function(arg, knownArgs) { |
|||
try { |
|||
var uri = new URI(arg, knownArgs); |
|||
return true; |
|||
} catch(err) { |
|||
return false; |
|||
} |
|||
}; |
|||
|
|||
URI.parse = function(uri) { |
|||
var info = URL.parse(uri, true); |
|||
|
|||
if (info.protocol != 'bitcoin:') { |
|||
throw new TypeError('Invalid bitcoin URI'); |
|||
} |
|||
|
|||
// workaround to host insensitiveness
|
|||
var group = /[^:]*:\/?\/?([^?]*)/.exec(uri); |
|||
info.query.address = group && group[1] || undefined; |
|||
|
|||
return info.query; |
|||
}; |
|||
|
|||
|
|||
URI.prototype._fromObject = function(obj) { |
|||
var members = ['address', 'amount', 'message', 'label']; |
|||
|
|||
if (!Address.isValid(obj.address)) throw new TypeError('Invalid bitcoin address'); |
|||
|
|||
this.address = new Address(obj.address); |
|||
this.network = this.address.network; |
|||
|
|||
if (obj.amount) this.amount = this._parseAmount(obj.amount); |
|||
|
|||
for (var key in obj) { |
|||
if (key === 'address' || key === 'amount') continue; |
|||
|
|||
if (/^req-/.exec(key) && this.knownArgs.indexOf(key) === -1) { |
|||
throw Error('Unknown required argument ' + key); |
|||
} |
|||
|
|||
var destination = members.indexOf(key) > -1 ? this : this.extras; |
|||
destination[key] = obj[key]; |
|||
} |
|||
}; |
|||
|
|||
URI.prototype._parseAmount = function(amount) { |
|||
var amount = Number(amount); |
|||
if (isNaN(amount)) throw new TypeError('Invalid amount'); |
|||
return amount; // TODO: Convert to Satoshis (yemel)
|
|||
}; |
|||
|
|||
|
|||
URI.prototype.toString = function() { |
|||
var query = _.clone(this.extras); |
|||
if (this.amount) query.amount = this.amount; // TODO: Convert to BTC (yemel)
|
|||
if (this.message) query.message = this.message; |
|||
|
|||
return URL.format({ |
|||
protocol: 'bitcoin:', |
|||
host: this.address, |
|||
query: query |
|||
}); |
|||
}; |
|||
|
|||
URI.prototype.inspect = function() { |
|||
return '<URI: ' + this.toString()+ '>'; |
|||
} |
|||
|
|||
module.exports = URI; |
@ -0,0 +1,180 @@ |
|||
'use strict'; |
|||
|
|||
var chai = chai || require('chai'); |
|||
var should = chai.should(); |
|||
var expect = chai.expect; |
|||
var bitcore = require('..'); |
|||
var URI = bitcore.URI; |
|||
|
|||
describe('URI', function() { |
|||
|
|||
it('should parse uris strings', function() { |
|||
var uri; |
|||
|
|||
URI.parse.bind(URI, 'badURI').should.throw(TypeError); |
|||
|
|||
uri = URI.parse('bitcoin:'); |
|||
expect(uri.address).to.be.undefined; |
|||
expect(uri.amount).to.be.undefined; |
|||
expect(uri.otherParam).to.be.undefined; |
|||
|
|||
uri = URI.parse('bitcoin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj'); |
|||
uri.address.should.equal('1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj'); |
|||
expect(uri.amount).to.be.undefined; |
|||
expect(uri.otherParam).to.be.undefined; |
|||
|
|||
uri = URI.parse('bitcoin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj?amount=123.22'); |
|||
uri.address.should.equal('1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj'); |
|||
uri.amount.should.equal('123.22'); |
|||
expect(uri.otherParam).to.be.undefined; |
|||
|
|||
uri = URI.parse('bitcoin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj?amount=123.22&other-param=something&req-extra=param'); |
|||
uri.address.should.equal('1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj'); |
|||
uri.amount.should.equal('123.22'); |
|||
uri['other-param'].should.equal('something'); |
|||
uri['req-extra'].should.equal('param'); |
|||
}); |
|||
|
|||
it('should statically validate uris', function() { |
|||
URI.isValid('bitcoin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj').should.be.true; |
|||
URI.isValid('bitcoin:mkYY5NRvikVBY1EPtaq9fAFgquesdjqECw').should.be.true; |
|||
|
|||
URI.isValid('bitcoin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj?amount=1.2').should.be.true; |
|||
URI.isValid('bitcoin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj?amount=1.2&other=param').should.be.true; |
|||
URI.isValid('bitcoin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj?amount=1.2&req-other=param', ['req-other']).should.be.true; |
|||
URI.isValid('bitcoin:mmrqEBJxUCf42vdb3oozZtyz5mKr3Vb2Em?amount=0.1&r=https%3A%2F%2Ftest.bitpay.com%2Fi%2F6DKgf8cnJC388irbXk5hHu').should.be.true; |
|||
|
|||
URI.isValid('bitcoin:').should.be.false; |
|||
URI.isValid('bitcoin:badUri').should.be.false; |
|||
URI.isValid('bitcoin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfk?amount=bad').should.be.false; |
|||
URI.isValid('bitcoin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfk?amount=1.2&req-other=param').should.be.false; |
|||
URI.isValid('bitcoin:?r=https%3A%2F%2Ftest.bitpay.com%2Fi%2F6DKgf8cnJC388irbXk5hHu').should.be.false; |
|||
}); |
|||
|
|||
it('should fail creation with no params', function() { |
|||
(function(){ |
|||
new URI(); |
|||
}).should.throw(TypeError); |
|||
}); |
|||
|
|||
it('should create instance from bitcoin uri', function() { |
|||
var uri; |
|||
|
|||
uri = new URI('bitcoin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj'); |
|||
uri.address.should.be.instanceof(bitcore.Address); |
|||
uri.network.should.equal('mainnet'); |
|||
|
|||
uri = new URI('bitcoin:mkYY5NRvikVBY1EPtaq9fAFgquesdjqECw'); |
|||
uri.address.should.be.instanceof(bitcore.Address); |
|||
uri.network.should.equal('testnet'); |
|||
|
|||
uri = new URI('bitcoin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj?amount=1.2&other=param'); |
|||
uri.address.should.be.instanceof(bitcore.Address); |
|||
uri.amount.should.equal(1.2); |
|||
expect(uri.other).to.be.undefined; |
|||
uri.extras.other.should.equal('param'); |
|||
|
|||
(function() { |
|||
new URI('bitcoin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj?amount=1.2&other=param&req-required=param'); |
|||
}).should.throw(Error); |
|||
|
|||
uri = new URI('bitcoin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj?amount=1.2&other=param&req-required=param', ['req-required']); |
|||
uri.address.should.be.instanceof(bitcore.Address); |
|||
uri.amount.should.equal(1.2); |
|||
uri.extras.other.should.equal('param'); |
|||
uri.extras['req-required'].should.equal('param'); |
|||
}); |
|||
|
|||
it('should create instance from object', function() { |
|||
var uri; |
|||
|
|||
uri = new URI({ |
|||
address: '1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj' |
|||
}); |
|||
uri.address.should.be.instanceof(bitcore.Address); |
|||
uri.network.should.equal('mainnet'); |
|||
|
|||
uri = new URI({ |
|||
address: 'mkYY5NRvikVBY1EPtaq9fAFgquesdjqECw' |
|||
}); |
|||
uri.address.should.be.instanceof(bitcore.Address); |
|||
uri.network.should.equal('testnet'); |
|||
|
|||
|
|||
uri = new URI({ |
|||
address: '1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj', |
|||
amount: '1.2', |
|||
other: 'param' |
|||
}); |
|||
uri.address.should.be.instanceof(bitcore.Address); |
|||
uri.amount.should.equal(1.2); |
|||
expect(uri.other).to.be.undefined; |
|||
uri.extras.other.should.equal('param'); |
|||
|
|||
(function() { |
|||
new URI({ |
|||
address: '1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj', |
|||
'req-required': param |
|||
}); |
|||
}).should.throw(Error); |
|||
|
|||
uri = new URI({ |
|||
address: '1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj', |
|||
amount: 1.2, |
|||
other: 'param', |
|||
'req-required': 'param' |
|||
}, ['req-required']); |
|||
uri.address.should.be.instanceof(bitcore.Address); |
|||
uri.amount.should.equal(1.2); |
|||
uri.extras.other.should.equal('param'); |
|||
uri.extras['req-required'].should.equal('param'); |
|||
}); |
|||
|
|||
it('should support double slash scheme', function() { |
|||
var uri = new URI('bitcoin://1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj'); |
|||
uri.address.toString().should.equal('1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj'); |
|||
}); |
|||
|
|||
it('should support numeric amounts', function() { |
|||
var uri = new URI('bitcoin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj?amount=12.10001'); |
|||
expect(uri.amount).to.be.equal(12.10001); |
|||
}); |
|||
|
|||
it('should support extra arguments', function() { |
|||
var uri = new URI('bitcoin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj?message=Donation%20for%20project%20xyz&label=myLabel&other=xD'); |
|||
|
|||
should.exist(uri.message); |
|||
uri.message.should.equal('Donation for project xyz'); |
|||
|
|||
should.exist(uri.label); |
|||
uri.label.should.equal('myLabel'); |
|||
|
|||
should.exist(uri.extras.other); |
|||
uri.extras.other.should.equal('xD'); |
|||
}); |
|||
|
|||
it('should generate a valid URI', function() { |
|||
new URI({ |
|||
address: '1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj', |
|||
}).toString().should.equal( |
|||
'bitcoin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj' |
|||
); |
|||
|
|||
new URI({ |
|||
address: '1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj', |
|||
amount: 1.10001, |
|||
message: 'Hello World', |
|||
something: 'else' |
|||
}).toString().should.equal( |
|||
'bitcoin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj?something=else&amount=1.10001&message=Hello%20World' |
|||
); |
|||
|
|||
}); |
|||
|
|||
it('should be case insensitive to protocol', function() { |
|||
var uri1 = new URI('bItcOin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj'); |
|||
var uri2 = new URI('bitcoin:1DP69gMMvSuYhbnxsi4EJEFufUAbDrEQfj'); |
|||
|
|||
uri1.address.toString().should.equal(uri2.address.toString()); |
|||
}); |
|||
}); |
Loading…
Reference in new issue