bitcoincash-oauth-client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +267 -0
- package/dist/index.browser.min.js +1 -0
- package/dist/index.cjs +450 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.mjs +445 -0
- package/package.json +60 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bitcoincash-oauth-client v1.0.0
|
|
3
|
+
* Universal Bitcoin Cash OAuth Client
|
|
4
|
+
* Works in both browser and Node.js environments
|
|
5
|
+
*/
|
|
6
|
+
import { instantiateSecp256k1, generatePrivateKey, CashAddressNetworkPrefix, encodeCashAddress, CashAddressType } from '@bitauth/libauth';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Environment utilities for cross-platform crypto operations
|
|
10
|
+
* Handles browser vs Node.js differences
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
let cryptoModule = null;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get the Node.js crypto module (cached)
|
|
17
|
+
* @returns {Object|null} Crypto module or null if not available
|
|
18
|
+
*/
|
|
19
|
+
async function getNodeCrypto() {
|
|
20
|
+
if (cryptoModule !== null) {
|
|
21
|
+
return cryptoModule;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
// Dynamic import for ES module compatibility
|
|
26
|
+
const crypto = await import('crypto');
|
|
27
|
+
cryptoModule = crypto.default || crypto;
|
|
28
|
+
return cryptoModule;
|
|
29
|
+
} catch (e) {
|
|
30
|
+
cryptoModule = false;
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate secure random bytes (cross-platform)
|
|
37
|
+
* @param {number} length - Number of bytes to generate
|
|
38
|
+
* @returns {Uint8Array} Random bytes
|
|
39
|
+
*/
|
|
40
|
+
async function getRandomBytes(length) {
|
|
41
|
+
// Browser environment: use crypto.getRandomValues
|
|
42
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
43
|
+
return crypto.getRandomValues(new Uint8Array(length));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Node.js environment: use crypto module
|
|
47
|
+
const nodeCrypto = await getNodeCrypto();
|
|
48
|
+
if (nodeCrypto) {
|
|
49
|
+
return new Uint8Array(nodeCrypto.randomBytes(length));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
throw new Error('Unable to generate secure random bytes - no crypto implementation available');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* SHA256 hash (cross-platform)
|
|
57
|
+
* @param {Uint8Array} data
|
|
58
|
+
* @returns {Promise<Uint8Array>}
|
|
59
|
+
*/
|
|
60
|
+
async function sha256(data) {
|
|
61
|
+
// Browser: use crypto.subtle
|
|
62
|
+
if (typeof crypto !== 'undefined' && crypto.subtle) {
|
|
63
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
64
|
+
return new Uint8Array(hashBuffer);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Node.js: use crypto module
|
|
68
|
+
const nodeCrypto = await getNodeCrypto();
|
|
69
|
+
if (nodeCrypto) {
|
|
70
|
+
return new Uint8Array(nodeCrypto.createHash('sha256').update(data).digest());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
throw new Error('SHA256 not available');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* RIPEMD160 hash (cross-platform)
|
|
78
|
+
* @param {Uint8Array} data
|
|
79
|
+
* @returns {Promise<Uint8Array>}
|
|
80
|
+
*/
|
|
81
|
+
async function ripemd160(data) {
|
|
82
|
+
// Node.js: use crypto module (preferred)
|
|
83
|
+
const nodeCrypto = await getNodeCrypto();
|
|
84
|
+
if (nodeCrypto) {
|
|
85
|
+
return new Uint8Array(nodeCrypto.createHash('ripemd160').update(data).digest());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
throw new Error('RIPEMD160 not available. This is required for Bitcoin Cash address generation.');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get fetch implementation for current environment
|
|
93
|
+
* @returns {Function} Fetch implementation
|
|
94
|
+
*/
|
|
95
|
+
function getFetch() {
|
|
96
|
+
// Use global fetch if available (browser or Node.js 18+)
|
|
97
|
+
if (typeof fetch !== 'undefined') {
|
|
98
|
+
return fetch;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
throw new Error('No fetch implementation available. For Node.js < 18, install node-fetch and pass it as an option.');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Bitcoin Cash OAuth Client Library
|
|
106
|
+
* Universal client that works in both browser and Node.js environments
|
|
107
|
+
*
|
|
108
|
+
* Uses libauth for key generation and ECDSA signing
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @typedef {Object} Keypair
|
|
114
|
+
* @property {string} privateKey - Hex-encoded private key
|
|
115
|
+
* @property {string} publicKey - Hex-encoded compressed public key
|
|
116
|
+
* @property {string} address - Bitcoin Cash address
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @typedef {Object} OAuthClientOptions
|
|
121
|
+
* @property {string} [serverUrl="http://localhost:8000"] - OAuth server URL
|
|
122
|
+
* @property {string} [network="mainnet"] - Network type ("mainnet" or "testnet")
|
|
123
|
+
* @property {SecureStorage} [secureStorage] - Storage interface for tokens
|
|
124
|
+
* @property {Function} [fetch] - Custom fetch implementation (optional)
|
|
125
|
+
*/
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @typedef {Object} SecureStorage
|
|
129
|
+
* @property {function(string): string|null} getItem
|
|
130
|
+
* @property {function(string, string): void} setItem
|
|
131
|
+
* @property {function(string): void} removeItem
|
|
132
|
+
*/
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @typedef {Object} AuthenticationResult
|
|
136
|
+
* @property {string} access_token - JWT access token
|
|
137
|
+
* @property {string} refresh_token - Refresh token
|
|
138
|
+
* @property {number} expires_in - Token expiration in seconds
|
|
139
|
+
* @property {string} token_type - Token type (e.g., "bearer")
|
|
140
|
+
*/
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Bitcoin Cash OAuth Client
|
|
144
|
+
* Universal client library for browser and Node.js
|
|
145
|
+
*/
|
|
146
|
+
class BitcoinCashOAuthClient {
|
|
147
|
+
/**
|
|
148
|
+
* Create a new OAuth client instance
|
|
149
|
+
* @param {OAuthClientOptions} options - Configuration options
|
|
150
|
+
*/
|
|
151
|
+
constructor(options = {}) {
|
|
152
|
+
this.serverUrl = options.serverUrl || "http://localhost:8000";
|
|
153
|
+
this.network = options.network || "mainnet";
|
|
154
|
+
this.secureStorage = options.secureStorage || null;
|
|
155
|
+
this.fetchImpl = options.fetch || getFetch();
|
|
156
|
+
this.secp256k1 = null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Initialize the client by instantiating secp256k1
|
|
161
|
+
* @returns {Promise<BitcoinCashOAuthClient>} The initialized client instance
|
|
162
|
+
*/
|
|
163
|
+
async init() {
|
|
164
|
+
if (!this.secp256k1) {
|
|
165
|
+
this.secp256k1 = await instantiateSecp256k1();
|
|
166
|
+
}
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Generate a new Bitcoin Cash keypair
|
|
172
|
+
* @returns {Promise<Keypair>} Keypair object with privateKey, publicKey, and address
|
|
173
|
+
*/
|
|
174
|
+
async generateKeypair() {
|
|
175
|
+
await this.init();
|
|
176
|
+
|
|
177
|
+
// Generate random bytes for private key
|
|
178
|
+
const randomBytes = await getRandomBytes(32);
|
|
179
|
+
|
|
180
|
+
// libauth's generatePrivateKey expects a function that returns Uint8Array
|
|
181
|
+
// We create a closure that returns our pre-generated random bytes
|
|
182
|
+
const secureRandom = () => randomBytes;
|
|
183
|
+
|
|
184
|
+
// Generate private key (32 bytes)
|
|
185
|
+
const privateKeyBytes = generatePrivateKey(secureRandom);
|
|
186
|
+
|
|
187
|
+
// Derive compressed public key (33 bytes)
|
|
188
|
+
const publicKeyBytes = this.secp256k1.derivePublicKeyCompressed(privateKeyBytes);
|
|
189
|
+
|
|
190
|
+
// Convert to address
|
|
191
|
+
const address = await this.publicKeyToCashAddress(publicKeyBytes);
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
privateKey: this.bytesToHex(privateKeyBytes),
|
|
195
|
+
publicKey: this.bytesToHex(publicKeyBytes),
|
|
196
|
+
address,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Convert public key to Bitcoin Cash CashAddr format
|
|
202
|
+
* @param {Uint8Array} publicKey - Compressed public key
|
|
203
|
+
* @returns {Promise<string>} Bitcoin Cash CashAddr address
|
|
204
|
+
*/
|
|
205
|
+
async publicKeyToCashAddress(publicKey) {
|
|
206
|
+
// Hash public key: RIPEMD160(SHA256(publicKey))
|
|
207
|
+
const sha256Hash = await sha256(publicKey);
|
|
208
|
+
const ripemd160Hash = await ripemd160(sha256Hash);
|
|
209
|
+
|
|
210
|
+
// Determine network prefix
|
|
211
|
+
const prefix = this.network === "mainnet"
|
|
212
|
+
? CashAddressNetworkPrefix.mainnet
|
|
213
|
+
: CashAddressNetworkPrefix.testnet;
|
|
214
|
+
|
|
215
|
+
// Encode as CashAddr (P2PKH type)
|
|
216
|
+
const address = encodeCashAddress(prefix, CashAddressType.P2PKH, ripemd160Hash);
|
|
217
|
+
|
|
218
|
+
if (typeof address !== 'string') {
|
|
219
|
+
throw new Error(`Failed to encode CashAddress: ${address}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return address;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Convert bytes to hex string
|
|
227
|
+
* @param {Uint8Array} bytes
|
|
228
|
+
* @returns {string}
|
|
229
|
+
*/
|
|
230
|
+
bytesToHex(bytes) {
|
|
231
|
+
return Array.from(bytes)
|
|
232
|
+
.map(b => b.toString(16).padStart(2, "0"))
|
|
233
|
+
.join("");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Convert hex string to bytes
|
|
238
|
+
* @param {string} hex
|
|
239
|
+
* @returns {Uint8Array}
|
|
240
|
+
*/
|
|
241
|
+
hexToBytes(hex) {
|
|
242
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
243
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
244
|
+
bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
|
|
245
|
+
}
|
|
246
|
+
return bytes;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Register a new user with the server
|
|
251
|
+
* @param {string} address - Bitcoin Cash address
|
|
252
|
+
* @param {string} [userId] - Optional user-provided ID
|
|
253
|
+
* @returns {Promise<Object>} Registration result with assigned userId
|
|
254
|
+
*/
|
|
255
|
+
async register(address, userId = null) {
|
|
256
|
+
const response = await this.fetchImpl(`${this.serverUrl}/auth/register`, {
|
|
257
|
+
method: "POST",
|
|
258
|
+
headers: {
|
|
259
|
+
"Content-Type": "application/json",
|
|
260
|
+
},
|
|
261
|
+
body: JSON.stringify({
|
|
262
|
+
address,
|
|
263
|
+
user_id: userId,
|
|
264
|
+
}),
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
if (!response.ok) {
|
|
268
|
+
const error = await response.text();
|
|
269
|
+
throw new Error(`Registration failed: ${response.status} ${response.statusText} - ${error}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return await response.json();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Create authentication message (protocol|domain|userId|timestamp)
|
|
277
|
+
* @param {string} userId
|
|
278
|
+
* @param {number} [timestamp] - Unix timestamp (defaults to now)
|
|
279
|
+
* @param {string} [domain] - Domain/host (defaults to window.location.host or 'oauth')
|
|
280
|
+
* @returns {string}
|
|
281
|
+
*/
|
|
282
|
+
createAuthMessage(userId, timestamp = null, domain = null) {
|
|
283
|
+
const ts = timestamp || Math.floor(Date.now() / 1000);
|
|
284
|
+
const host = domain || (typeof window !== 'undefined' && window?.location?.host) || 'oauth';
|
|
285
|
+
return `bitcoincash-oauth|${host}|${userId}|${ts}`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Sign authentication message with private key
|
|
290
|
+
* @param {string} message - The message to sign (userId,timestamp)
|
|
291
|
+
* @param {string} privateKeyHex - Hex-encoded private key
|
|
292
|
+
* @returns {Promise<string>} DER-encoded signature in hex
|
|
293
|
+
*/
|
|
294
|
+
async signAuthMessage(message, privateKeyHex) {
|
|
295
|
+
await this.init();
|
|
296
|
+
|
|
297
|
+
const privateKey = this.hexToBytes(privateKeyHex);
|
|
298
|
+
|
|
299
|
+
// Hash the message using SHA256
|
|
300
|
+
const messageBytes = new TextEncoder().encode(message);
|
|
301
|
+
const messageHash = await sha256(messageBytes);
|
|
302
|
+
|
|
303
|
+
// Sign using secp256k1
|
|
304
|
+
const signature = this.secp256k1.signMessageHashDER(privateKey, messageHash);
|
|
305
|
+
|
|
306
|
+
return this.bytesToHex(signature);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Authenticate with the server
|
|
311
|
+
* @param {string} userId
|
|
312
|
+
* @param {string} privateKeyHex
|
|
313
|
+
* @param {string} publicKeyHex
|
|
314
|
+
* @param {number} [timestamp] - Optional timestamp
|
|
315
|
+
* @param {string} [domain] - Optional domain for message binding
|
|
316
|
+
* @returns {Promise<AuthenticationResult>} Authentication result with access_token
|
|
317
|
+
*/
|
|
318
|
+
async authenticate(userId, privateKeyHex, publicKeyHex, timestamp = null, domain = null) {
|
|
319
|
+
const ts = timestamp || Math.floor(Date.now() / 1000);
|
|
320
|
+
const message = this.createAuthMessage(userId, ts, domain);
|
|
321
|
+
const signature = await this.signAuthMessage(message, privateKeyHex);
|
|
322
|
+
|
|
323
|
+
const response = await this.fetchImpl(`${this.serverUrl}/auth/token`, {
|
|
324
|
+
method: "POST",
|
|
325
|
+
headers: {
|
|
326
|
+
"Content-Type": "application/json",
|
|
327
|
+
},
|
|
328
|
+
body: JSON.stringify({
|
|
329
|
+
user_id: userId,
|
|
330
|
+
timestamp: ts,
|
|
331
|
+
public_key: publicKeyHex,
|
|
332
|
+
signature: signature,
|
|
333
|
+
}),
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
if (!response.ok) {
|
|
337
|
+
const error = await response.text();
|
|
338
|
+
throw new Error(`Authentication failed: ${response.status} ${response.statusText} - ${error}`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const result = await response.json();
|
|
342
|
+
|
|
343
|
+
// Store token if secure storage is available
|
|
344
|
+
if (this.secureStorage && result.access_token) {
|
|
345
|
+
this.secureStorage.setItem("oauth_token", result.access_token);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Get stored token
|
|
353
|
+
* @returns {string|null}
|
|
354
|
+
*/
|
|
355
|
+
getToken() {
|
|
356
|
+
if (this.secureStorage) {
|
|
357
|
+
return this.secureStorage.getItem("oauth_token");
|
|
358
|
+
}
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Make authenticated request
|
|
364
|
+
* @param {string} endpoint - API endpoint (relative to serverUrl)
|
|
365
|
+
* @param {Object} [options] - Fetch options
|
|
366
|
+
* @returns {Promise<Response>}
|
|
367
|
+
*/
|
|
368
|
+
async authenticatedRequest(endpoint, options = {}) {
|
|
369
|
+
const token = this.getToken();
|
|
370
|
+
|
|
371
|
+
if (!token) {
|
|
372
|
+
throw new Error("No authentication token available");
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const headers = {
|
|
376
|
+
"Authorization": `Bearer ${token}`,
|
|
377
|
+
...options.headers,
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
return this.fetchImpl(`${this.serverUrl}${endpoint}`, {
|
|
381
|
+
...options,
|
|
382
|
+
headers,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Refresh access token
|
|
388
|
+
* @param {string} refreshToken
|
|
389
|
+
* @returns {Promise<AuthenticationResult>}
|
|
390
|
+
*/
|
|
391
|
+
async refreshToken(refreshToken) {
|
|
392
|
+
const response = await this.fetchImpl(`${this.serverUrl}/auth/refresh`, {
|
|
393
|
+
method: "POST",
|
|
394
|
+
headers: {
|
|
395
|
+
"Content-Type": "application/json",
|
|
396
|
+
},
|
|
397
|
+
body: JSON.stringify({
|
|
398
|
+
refresh_token: refreshToken,
|
|
399
|
+
}),
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
if (!response.ok) {
|
|
403
|
+
const error = await response.text();
|
|
404
|
+
throw new Error(`Token refresh failed: ${response.status} ${response.statusText} - ${error}`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const result = await response.json();
|
|
408
|
+
|
|
409
|
+
if (this.secureStorage && result.access_token) {
|
|
410
|
+
this.secureStorage.setItem("oauth_token", result.access_token);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return result;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Revoke token
|
|
418
|
+
* @param {string} token
|
|
419
|
+
* @returns {Promise<Object>}
|
|
420
|
+
*/
|
|
421
|
+
async revokeToken(token) {
|
|
422
|
+
const response = await this.fetchImpl(`${this.serverUrl}/auth/revoke`, {
|
|
423
|
+
method: "POST",
|
|
424
|
+
headers: {
|
|
425
|
+
"Content-Type": "application/json",
|
|
426
|
+
},
|
|
427
|
+
body: JSON.stringify({
|
|
428
|
+
token,
|
|
429
|
+
}),
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
if (!response.ok) {
|
|
433
|
+
const error = await response.text();
|
|
434
|
+
throw new Error(`Token revocation failed: ${response.status} ${response.statusText} - ${error}`);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (this.secureStorage) {
|
|
438
|
+
this.secureStorage.removeItem("oauth_token");
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return await response.json();
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export { BitcoinCashOAuthClient, BitcoinCashOAuthClient as default };
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bitcoincash-oauth-client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/paytaca/bitcoincash-oauth.git"
|
|
7
|
+
},
|
|
8
|
+
"description": "Universal JavaScript client library for Bitcoin Cash OAuth authentication - works in both browser and Node.js environments",
|
|
9
|
+
"main": "dist/index.cjs",
|
|
10
|
+
"module": "dist/index.mjs",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.mjs"
|
|
18
|
+
},
|
|
19
|
+
"require": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.cjs"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "rollup -c",
|
|
32
|
+
"prepublishOnly": "npm run build",
|
|
33
|
+
"test": "node test/test.js"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"bitcoin-cash",
|
|
37
|
+
"bch",
|
|
38
|
+
"oauth",
|
|
39
|
+
"authentication",
|
|
40
|
+
"libauth",
|
|
41
|
+
"browser",
|
|
42
|
+
"nodejs",
|
|
43
|
+
"universal"
|
|
44
|
+
],
|
|
45
|
+
"author": "",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@bitauth/libauth": "^1.19.1"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@rollup/plugin-commonjs": "^25.0.0",
|
|
52
|
+
"@rollup/plugin-node-resolve": "^15.0.0",
|
|
53
|
+
"@rollup/plugin-terser": "^0.4.0",
|
|
54
|
+
"rollup": "^4.0.0",
|
|
55
|
+
"typescript": "^5.3.0"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=14.0.0"
|
|
59
|
+
}
|
|
60
|
+
}
|