UNPKG

5.68 kBJavaScriptView Raw
1'use strict';
2
3var _ = require('lodash');
4var URL = require('url');
5
6var Address = require('./address');
7var Unit = require('./unit');
8
9/**
10 * Bitcore URI
11 *
12 * Instantiate an URI from a bitcoin URI String or an Object. An URI instance
13 * can be created with a bitcoin uri string or an object. All instances of
14 * URI are valid, the static method isValid allows checking before instantiation.
15 *
16 * All standard parameters can be found as members of the class, the address
17 * is represented using an {Address} instance and the amount is represented in
18 * satoshis. Any other non-standard parameters can be found under the extra member.
19 *
20 * @example
21 * ```javascript
22 *
23 * var uri = new URI('bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu?amount=1.2');
24 * console.log(uri.address, uri.amount);
25 * ```
26 *
27 * @param {string|Object} data - A bitcoin URI string or an Object
28 * @param {Array.<string>=} knownParams - Required non-standard params
29 * @throws {TypeError} Invalid bitcoin address
30 * @throws {TypeError} Invalid amount
31 * @throws {Error} Unknown required argument
32 * @returns {URI} A new valid and frozen instance of URI
33 * @constructor
34 */
35var URI = function(data, knownParams) {
36 if (!(this instanceof URI)) {
37 return new URI(data, knownParams);
38 }
39
40 this.extras = {};
41 this.knownParams = knownParams || [];
42 this.address = this.network = this.amount = this.message = null;
43
44 if (typeof(data) === 'string') {
45 var params = URI.parse(data);
46 if (params.amount) {
47 params.amount = this._parseAmount(params.amount);
48 }
49 this._fromObject(params);
50 } else if (typeof(data) === 'object') {
51 this._fromObject(data);
52 } else {
53 throw new TypeError('Unrecognized data format.');
54 }
55};
56
57/**
58 * Instantiate a URI from a String
59 *
60 * @param {string} str - JSON string or object of the URI
61 * @returns {URI} A new instance of a URI
62 */
63URI.fromString = function fromString(str) {
64 if (typeof(str) !== 'string') {
65 throw new TypeError('Expected a string');
66 }
67 return new URI(str);
68};
69
70/**
71 * Instantiate a URI from an Object
72 *
73 * @param {Object} data - object of the URI
74 * @returns {URI} A new instance of a URI
75 */
76URI.fromObject = function fromObject(json) {
77 return new URI(json);
78};
79
80/**
81 * Check if an bitcoin URI string is valid
82 *
83 * @example
84 * ```javascript
85 *
86 * var valid = URI.isValid('bitcoin:12A1MyfXbW6RhdRAZEqofac5jCQQjwEPBu');
87 * // true
88 * ```
89 *
90 * @param {string|Object} data - A bitcoin URI string or an Object
91 * @param {Array.<string>=} knownParams - Required non-standard params
92 * @returns {boolean} Result of uri validation
93 */
94URI.isValid = function(arg, knownParams) {
95 try {
96 new URI(arg, knownParams);
97 } catch (err) {
98 return false;
99 }
100 return true;
101};
102
103/**
104 * Convert a bitcoin URI string into a simple object.
105 *
106 * @param {string} uri - A bitcoin URI string
107 * @throws {TypeError} Invalid bitcoin URI
108 * @returns {Object} An object with the parsed params
109 */
110URI.parse = function(uri) {
111 var info = URL.parse(uri, true);
112
113 if (info.protocol !== 'bitcoin:') {
114 throw new TypeError('Invalid bitcoin URI');
115 }
116
117 // workaround to host insensitiveness
118 var group = /[^:]*:\/?\/?([^?]*)/.exec(uri);
119 info.query.address = group && group[1] || undefined;
120
121 return info.query;
122};
123
124URI.Members = ['address', 'amount', 'message', 'label', 'r'];
125
126/**
127 * Internal function to load the URI instance with an object.
128 *
129 * @param {Object} obj - Object with the information
130 * @throws {TypeError} Invalid bitcoin address
131 * @throws {TypeError} Invalid amount
132 * @throws {Error} Unknown required argument
133 */
134URI.prototype._fromObject = function(obj) {
135 /* jshint maxcomplexity: 10 */
136
137 if (!Address.isValid(obj.address)) {
138 throw new TypeError('Invalid bitcoin address');
139 }
140
141 this.address = new Address(obj.address);
142 this.network = this.address.network;
143 this.amount = obj.amount;
144
145 for (var key in obj) {
146 if (key === 'address' || key === 'amount') {
147 continue;
148 }
149
150 if (/^req-/.exec(key) && this.knownParams.indexOf(key) === -1) {
151 throw Error('Unknown required argument ' + key);
152 }
153
154 var destination = URI.Members.indexOf(key) > -1 ? this : this.extras;
155 destination[key] = obj[key];
156 }
157};
158
159/**
160 * Internal function to transform a BTC string amount into satoshis
161 *
162 * @param {string} amount - Amount BTC string
163 * @throws {TypeError} Invalid amount
164 * @returns {Object} Amount represented in satoshis
165 */
166URI.prototype._parseAmount = function(amount) {
167 amount = Number(amount);
168 if (isNaN(amount)) {
169 throw new TypeError('Invalid amount');
170 }
171 return Unit.fromBTC(amount).toSatoshis();
172};
173
174URI.prototype.toObject = URI.prototype.toJSON = function toObject() {
175 var json = {};
176 for (var i = 0; i < URI.Members.length; i++) {
177 var m = URI.Members[i];
178 if (this.hasOwnProperty(m) && typeof(this[m]) !== 'undefined') {
179 json[m] = this[m].toString();
180 }
181 }
182 _.extend(json, this.extras);
183 return json;
184};
185
186/**
187 * Will return a the string representation of the URI
188 *
189 * @returns {string} Bitcoin URI string
190 */
191URI.prototype.toString = function() {
192 var query = {};
193 if (this.amount) {
194 query.amount = Unit.fromSatoshis(this.amount).toBTC();
195 }
196 if (this.message) {
197 query.message = this.message;
198 }
199 if (this.label) {
200 query.label = this.label;
201 }
202 if (this.r) {
203 query.r = this.r;
204 }
205 _.extend(query, this.extras);
206
207 return URL.format({
208 protocol: 'bitcoin:',
209 host: this.address,
210 query: query
211 });
212};
213
214/**
215 * Will return a string formatted for the console
216 *
217 * @returns {string} Bitcoin URI
218 */
219URI.prototype.inspect = function() {
220 return '<URI: ' + this.toString() + '>';
221};
222
223module.exports = URI;