@qrlwallet/connect 2.0.0 → 3.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/README.md +3 -1
- package/dist/index.d.mts +285 -18
- package/dist/index.d.ts +285 -18
- package/dist/index.js +914 -144
- package/dist/index.mjs +896 -143
- package/package.json +10 -8
package/dist/index.js
CHANGED
|
@@ -35,6 +35,7 @@ __export(index_exports, {
|
|
|
35
35
|
ConnectionManager: () => ConnectionManager,
|
|
36
36
|
ConnectionStatus: () => ConnectionStatus,
|
|
37
37
|
DEFAULT_RELAY_URL: () => DEFAULT_RELAY_URL,
|
|
38
|
+
DIGEST_LEN: () => DIGEST_LEN,
|
|
38
39
|
FP_LEN: () => FP_LEN,
|
|
39
40
|
KeyExchange: () => KeyExchange,
|
|
40
41
|
KeyExchangeMessageType: () => KeyExchangeMessageType,
|
|
@@ -42,18 +43,34 @@ __export(index_exports, {
|
|
|
42
43
|
PROTOCOL_VERSION: () => PROTOCOL_VERSION,
|
|
43
44
|
QRLConnect: () => QRLConnectProvider,
|
|
44
45
|
QRLConnectProvider: () => QRLConnectProvider,
|
|
46
|
+
QRL_CONNECT_PROVIDER_INFO: () => QRL_CONNECT_PROVIDER_INFO,
|
|
45
47
|
RESTRICTED_METHODS: () => RESTRICTED_METHODS,
|
|
48
|
+
SCHEME_TAG_MSG: () => SCHEME_TAG_MSG,
|
|
49
|
+
SCHEME_TAG_TYPED: () => SCHEME_TAG_TYPED,
|
|
50
|
+
SCHEME_VERSION_MSG: () => SCHEME_VERSION_MSG,
|
|
51
|
+
SCHEME_VERSION_TYPED: () => SCHEME_VERSION_TYPED,
|
|
46
52
|
SocketClient: () => SocketClient,
|
|
47
53
|
UNRESTRICTED_METHODS: () => UNRESTRICTED_METHODS,
|
|
54
|
+
bytesToHex: () => bytesToHex,
|
|
48
55
|
cidFromString: () => cidFromString,
|
|
49
56
|
cidRandom: () => cidRandom,
|
|
50
57
|
cidToString: () => cidToString,
|
|
51
58
|
computeFingerprint: () => computeFingerprint,
|
|
59
|
+
computeMessageDigest: () => computeMessageDigest,
|
|
60
|
+
computeTypedDataDigest: () => computeTypedDataDigest,
|
|
61
|
+
concatBytes: () => concatBytes,
|
|
62
|
+
encodeField: () => encodeField,
|
|
63
|
+
encodeType: () => encodeType,
|
|
52
64
|
fingerprintEquals: () => fingerprintEquals,
|
|
53
65
|
generateConnectionURI: () => generateConnectionURI,
|
|
54
66
|
getAppStoreUrl: () => getAppStoreUrl,
|
|
67
|
+
hashStruct: () => hashStruct,
|
|
68
|
+
hexToBytes: () => hexToBytes,
|
|
55
69
|
isMobileBrowser: () => isMobileBrowser,
|
|
56
|
-
parseConnectionURI: () => parseConnectionURI
|
|
70
|
+
parseConnectionURI: () => parseConnectionURI,
|
|
71
|
+
typeHash: () => typeHash,
|
|
72
|
+
verifyMessage: () => verifyMessage,
|
|
73
|
+
verifyTypedData: () => verifyTypedData
|
|
57
74
|
});
|
|
58
75
|
module.exports = __toCommonJS(index_exports);
|
|
59
76
|
|
|
@@ -62,68 +79,144 @@ var import_eventemitter34 = __toESM(require("eventemitter3"));
|
|
|
62
79
|
|
|
63
80
|
// src/ConnectionManager.ts
|
|
64
81
|
var import_eventemitter33 = __toESM(require("eventemitter3"));
|
|
65
|
-
var import_uuid = require("uuid");
|
|
66
82
|
|
|
67
83
|
// src/KeyExchange.ts
|
|
68
84
|
var import_eventemitter3 = __toESM(require("eventemitter3"));
|
|
69
85
|
|
|
70
|
-
// src/
|
|
86
|
+
// src/crypto/primitives.ts
|
|
71
87
|
var import_ml_kem = require("@noble/post-quantum/ml-kem.js");
|
|
72
|
-
var
|
|
73
|
-
var
|
|
74
|
-
|
|
75
|
-
var DIR_DAPP_TX = new Uint8Array([0, 0, 0, 1]);
|
|
76
|
-
var DIR_WALLET_TX = new Uint8Array([0, 0, 0, 2]);
|
|
77
|
-
var ML_KEM_768_CT_LEN = 1088;
|
|
78
|
-
var AEAD_KEY_LEN = 32;
|
|
79
|
-
function subtle() {
|
|
88
|
+
var import_sha3 = require("@noble/hashes/sha3.js");
|
|
89
|
+
var mldsa = __toESM(require("@theqrl/mldsa87"));
|
|
90
|
+
function webCrypto() {
|
|
80
91
|
const c = globalThis.crypto;
|
|
81
|
-
if (!c
|
|
82
|
-
throw new Error("
|
|
92
|
+
if (!c) {
|
|
93
|
+
throw new Error("crypto: WebCrypto is not available in this environment");
|
|
94
|
+
}
|
|
95
|
+
return c;
|
|
96
|
+
}
|
|
97
|
+
function subtle() {
|
|
98
|
+
const c = webCrypto();
|
|
99
|
+
if (!c.subtle) {
|
|
100
|
+
throw new Error("crypto: WebCrypto SubtleCrypto is not available in this environment");
|
|
83
101
|
}
|
|
84
102
|
return c.subtle;
|
|
85
103
|
}
|
|
86
104
|
function bs(u) {
|
|
87
|
-
|
|
105
|
+
if (u.buffer instanceof ArrayBuffer) {
|
|
106
|
+
return new Uint8Array(u.buffer, u.byteOffset, u.byteLength);
|
|
107
|
+
}
|
|
108
|
+
const copy = new Uint8Array(u.byteLength);
|
|
109
|
+
copy.set(u);
|
|
110
|
+
return copy;
|
|
88
111
|
}
|
|
89
|
-
function
|
|
112
|
+
function mlkemKeygen() {
|
|
90
113
|
const { publicKey, secretKey } = import_ml_kem.ml_kem768.keygen();
|
|
91
114
|
return { pk: publicKey, sk: secretKey };
|
|
92
115
|
}
|
|
93
|
-
function
|
|
116
|
+
function mlkemEncaps(pk) {
|
|
94
117
|
const { cipherText, sharedSecret } = import_ml_kem.ml_kem768.encapsulate(pk);
|
|
95
118
|
return { ct: cipherText, ss: sharedSecret };
|
|
96
119
|
}
|
|
97
|
-
function
|
|
120
|
+
function mlkemDecaps(sk, ct) {
|
|
98
121
|
return import_ml_kem.ml_kem768.decapsulate(ct, sk);
|
|
99
122
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return new Uint8Array(await subtle().digest("SHA-256", bs(buf)));
|
|
123
|
+
function shake256Digest(data, dkLen) {
|
|
124
|
+
return (0, import_sha3.shake256)(data, { dkLen });
|
|
103
125
|
}
|
|
104
|
-
async function
|
|
105
|
-
|
|
106
|
-
|
|
126
|
+
async function sha256(data) {
|
|
127
|
+
return new Uint8Array(await subtle().digest("SHA-256", bs(data)));
|
|
128
|
+
}
|
|
129
|
+
function mldsaVerify(signature, digest, publicKey, ctx) {
|
|
130
|
+
return mldsa.cryptoSignVerify(signature, digest, publicKey, ctx);
|
|
131
|
+
}
|
|
132
|
+
async function hkdfAesGcmKey(ikm, salt, info) {
|
|
133
|
+
const ikmKey = await subtle().importKey("raw", bs(ikm), "HKDF", false, ["deriveKey"]);
|
|
107
134
|
return subtle().deriveKey(
|
|
108
|
-
{ name: "HKDF", hash: "SHA-256", salt:
|
|
109
|
-
|
|
135
|
+
{ name: "HKDF", hash: "SHA-256", salt: bs(salt), info: bs(info) },
|
|
136
|
+
ikmKey,
|
|
110
137
|
{ name: "AES-GCM", length: 256 },
|
|
111
138
|
true,
|
|
112
139
|
["encrypt", "decrypt"]
|
|
113
140
|
);
|
|
114
141
|
}
|
|
115
|
-
async function
|
|
116
|
-
if (raw.length !== AEAD_KEY_LEN) {
|
|
117
|
-
throw new Error(`PQCrypto: raw AEAD key must be ${AEAD_KEY_LEN} bytes`);
|
|
118
|
-
}
|
|
142
|
+
async function importAesGcmKey(raw) {
|
|
119
143
|
return subtle().importKey("raw", bs(raw), { name: "AES-GCM", length: 256 }, true, [
|
|
120
144
|
"encrypt",
|
|
121
145
|
"decrypt"
|
|
122
146
|
]);
|
|
123
147
|
}
|
|
148
|
+
async function exportAesGcmKey(key) {
|
|
149
|
+
return new Uint8Array(await subtle().exportKey("raw", key));
|
|
150
|
+
}
|
|
151
|
+
async function aesGcmEncrypt(key, iv, aad2, plaintext) {
|
|
152
|
+
const ct = await subtle().encrypt(
|
|
153
|
+
{ name: "AES-GCM", iv: bs(iv), additionalData: bs(aad2) },
|
|
154
|
+
key,
|
|
155
|
+
bs(plaintext)
|
|
156
|
+
);
|
|
157
|
+
return new Uint8Array(ct);
|
|
158
|
+
}
|
|
159
|
+
async function aesGcmDecrypt(key, iv, aad2, ciphertext) {
|
|
160
|
+
const pt = await subtle().decrypt(
|
|
161
|
+
{ name: "AES-GCM", iv: bs(iv), additionalData: bs(aad2) },
|
|
162
|
+
key,
|
|
163
|
+
bs(ciphertext)
|
|
164
|
+
);
|
|
165
|
+
return new Uint8Array(pt);
|
|
166
|
+
}
|
|
167
|
+
function randomBytes(n) {
|
|
168
|
+
const c = webCrypto();
|
|
169
|
+
if (typeof c.getRandomValues !== "function") {
|
|
170
|
+
throw new Error("crypto: crypto.getRandomValues is not available");
|
|
171
|
+
}
|
|
172
|
+
return c.getRandomValues(new Uint8Array(n));
|
|
173
|
+
}
|
|
174
|
+
function randomUuid() {
|
|
175
|
+
const b = randomBytes(16);
|
|
176
|
+
b[6] = (b[6] ?? 0) & 15 | 64;
|
|
177
|
+
b[8] = (b[8] ?? 0) & 63 | 128;
|
|
178
|
+
const hex = Array.from(b, (v) => v.toString(16).padStart(2, "0")).join("");
|
|
179
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
180
|
+
}
|
|
181
|
+
function constantTimeEquals(a, b) {
|
|
182
|
+
if (a.length !== b.length) return false;
|
|
183
|
+
let d = 0;
|
|
184
|
+
for (let i = 0; i < a.length; i++) d |= (a[i] ?? 0) ^ (b[i] ?? 0);
|
|
185
|
+
return d === 0;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/PQCrypto.ts
|
|
189
|
+
var textEncoder = new TextEncoder();
|
|
190
|
+
var LABEL = textEncoder.encode("pq-pair/v1");
|
|
191
|
+
var LABEL_AEAD_SUFFIX = textEncoder.encode(" aead");
|
|
192
|
+
var DIR_DAPP_TX = new Uint8Array([0, 0, 0, 1]);
|
|
193
|
+
var DIR_WALLET_TX = new Uint8Array([0, 0, 0, 2]);
|
|
194
|
+
var ML_KEM_768_CT_LEN = 1088;
|
|
195
|
+
var AEAD_KEY_LEN = 32;
|
|
196
|
+
function kemKeygen() {
|
|
197
|
+
return mlkemKeygen();
|
|
198
|
+
}
|
|
199
|
+
function kemEncaps(pk) {
|
|
200
|
+
return mlkemEncaps(pk);
|
|
201
|
+
}
|
|
202
|
+
function kemDecaps(sk, ct) {
|
|
203
|
+
return mlkemDecaps(sk, ct);
|
|
204
|
+
}
|
|
205
|
+
async function transcriptHash(cid, pk, ct) {
|
|
206
|
+
return sha256(concat(LABEL, cid, pk, ct));
|
|
207
|
+
}
|
|
208
|
+
async function deriveAeadKey(ss, htx) {
|
|
209
|
+
const info = concat(LABEL, LABEL_AEAD_SUFFIX, htx);
|
|
210
|
+
return hkdfAesGcmKey(ss, new Uint8Array(32), info);
|
|
211
|
+
}
|
|
212
|
+
async function importRawAeadKey(raw) {
|
|
213
|
+
if (raw.length !== AEAD_KEY_LEN) {
|
|
214
|
+
throw new Error(`PQCrypto: raw AEAD key must be ${AEAD_KEY_LEN} bytes`);
|
|
215
|
+
}
|
|
216
|
+
return importAesGcmKey(raw);
|
|
217
|
+
}
|
|
124
218
|
async function exportRawAeadKey(key) {
|
|
125
|
-
|
|
126
|
-
return new Uint8Array(buf);
|
|
219
|
+
return exportAesGcmKey(key);
|
|
127
220
|
}
|
|
128
221
|
function nonce(dir, seq) {
|
|
129
222
|
if (dir.length !== 4) {
|
|
@@ -145,20 +238,10 @@ function aad(htx, seq) {
|
|
|
145
238
|
return out;
|
|
146
239
|
}
|
|
147
240
|
async function seal(key, dir, seq, htx, pt) {
|
|
148
|
-
|
|
149
|
-
{ name: "AES-GCM", iv: bs(nonce(dir, seq)), additionalData: bs(aad(htx, seq)) },
|
|
150
|
-
key,
|
|
151
|
-
bs(pt)
|
|
152
|
-
);
|
|
153
|
-
return new Uint8Array(ct);
|
|
241
|
+
return aesGcmEncrypt(key, nonce(dir, seq), aad(htx, seq), pt);
|
|
154
242
|
}
|
|
155
243
|
async function open(key, dir, seq, htx, ct) {
|
|
156
|
-
|
|
157
|
-
{ name: "AES-GCM", iv: bs(nonce(dir, seq)), additionalData: bs(aad(htx, seq)) },
|
|
158
|
-
key,
|
|
159
|
-
bs(ct)
|
|
160
|
-
);
|
|
161
|
-
return new Uint8Array(pt);
|
|
244
|
+
return aesGcmDecrypt(key, nonce(dir, seq), aad(htx, seq), ct);
|
|
162
245
|
}
|
|
163
246
|
function zeroize(b) {
|
|
164
247
|
b.fill(0);
|
|
@@ -179,7 +262,7 @@ function toBase64(bytes) {
|
|
|
179
262
|
let bin = "";
|
|
180
263
|
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
181
264
|
const slice = bytes.subarray(i, i + CHUNK);
|
|
182
|
-
bin += String.fromCharCode.apply(null, slice);
|
|
265
|
+
bin += String.fromCharCode.apply(null, Array.from(slice));
|
|
183
266
|
}
|
|
184
267
|
return btoa(bin);
|
|
185
268
|
}
|
|
@@ -189,12 +272,6 @@ function fromBase64(b64) {
|
|
|
189
272
|
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
190
273
|
return out;
|
|
191
274
|
}
|
|
192
|
-
function constantTimeEquals(a, b) {
|
|
193
|
-
if (a.length !== b.length) return false;
|
|
194
|
-
let d = 0;
|
|
195
|
-
for (let i = 0; i < a.length; i++) d |= a[i] ^ b[i];
|
|
196
|
-
return d === 0;
|
|
197
|
-
}
|
|
198
275
|
|
|
199
276
|
// src/config.ts
|
|
200
277
|
var DEFAULT_RELAY_URL = "https://qrlwallet.com";
|
|
@@ -204,15 +281,13 @@ var SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
204
281
|
var STORAGE_KEY_PREFIX = "@qrlwallet/connect";
|
|
205
282
|
var REQUEST_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
206
283
|
var WALLET_UNRESPONSIVE_MS = 30 * 1e3;
|
|
284
|
+
var RECONNECT_WALLET_PROBE_MS = 12 * 1e3;
|
|
207
285
|
var RESTRICTED_METHODS = /* @__PURE__ */ new Set([
|
|
208
286
|
"qrl_requestAccounts",
|
|
209
287
|
"qrl_sendTransaction",
|
|
210
288
|
"qrl_signTransaction",
|
|
211
|
-
"
|
|
212
|
-
"personal_sign",
|
|
289
|
+
"qrl_signMessage",
|
|
213
290
|
"qrl_signTypedData",
|
|
214
|
-
"qrl_signTypedData_v3",
|
|
215
|
-
"qrl_signTypedData_v4",
|
|
216
291
|
"wallet_addQrlChain",
|
|
217
292
|
"wallet_switchQrlChain"
|
|
218
293
|
]);
|
|
@@ -316,6 +391,13 @@ var KeyExchange = class extends import_eventemitter3.default {
|
|
|
316
391
|
this.awaitingSynAck = false;
|
|
317
392
|
this.awaitingAck = false;
|
|
318
393
|
this.keysExchanged = false;
|
|
394
|
+
// The ACK we produced for the current handshake. A wallet that lost its
|
|
395
|
+
// transport right after sending SYNACK re-sends the identical SYNACK on
|
|
396
|
+
// rejoin; we answer with this cached ACK so the handshake converges
|
|
397
|
+
// instead of stalling (onSynAck ignores duplicates and would otherwise
|
|
398
|
+
// never reply). Cleared on reset; not persisted (a reloaded dApp has a
|
|
399
|
+
// completed session and the wallet is no longer awaiting an ACK).
|
|
400
|
+
this.lastAck = null;
|
|
319
401
|
this.isOriginator = isOriginator;
|
|
320
402
|
if (restored) {
|
|
321
403
|
this.session = restored;
|
|
@@ -346,7 +428,7 @@ var KeyExchange = class extends import_eventemitter3.default {
|
|
|
346
428
|
*/
|
|
347
429
|
async onSynAck(cid, msg) {
|
|
348
430
|
if (!this.isOriginator) {
|
|
349
|
-
warn("KeyExchange", "Responder received SYNACK
|
|
431
|
+
warn("KeyExchange", "Responder received SYNACK - ignoring");
|
|
350
432
|
return null;
|
|
351
433
|
}
|
|
352
434
|
if (!this.awaitingSynAck || !this.keypair) {
|
|
@@ -389,11 +471,13 @@ var KeyExchange = class extends import_eventemitter3.default {
|
|
|
389
471
|
this.step = "key_handshake_ACK" /* ACK */;
|
|
390
472
|
this.emit("keys_exchanged");
|
|
391
473
|
this.emit("step_change", this.step);
|
|
392
|
-
|
|
474
|
+
const ack = {
|
|
393
475
|
type: "key_handshake_ACK" /* ACK */,
|
|
394
476
|
c1: toBase64(c1),
|
|
395
477
|
v: PROTOCOL_VERSION
|
|
396
478
|
};
|
|
479
|
+
this.lastAck = ack;
|
|
480
|
+
return ack;
|
|
397
481
|
}
|
|
398
482
|
/**
|
|
399
483
|
* Responder (wallet) side: begin handshake from a scanned QR.
|
|
@@ -434,7 +518,7 @@ var KeyExchange = class extends import_eventemitter3.default {
|
|
|
434
518
|
*/
|
|
435
519
|
async onAck(msg) {
|
|
436
520
|
if (this.isOriginator) {
|
|
437
|
-
warn("KeyExchange", "Originator received ACK
|
|
521
|
+
warn("KeyExchange", "Originator received ACK - ignoring");
|
|
438
522
|
return;
|
|
439
523
|
}
|
|
440
524
|
if (!this.awaitingAck) {
|
|
@@ -461,23 +545,17 @@ var KeyExchange = class extends import_eventemitter3.default {
|
|
|
461
545
|
/** Encrypt a string for the counterparty. Returns base64. */
|
|
462
546
|
async encryptMessage(data) {
|
|
463
547
|
if (!this.session) {
|
|
464
|
-
throw new Error("KeyExchange: cannot encrypt
|
|
548
|
+
throw new Error("KeyExchange: cannot encrypt - session not established");
|
|
465
549
|
}
|
|
550
|
+
const seq = this.session.sendSeq++;
|
|
466
551
|
const pt = textEncoder2.encode(data);
|
|
467
|
-
const ct = await seal(
|
|
468
|
-
this.session.key,
|
|
469
|
-
this.session.sendDir,
|
|
470
|
-
this.session.sendSeq,
|
|
471
|
-
this.session.htx,
|
|
472
|
-
pt
|
|
473
|
-
);
|
|
474
|
-
this.session.sendSeq++;
|
|
552
|
+
const ct = await seal(this.session.key, this.session.sendDir, seq, this.session.htx, pt);
|
|
475
553
|
return toBase64(ct);
|
|
476
554
|
}
|
|
477
555
|
/** Decrypt a base64 ciphertext from the counterparty. */
|
|
478
556
|
async decryptMessage(b64) {
|
|
479
557
|
if (!this.session) {
|
|
480
|
-
throw new Error("KeyExchange: cannot decrypt
|
|
558
|
+
throw new Error("KeyExchange: cannot decrypt - session not established");
|
|
481
559
|
}
|
|
482
560
|
const ct = fromBase64(b64);
|
|
483
561
|
const pt = await open(
|
|
@@ -503,12 +581,17 @@ var KeyExchange = class extends import_eventemitter3.default {
|
|
|
503
581
|
this.keysExchanged = false;
|
|
504
582
|
this.awaitingSynAck = false;
|
|
505
583
|
this.awaitingAck = false;
|
|
584
|
+
this.lastAck = null;
|
|
506
585
|
this.step = "key_handshake_SYN" /* SYN */;
|
|
507
586
|
if (emit) this.emit("step_change", this.step);
|
|
508
587
|
}
|
|
509
588
|
areKeysExchanged() {
|
|
510
589
|
return this.keysExchanged;
|
|
511
590
|
}
|
|
591
|
+
/** The ACK produced for the current handshake, for duplicate-SYNACK replies. */
|
|
592
|
+
getLastAck() {
|
|
593
|
+
return this.lastAck;
|
|
594
|
+
}
|
|
512
595
|
getSession() {
|
|
513
596
|
return this.session;
|
|
514
597
|
}
|
|
@@ -575,7 +658,10 @@ var SocketClient = class extends import_eventemitter32.default {
|
|
|
575
658
|
* Connect to the relay server.
|
|
576
659
|
*/
|
|
577
660
|
connect() {
|
|
578
|
-
if (this.socket
|
|
661
|
+
if (this.socket) {
|
|
662
|
+
if (!this.socket.connected) this.socket.connect();
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
579
665
|
log("Socket", `Connecting to ${this.relayUrl}`);
|
|
580
666
|
this.socket = (0, import_socket.io)(this.relayUrl, {
|
|
581
667
|
path: RELAY_PATH,
|
|
@@ -593,7 +679,7 @@ var SocketClient = class extends import_eventemitter32.default {
|
|
|
593
679
|
const reconnectChannelId = this.channelId;
|
|
594
680
|
this.joinChannelNow(reconnectChannelId).then((result) => {
|
|
595
681
|
this.settlePendingJoin(reconnectChannelId, { result });
|
|
596
|
-
this.emit("reconnected");
|
|
682
|
+
this.emit("reconnected", result);
|
|
597
683
|
}).catch((err) => {
|
|
598
684
|
const reconnectErr = err instanceof Error ? err : new Error(String(err));
|
|
599
685
|
this.settlePendingJoin(reconnectChannelId, { error: reconnectErr });
|
|
@@ -611,8 +697,14 @@ var SocketClient = class extends import_eventemitter32.default {
|
|
|
611
697
|
this.emit("message", data);
|
|
612
698
|
});
|
|
613
699
|
this.socket.on("participants_changed", (data) => {
|
|
614
|
-
|
|
615
|
-
|
|
700
|
+
if (typeof data !== "object" || data === null) return;
|
|
701
|
+
const rec = { ...data };
|
|
702
|
+
const event = typeof rec.event === "string" ? rec.event : "";
|
|
703
|
+
log("Socket", `Participants changed: ${event}`);
|
|
704
|
+
this.emit("participants_changed", {
|
|
705
|
+
event,
|
|
706
|
+
...typeof rec.clientType === "string" ? { clientType: rec.clientType } : {}
|
|
707
|
+
});
|
|
616
708
|
});
|
|
617
709
|
this.socket.on("connect_error", (err) => {
|
|
618
710
|
warn("Socket", `Connection error: ${err.message}`);
|
|
@@ -625,7 +717,7 @@ var SocketClient = class extends import_eventemitter32.default {
|
|
|
625
717
|
* it doesn't fire after the fact.
|
|
626
718
|
*/
|
|
627
719
|
settlePendingJoin(channelId, outcome) {
|
|
628
|
-
if (
|
|
720
|
+
if (this.pendingJoin?.channelId !== channelId) return;
|
|
629
721
|
clearTimeout(this.pendingJoin.watchdog);
|
|
630
722
|
const pending = this.pendingJoin;
|
|
631
723
|
this.pendingJoin = null;
|
|
@@ -692,12 +784,14 @@ var SocketClient = class extends import_eventemitter32.default {
|
|
|
692
784
|
if (response.success) {
|
|
693
785
|
log("Socket", `Joined channel ${channelId}`);
|
|
694
786
|
resolve({
|
|
695
|
-
bufferedMessages: response.bufferedMessages
|
|
696
|
-
channelPublicKey: response.channelPublicKey ?? null
|
|
787
|
+
bufferedMessages: response.bufferedMessages ?? [],
|
|
788
|
+
channelPublicKey: response.channelPublicKey ?? null,
|
|
789
|
+
participants: response.participants ?? [],
|
|
790
|
+
terminated: response.terminated === true
|
|
697
791
|
});
|
|
698
792
|
} else {
|
|
699
793
|
error("Socket", `Failed to join channel: ${response.error}`);
|
|
700
|
-
reject(new Error(response.error
|
|
794
|
+
reject(new Error(response.error ?? "Failed to join channel"));
|
|
701
795
|
}
|
|
702
796
|
}
|
|
703
797
|
);
|
|
@@ -720,7 +814,7 @@ var SocketClient = class extends import_eventemitter32.default {
|
|
|
720
814
|
if (response?.success) {
|
|
721
815
|
resolve({ success: true, buffered: response.buffered });
|
|
722
816
|
} else {
|
|
723
|
-
reject(new Error(response?.error
|
|
817
|
+
reject(new Error(response?.error ?? "Failed to send message"));
|
|
724
818
|
}
|
|
725
819
|
}
|
|
726
820
|
);
|
|
@@ -776,26 +870,26 @@ var DECODE = (() => {
|
|
|
776
870
|
})();
|
|
777
871
|
function decodeChar(charCode) {
|
|
778
872
|
if (charCode < 0 || charCode >= 128) return -1;
|
|
779
|
-
return DECODE[charCode];
|
|
873
|
+
return DECODE[charCode] ?? -1;
|
|
780
874
|
}
|
|
781
875
|
function base45Encode(bytes) {
|
|
782
876
|
const n = bytes.length;
|
|
783
877
|
let out = "";
|
|
784
878
|
let i = 0;
|
|
785
879
|
while (i + 2 <= n) {
|
|
786
|
-
const v = bytes[i] << 8 | bytes[i + 1];
|
|
880
|
+
const v = (bytes[i] ?? 0) << 8 | (bytes[i + 1] ?? 0);
|
|
787
881
|
const c2 = Math.floor(v / 2025);
|
|
788
882
|
const r = v - c2 * 2025;
|
|
789
883
|
const c1 = Math.floor(r / 45);
|
|
790
884
|
const c0 = r - c1 * 45;
|
|
791
|
-
out += ALPHABET[c0]
|
|
885
|
+
out += `${ALPHABET[c0] ?? ""}${ALPHABET[c1] ?? ""}${ALPHABET[c2] ?? ""}`;
|
|
792
886
|
i += 2;
|
|
793
887
|
}
|
|
794
888
|
if (i < n) {
|
|
795
|
-
const v = bytes[i];
|
|
889
|
+
const v = bytes[i] ?? 0;
|
|
796
890
|
const c1 = Math.floor(v / 45);
|
|
797
891
|
const c0 = v - c1 * 45;
|
|
798
|
-
out += ALPHABET[c0]
|
|
892
|
+
out += `${ALPHABET[c0] ?? ""}${ALPHABET[c1] ?? ""}`;
|
|
799
893
|
}
|
|
800
894
|
return out;
|
|
801
895
|
}
|
|
@@ -844,29 +938,18 @@ var FP_LABEL = new TextEncoder().encode("pq-fp/v2");
|
|
|
844
938
|
var CID_LEN = 16;
|
|
845
939
|
var FP_LEN = 32;
|
|
846
940
|
var BLOB_LEN = 4 + CID_LEN + FP_LEN;
|
|
847
|
-
function bs2(u) {
|
|
848
|
-
return u;
|
|
849
|
-
}
|
|
850
941
|
async function computeFingerprint(cid, pk) {
|
|
851
942
|
if (cid.length !== CID_LEN) {
|
|
852
943
|
throw new Error(`qrUri: cid must be ${CID_LEN} bytes`);
|
|
853
944
|
}
|
|
854
|
-
const c = globalThis.crypto;
|
|
855
|
-
if (!c || !c.subtle) {
|
|
856
|
-
throw new Error("qrUri: WebCrypto SubtleCrypto is not available");
|
|
857
|
-
}
|
|
858
945
|
const buf = new Uint8Array(FP_LABEL.length + cid.length + pk.length);
|
|
859
946
|
buf.set(FP_LABEL, 0);
|
|
860
947
|
buf.set(cid, FP_LABEL.length);
|
|
861
948
|
buf.set(pk, FP_LABEL.length + cid.length);
|
|
862
|
-
|
|
863
|
-
return new Uint8Array(digest);
|
|
949
|
+
return sha256(buf);
|
|
864
950
|
}
|
|
865
951
|
function fingerprintEquals(a, b) {
|
|
866
|
-
|
|
867
|
-
let diff = 0;
|
|
868
|
-
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
869
|
-
return diff === 0;
|
|
952
|
+
return constantTimeEquals(a, b);
|
|
870
953
|
}
|
|
871
954
|
async function generateConnectionURI(cid, pk, relayUrl) {
|
|
872
955
|
if (cid.length !== CID_LEN) {
|
|
@@ -905,7 +988,7 @@ async function parseConnectionURI(uri) {
|
|
|
905
988
|
}
|
|
906
989
|
if (params.has("channelId") || params.has("pubKey")) {
|
|
907
990
|
throw new Error(
|
|
908
|
-
"qrUri: legacy v1 URI detected
|
|
991
|
+
"qrUri: legacy v1 URI detected - this wallet and this dApp must both run protocol v2"
|
|
909
992
|
);
|
|
910
993
|
}
|
|
911
994
|
const q = params.get("q");
|
|
@@ -921,7 +1004,7 @@ async function parseConnectionURI(uri) {
|
|
|
921
1004
|
}
|
|
922
1005
|
if (blob.length !== BLOB_LEN) {
|
|
923
1006
|
if (blob.length === 1208 && startsWith(blob, PQP1_MAGIC)) {
|
|
924
|
-
throw new Error("qrUri: legacy PQP1 URI detected
|
|
1007
|
+
throw new Error("qrUri: legacy PQP1 URI detected - regenerate the QR with a v2.0+ dApp SDK");
|
|
925
1008
|
}
|
|
926
1009
|
throw new Error(`qrUri: expected ${BLOB_LEN}-byte blob, got ${blob.length}`);
|
|
927
1010
|
}
|
|
@@ -930,8 +1013,8 @@ async function parseConnectionURI(uri) {
|
|
|
930
1013
|
}
|
|
931
1014
|
const cid = blob.slice(4, 4 + CID_LEN);
|
|
932
1015
|
const fp = blob.slice(4 + CID_LEN, 4 + CID_LEN + FP_LEN);
|
|
933
|
-
const
|
|
934
|
-
return { cid, fp, relayUrl };
|
|
1016
|
+
const r = params.get("r");
|
|
1017
|
+
return { cid, fp, relayUrl: r === null || r === "" ? void 0 : r };
|
|
935
1018
|
}
|
|
936
1019
|
function cidToString(cid) {
|
|
937
1020
|
if (cid.length !== CID_LEN) {
|
|
@@ -939,7 +1022,7 @@ function cidToString(cid) {
|
|
|
939
1022
|
}
|
|
940
1023
|
let hex = "";
|
|
941
1024
|
for (let i = 0; i < CID_LEN; i++) {
|
|
942
|
-
hex += cid[i].toString(16).padStart(2, "0");
|
|
1025
|
+
hex += (cid[i] ?? 0).toString(16).padStart(2, "0");
|
|
943
1026
|
}
|
|
944
1027
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
945
1028
|
}
|
|
@@ -955,15 +1038,79 @@ function cidFromString(s) {
|
|
|
955
1038
|
return out;
|
|
956
1039
|
}
|
|
957
1040
|
function cidRandom() {
|
|
958
|
-
|
|
959
|
-
if (!c || typeof c.getRandomValues !== "function") {
|
|
960
|
-
throw new Error("qrUri: crypto.getRandomValues is not available");
|
|
961
|
-
}
|
|
962
|
-
return c.getRandomValues(new Uint8Array(CID_LEN));
|
|
1041
|
+
return randomBytes(CID_LEN);
|
|
963
1042
|
}
|
|
964
1043
|
|
|
965
1044
|
// src/ConnectionManager.ts
|
|
966
1045
|
var DAPP_PARTICIPANT_CONFLICT_ERROR_MSG = "dapp participant is already connected";
|
|
1046
|
+
function isRecord(v) {
|
|
1047
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1048
|
+
}
|
|
1049
|
+
function isStringArray(v) {
|
|
1050
|
+
return Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
1051
|
+
}
|
|
1052
|
+
function isRelayMessage(v) {
|
|
1053
|
+
return isRecord(v) && typeof v.id === "string" && (v.clientType === "dapp" || v.clientType === "wallet") && "message" in v && (typeof v.message === "string" || typeof v.message === "object" && v.message !== null);
|
|
1054
|
+
}
|
|
1055
|
+
var MESSAGE_TYPE_BY_VALUE = Object.fromEntries(
|
|
1056
|
+
Object.values(MessageType).map((m) => [m, m])
|
|
1057
|
+
);
|
|
1058
|
+
function parseJsonRpcResponse(msg) {
|
|
1059
|
+
if (typeof msg.id !== "string" && typeof msg.id !== "number") return null;
|
|
1060
|
+
const out = {
|
|
1061
|
+
jsonrpc: typeof msg.jsonrpc === "string" ? msg.jsonrpc : "2.0",
|
|
1062
|
+
id: msg.id
|
|
1063
|
+
};
|
|
1064
|
+
if ("result" in msg) out.result = msg.result;
|
|
1065
|
+
if (msg.error !== void 0) {
|
|
1066
|
+
if (!isRecord(msg.error) || typeof msg.error.message !== "string") return null;
|
|
1067
|
+
out.error = {
|
|
1068
|
+
code: typeof msg.error.code === "number" ? msg.error.code : -32e3,
|
|
1069
|
+
message: msg.error.message,
|
|
1070
|
+
data: msg.error.data
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
return out;
|
|
1074
|
+
}
|
|
1075
|
+
function parsePersistedKex(v) {
|
|
1076
|
+
if (!isRecord(v)) return null;
|
|
1077
|
+
const { cid, kAeadRaw, htx, sendDir, recvDir, sendSeq, recvSeq } = v;
|
|
1078
|
+
if (typeof cid !== "string" || typeof kAeadRaw !== "string" || typeof htx !== "string" || typeof sendDir !== "string" || typeof recvDir !== "string" || typeof sendSeq !== "number" || !Number.isInteger(sendSeq) || sendSeq < 0 || typeof recvSeq !== "number" || !Number.isInteger(recvSeq) || recvSeq < 0) {
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
return { cid, kAeadRaw, htx, sendDir, recvDir, sendSeq, recvSeq };
|
|
1082
|
+
}
|
|
1083
|
+
function parseDAppMetadata(v) {
|
|
1084
|
+
if (!isRecord(v) || typeof v.name !== "string" || typeof v.url !== "string") return null;
|
|
1085
|
+
const meta = { name: v.name, url: v.url };
|
|
1086
|
+
if (typeof v.icon === "string") meta.icon = v.icon;
|
|
1087
|
+
if (typeof v.redirectUrl === "string") meta.redirectUrl = v.redirectUrl;
|
|
1088
|
+
return meta;
|
|
1089
|
+
}
|
|
1090
|
+
function parseStoredSession(raw) {
|
|
1091
|
+
let v;
|
|
1092
|
+
try {
|
|
1093
|
+
v = JSON.parse(raw);
|
|
1094
|
+
} catch {
|
|
1095
|
+
return null;
|
|
1096
|
+
}
|
|
1097
|
+
if (!isRecord(v) || v.version !== 3) return null;
|
|
1098
|
+
if (typeof v.channelId !== "string" || typeof v.chainId !== "string") return null;
|
|
1099
|
+
if (typeof v.createdAt !== "number" || typeof v.lastActivity !== "number") return null;
|
|
1100
|
+
const keyExchange = parsePersistedKex(v.keyExchange);
|
|
1101
|
+
const dappMetadata = parseDAppMetadata(v.dappMetadata);
|
|
1102
|
+
if (!keyExchange || !dappMetadata || !isStringArray(v.connectedAccounts)) return null;
|
|
1103
|
+
return {
|
|
1104
|
+
version: 3,
|
|
1105
|
+
channelId: v.channelId,
|
|
1106
|
+
keyExchange,
|
|
1107
|
+
dappMetadata,
|
|
1108
|
+
connectedAccounts: v.connectedAccounts,
|
|
1109
|
+
chainId: v.chainId,
|
|
1110
|
+
createdAt: v.createdAt,
|
|
1111
|
+
lastActivity: v.lastActivity
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
967
1114
|
var TERMINATE_SEND_TIMEOUT_MS = 800;
|
|
968
1115
|
var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.default {
|
|
969
1116
|
constructor(options) {
|
|
@@ -972,14 +1119,16 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
972
1119
|
this.status = "disconnected" /* DISCONNECTED */;
|
|
973
1120
|
this.connectedAccounts = [];
|
|
974
1121
|
this.unresponsiveTimer = null;
|
|
1122
|
+
this.reconnectProbeTimer = null;
|
|
975
1123
|
this.failedReconnects = 0;
|
|
976
1124
|
this.walletPresent = false;
|
|
977
1125
|
this.pendingRestore = null;
|
|
978
1126
|
this.messageQueue = Promise.resolve();
|
|
1127
|
+
this.outboundQueue = Promise.resolve();
|
|
979
1128
|
this.dappMetadata = options.dappMetadata;
|
|
980
|
-
this.relayUrl = options.relayUrl
|
|
981
|
-
this.chainId = options.chainId
|
|
982
|
-
this.storageKey = options.storageKey
|
|
1129
|
+
this.relayUrl = options.relayUrl ?? DEFAULT_RELAY_URL;
|
|
1130
|
+
this.chainId = options.chainId ?? "0x0";
|
|
1131
|
+
this.storageKey = options.storageKey ?? `${STORAGE_KEY_PREFIX}:session`;
|
|
983
1132
|
const stored = this.readStoredSession();
|
|
984
1133
|
if (stored) {
|
|
985
1134
|
this.channelId = stored.channelId;
|
|
@@ -989,7 +1138,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
989
1138
|
this.pendingRestore = stored;
|
|
990
1139
|
log("ConnectionManager", `Found persisted session for channel ${this.channelId}`);
|
|
991
1140
|
} else {
|
|
992
|
-
this.channelId = (
|
|
1141
|
+
this.channelId = randomUuid();
|
|
993
1142
|
}
|
|
994
1143
|
this.socketClient = new SocketClient(this.relayUrl, "dapp");
|
|
995
1144
|
this.setupSocketListeners();
|
|
@@ -1001,13 +1150,23 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1001
1150
|
this.failedReconnects = 0;
|
|
1002
1151
|
}
|
|
1003
1152
|
});
|
|
1004
|
-
this.socketClient.on("reconnected", () => {
|
|
1153
|
+
this.socketClient.on("reconnected", (result) => {
|
|
1154
|
+
if (result?.terminated) {
|
|
1155
|
+
log("ConnectionManager", "Channel terminated, observed on auto-reconnect");
|
|
1156
|
+
this.handleSessionTerminated();
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
if (result) {
|
|
1160
|
+
this.walletPresent = result.participants.includes("wallet");
|
|
1161
|
+
}
|
|
1005
1162
|
if (this.keyExchange?.areKeysExchanged()) {
|
|
1006
1163
|
if (this.walletPresent) {
|
|
1164
|
+
this.clearReconnectProbe();
|
|
1007
1165
|
this.setStatus("connected" /* CONNECTED */);
|
|
1008
1166
|
this.failedReconnects = 0;
|
|
1009
1167
|
} else {
|
|
1010
1168
|
this.setStatus("waiting" /* WAITING */);
|
|
1169
|
+
this.armReconnectProbe();
|
|
1011
1170
|
}
|
|
1012
1171
|
} else {
|
|
1013
1172
|
this.setStatus("waiting" /* WAITING */);
|
|
@@ -1034,11 +1193,17 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1034
1193
|
this.walletPresent = true;
|
|
1035
1194
|
log("ConnectionManager", "Wallet joined channel");
|
|
1036
1195
|
this.clearUnresponsiveTimer();
|
|
1196
|
+
this.clearReconnectProbe();
|
|
1037
1197
|
if (this.keyExchange?.areKeysExchanged()) {
|
|
1038
1198
|
this.failedReconnects = 0;
|
|
1039
1199
|
this.setStatus("connected" /* CONNECTED */);
|
|
1040
1200
|
}
|
|
1041
1201
|
}
|
|
1202
|
+
if (data.event === "close") {
|
|
1203
|
+
log("ConnectionManager", "Wallet closed the channel (explicit terminate)");
|
|
1204
|
+
this.handleSessionTerminated();
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1042
1207
|
if (data.event === "disconnect" || data.event === "leave") {
|
|
1043
1208
|
if (data.clientType === "wallet" || !data.clientType) {
|
|
1044
1209
|
this.walletPresent = false;
|
|
@@ -1054,6 +1219,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1054
1219
|
if (!this.keyExchange) return;
|
|
1055
1220
|
this.keyExchange.on("keys_exchanged", () => {
|
|
1056
1221
|
log("ConnectionManager", "Key exchange complete");
|
|
1222
|
+
this.clearReconnectProbe();
|
|
1057
1223
|
this.setStatus("connected" /* CONNECTED */);
|
|
1058
1224
|
this.walletPresent = true;
|
|
1059
1225
|
this.failedReconnects = 0;
|
|
@@ -1074,6 +1240,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1074
1240
|
*/
|
|
1075
1241
|
async getConnectionURI(retryOnConflict = true) {
|
|
1076
1242
|
this.setStatus("connecting" /* CONNECTING */);
|
|
1243
|
+
this.clearReconnectProbe();
|
|
1077
1244
|
this.pendingRestore = null;
|
|
1078
1245
|
this.walletPresent = false;
|
|
1079
1246
|
if (this.keyExchange) {
|
|
@@ -1083,7 +1250,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1083
1250
|
this.setupKeyExchangeListeners();
|
|
1084
1251
|
}
|
|
1085
1252
|
const pk = this.keyExchange.initiate();
|
|
1086
|
-
this.channelId = (
|
|
1253
|
+
this.channelId = randomUuid();
|
|
1087
1254
|
this.socketClient.setPublicKey(toBase64(pk));
|
|
1088
1255
|
this.socketClient.connect();
|
|
1089
1256
|
try {
|
|
@@ -1094,7 +1261,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1094
1261
|
"ConnectionManager",
|
|
1095
1262
|
"Channel already has an active dApp participant. Rotating to a fresh channel."
|
|
1096
1263
|
);
|
|
1097
|
-
this.channelId = (
|
|
1264
|
+
this.channelId = randomUuid();
|
|
1098
1265
|
return this.getConnectionURI(false);
|
|
1099
1266
|
}
|
|
1100
1267
|
this.setStatus("disconnected" /* DISCONNECTED */);
|
|
@@ -1128,17 +1295,39 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1128
1295
|
this.setStatus("disconnected" /* DISCONNECTED */);
|
|
1129
1296
|
return false;
|
|
1130
1297
|
}
|
|
1298
|
+
return this.joinAndSettle();
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* (Re)open the socket, join the persisted channel, drain buffered relay
|
|
1302
|
+
* messages, and settle status. Shared by the cold-restore reconnect() and
|
|
1303
|
+
* the warm resume() path whose socket was torn down by the reconnect probe.
|
|
1304
|
+
* Assumes channelId and a hydrated keyExchange are already in place, and
|
|
1305
|
+
* that the caller has reset walletPresent + set RECONNECTING status.
|
|
1306
|
+
*/
|
|
1307
|
+
async joinAndSettle() {
|
|
1131
1308
|
this.socketClient.connect();
|
|
1132
1309
|
try {
|
|
1133
|
-
const { bufferedMessages } = await this.socketClient.joinChannel(
|
|
1310
|
+
const { bufferedMessages, participants, terminated } = await this.socketClient.joinChannel(
|
|
1311
|
+
this.channelId
|
|
1312
|
+
);
|
|
1313
|
+
if (terminated) {
|
|
1314
|
+
log("ConnectionManager", "Stored session was terminated by the wallet; dropping it");
|
|
1315
|
+
this.handleSessionTerminated();
|
|
1316
|
+
return false;
|
|
1317
|
+
}
|
|
1318
|
+
if (participants.includes("wallet")) {
|
|
1319
|
+
this.walletPresent = true;
|
|
1320
|
+
}
|
|
1134
1321
|
for (const msg of bufferedMessages) {
|
|
1135
1322
|
this.enqueueRelayMessage(msg);
|
|
1136
1323
|
}
|
|
1137
1324
|
await this.messageQueue;
|
|
1138
|
-
if (this.keyExchange?.areKeysExchanged()) {
|
|
1139
|
-
this.
|
|
1325
|
+
if (this.keyExchange?.areKeysExchanged() && this.walletPresent) {
|
|
1326
|
+
this.clearReconnectProbe();
|
|
1327
|
+
this.setStatus("connected" /* CONNECTED */);
|
|
1140
1328
|
} else {
|
|
1141
1329
|
this.setStatus("waiting" /* WAITING */);
|
|
1330
|
+
this.armReconnectProbe();
|
|
1142
1331
|
}
|
|
1143
1332
|
return true;
|
|
1144
1333
|
} catch (err) {
|
|
@@ -1147,6 +1336,65 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1147
1336
|
return false;
|
|
1148
1337
|
}
|
|
1149
1338
|
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Nudge the connection back to life after the dApp tab / wallet app was
|
|
1341
|
+
* backgrounded. Idempotent and safe to call from visibilitychange / online
|
|
1342
|
+
* / pageshow handlers. If a restored session has not yet been hydrated,
|
|
1343
|
+
* runs the full reconnect(); otherwise just re-opens the socket, whose
|
|
1344
|
+
* own connect handler re-joins the channel and drains the relay buffer.
|
|
1345
|
+
*/
|
|
1346
|
+
resume() {
|
|
1347
|
+
if (this.status === "connected" /* CONNECTED */) return;
|
|
1348
|
+
if (this.pendingRestore && !this.keyExchange?.areKeysExchanged()) {
|
|
1349
|
+
void this.reconnect();
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
if (this.channelId && this.keyExchange?.areKeysExchanged()) {
|
|
1353
|
+
if (this.socketClient.getChannelId()) {
|
|
1354
|
+
this.socketClient.connect();
|
|
1355
|
+
} else {
|
|
1356
|
+
this.setStatus("reconnecting" /* RECONNECTING */);
|
|
1357
|
+
this.walletPresent = false;
|
|
1358
|
+
void this.joinAndSettle();
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* A session was terminated for good (wallet sent a relay 'close', or the
|
|
1364
|
+
* join ack reported a tombstone). Clear local state and surface
|
|
1365
|
+
* DISCONNECTED so the consumer drops to a fresh-pairing UI.
|
|
1366
|
+
*/
|
|
1367
|
+
handleSessionTerminated() {
|
|
1368
|
+
this.clearReconnectProbe();
|
|
1369
|
+
this.clearUnresponsiveTimer();
|
|
1370
|
+
this.walletPresent = false;
|
|
1371
|
+
this.pendingRestore = null;
|
|
1372
|
+
this.keyExchange = null;
|
|
1373
|
+
this.socketClient.leaveChannel();
|
|
1374
|
+
this.socketClient.disconnect();
|
|
1375
|
+
this.clearSession();
|
|
1376
|
+
this.setStatus("disconnected" /* DISCONNECTED */);
|
|
1377
|
+
}
|
|
1378
|
+
armReconnectProbe() {
|
|
1379
|
+
this.clearReconnectProbe();
|
|
1380
|
+
this.reconnectProbeTimer = setTimeout(() => {
|
|
1381
|
+
this.reconnectProbeTimer = null;
|
|
1382
|
+
if (this.walletPresent) return;
|
|
1383
|
+
warn(
|
|
1384
|
+
"ConnectionManager",
|
|
1385
|
+
`No wallet rejoined within ${RECONNECT_WALLET_PROBE_MS}ms; treating reconnect as dead`
|
|
1386
|
+
);
|
|
1387
|
+
this.socketClient.leaveChannel();
|
|
1388
|
+
this.socketClient.disconnect();
|
|
1389
|
+
this.setStatus("disconnected" /* DISCONNECTED */);
|
|
1390
|
+
}, RECONNECT_WALLET_PROBE_MS);
|
|
1391
|
+
}
|
|
1392
|
+
clearReconnectProbe() {
|
|
1393
|
+
if (this.reconnectProbeTimer) {
|
|
1394
|
+
clearTimeout(this.reconnectProbeTimer);
|
|
1395
|
+
this.reconnectProbeTimer = null;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1150
1398
|
/**
|
|
1151
1399
|
* Send a JSON-RPC request to the wallet (async, fire-and-forget).
|
|
1152
1400
|
*/
|
|
@@ -1183,8 +1431,8 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1183
1431
|
try {
|
|
1184
1432
|
const raw = localStorage.getItem(this.storageKey);
|
|
1185
1433
|
if (!raw) return false;
|
|
1186
|
-
const session =
|
|
1187
|
-
if (session
|
|
1434
|
+
const session = parseStoredSession(raw);
|
|
1435
|
+
if (!session) return false;
|
|
1188
1436
|
return Date.now() - session.createdAt <= SESSION_TTL_MS;
|
|
1189
1437
|
} catch {
|
|
1190
1438
|
return false;
|
|
@@ -1199,8 +1447,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1199
1447
|
*/
|
|
1200
1448
|
async flushTerminate() {
|
|
1201
1449
|
if (!this.keyExchange?.areKeysExchanged()) return;
|
|
1202
|
-
const send = this.sendEncrypted({ type: "terminate" /* TERMINATE */ }).catch(() =>
|
|
1203
|
-
});
|
|
1450
|
+
const send = this.sendEncrypted({ type: "terminate" /* TERMINATE */ }).catch(() => void 0);
|
|
1204
1451
|
const timeout = new Promise((resolve) => setTimeout(resolve, TERMINATE_SEND_TIMEOUT_MS));
|
|
1205
1452
|
await Promise.race([send, timeout]);
|
|
1206
1453
|
}
|
|
@@ -1211,6 +1458,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1211
1458
|
*/
|
|
1212
1459
|
async resetForNewChannel() {
|
|
1213
1460
|
this.clearUnresponsiveTimer();
|
|
1461
|
+
this.clearReconnectProbe();
|
|
1214
1462
|
this.walletPresent = false;
|
|
1215
1463
|
await this.flushTerminate();
|
|
1216
1464
|
this.socketClient.leaveChannel();
|
|
@@ -1218,7 +1466,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1218
1466
|
this.clearSession();
|
|
1219
1467
|
this.connectedAccounts = [];
|
|
1220
1468
|
this.pendingRestore = null;
|
|
1221
|
-
this.channelId = (
|
|
1469
|
+
this.channelId = randomUuid();
|
|
1222
1470
|
this.keyExchange = null;
|
|
1223
1471
|
this.socketClient = new SocketClient(this.relayUrl, "dapp");
|
|
1224
1472
|
this.setupSocketListeners();
|
|
@@ -1226,6 +1474,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1226
1474
|
}
|
|
1227
1475
|
async disconnect() {
|
|
1228
1476
|
this.clearUnresponsiveTimer();
|
|
1477
|
+
this.clearReconnectProbe();
|
|
1229
1478
|
this.walletPresent = false;
|
|
1230
1479
|
await this.flushTerminate();
|
|
1231
1480
|
this.socketClient.leaveChannel();
|
|
@@ -1233,36 +1482,58 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1233
1482
|
this.setStatus("disconnected" /* DISCONNECTED */);
|
|
1234
1483
|
this.clearSession();
|
|
1235
1484
|
this.connectedAccounts = [];
|
|
1485
|
+
this.pendingRestore = null;
|
|
1486
|
+
this.keyExchange = null;
|
|
1236
1487
|
}
|
|
1237
1488
|
// ── Internals ──────────────────────────────────────────────
|
|
1238
1489
|
enqueueRelayMessage(data) {
|
|
1239
|
-
|
|
1490
|
+
if (!isRelayMessage(data)) {
|
|
1491
|
+
warn("ConnectionManager", "Dropping malformed relay envelope");
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
this.messageQueue = this.messageQueue.then(() => this.handleRelayMessage(data)).catch((err) => {
|
|
1495
|
+
error("ConnectionManager", "messageQueue handler error:", err);
|
|
1496
|
+
});
|
|
1240
1497
|
}
|
|
1241
1498
|
async handleRelayMessage(data) {
|
|
1242
1499
|
if (data.clientType === "wallet") {
|
|
1243
1500
|
this.walletPresent = true;
|
|
1501
|
+
this.clearReconnectProbe();
|
|
1244
1502
|
if (this.keyExchange?.areKeysExchanged() && this.status !== "connected" /* CONNECTED */) {
|
|
1245
1503
|
this.failedReconnects = 0;
|
|
1246
1504
|
this.setStatus("connected" /* CONNECTED */);
|
|
1247
1505
|
}
|
|
1248
1506
|
}
|
|
1249
1507
|
const message = data.message;
|
|
1250
|
-
if (
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1508
|
+
if (isRecord(message)) {
|
|
1509
|
+
if (message.type === "key_handshake_SYNACK" /* SYNACK */) {
|
|
1510
|
+
if (typeof message.ct === "string" && typeof message.c0 === "string") {
|
|
1511
|
+
await this.handleSynAck({
|
|
1512
|
+
type: "key_handshake_SYNACK" /* SYNACK */,
|
|
1513
|
+
ct: message.ct,
|
|
1514
|
+
c0: message.c0,
|
|
1515
|
+
v: typeof message.v === "number" ? message.v : 0
|
|
1516
|
+
});
|
|
1517
|
+
} else {
|
|
1518
|
+
warn("ConnectionManager", "Dropping malformed SYNACK");
|
|
1519
|
+
}
|
|
1254
1520
|
return;
|
|
1255
1521
|
}
|
|
1256
|
-
if (
|
|
1257
|
-
warn("ConnectionManager", `Unexpected ${
|
|
1522
|
+
if (message.type === "key_handshake_SYN" /* SYN */ || message.type === "key_handshake_ACK" /* ACK */) {
|
|
1523
|
+
warn("ConnectionManager", `Unexpected ${message.type} on dApp side, ignoring`);
|
|
1258
1524
|
return;
|
|
1259
1525
|
}
|
|
1260
1526
|
}
|
|
1261
1527
|
if (typeof message === "string" && this.keyExchange?.areKeysExchanged()) {
|
|
1262
1528
|
try {
|
|
1263
1529
|
const decrypted = await this.keyExchange.decryptMessage(message);
|
|
1530
|
+
await this.persistSession();
|
|
1264
1531
|
const parsed = JSON.parse(decrypted);
|
|
1265
|
-
|
|
1532
|
+
if (isRecord(parsed)) {
|
|
1533
|
+
this.handleDecryptedMessage(parsed);
|
|
1534
|
+
} else {
|
|
1535
|
+
warn("ConnectionManager", "Dropping non-object decrypted payload");
|
|
1536
|
+
}
|
|
1266
1537
|
} catch (err) {
|
|
1267
1538
|
error("ConnectionManager", "Failed to decrypt message:", err);
|
|
1268
1539
|
}
|
|
@@ -1282,16 +1553,21 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1282
1553
|
}
|
|
1283
1554
|
if (response) {
|
|
1284
1555
|
this.sendPlaintext(response);
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
const cachedAck = this.keyExchange.getLastAck();
|
|
1559
|
+
if (this.keyExchange.areKeysExchanged() && cachedAck) {
|
|
1560
|
+
log("ConnectionManager", "Duplicate SYNACK after handshake; re-sending cached ACK");
|
|
1561
|
+
this.sendPlaintext(cachedAck);
|
|
1285
1562
|
}
|
|
1286
1563
|
}
|
|
1287
1564
|
handleDecryptedMessage(msg) {
|
|
1288
1565
|
this.clearUnresponsiveTimer();
|
|
1289
|
-
const type = msg.type;
|
|
1290
|
-
switch (type) {
|
|
1566
|
+
const type = typeof msg.type === "string" ? msg.type : "";
|
|
1567
|
+
switch (MESSAGE_TYPE_BY_VALUE[type]) {
|
|
1291
1568
|
case "wallet_info" /* WALLET_INFO */: {
|
|
1292
|
-
const
|
|
1293
|
-
const
|
|
1294
|
-
const nextChainId = info.chainId || this.chainId;
|
|
1569
|
+
const nextAccounts = isStringArray(msg.accounts) ? msg.accounts : [];
|
|
1570
|
+
const nextChainId = typeof msg.chainId === "string" && msg.chainId ? msg.chainId : this.chainId;
|
|
1295
1571
|
const accountsChanged = !this.areArraysEqual(this.connectedAccounts, nextAccounts);
|
|
1296
1572
|
const chainChanged = this.chainId !== nextChainId;
|
|
1297
1573
|
this.connectedAccounts = nextAccounts;
|
|
@@ -1306,7 +1582,11 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1306
1582
|
break;
|
|
1307
1583
|
}
|
|
1308
1584
|
case "jsonrpc" /* JSONRPC */: {
|
|
1309
|
-
const response = msg;
|
|
1585
|
+
const response = parseJsonRpcResponse(msg);
|
|
1586
|
+
if (!response) {
|
|
1587
|
+
warn("ConnectionManager", "Dropping malformed JSON-RPC response");
|
|
1588
|
+
break;
|
|
1589
|
+
}
|
|
1310
1590
|
this.emit("jsonrpc_response", response);
|
|
1311
1591
|
break;
|
|
1312
1592
|
}
|
|
@@ -1328,11 +1608,28 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1328
1608
|
error("ConnectionManager", "Failed to send plaintext:", err);
|
|
1329
1609
|
});
|
|
1330
1610
|
}
|
|
1331
|
-
|
|
1611
|
+
/**
|
|
1612
|
+
* Serialize every encrypt+persist+send on a single outbound queue.
|
|
1613
|
+
* Callers like sendJsonRpc() are fire-and-forget, so without the queue two
|
|
1614
|
+
* rapid provider.request() calls would interleave inside encryptMessage()
|
|
1615
|
+
* across its await and could complete out of order; the receiver's
|
|
1616
|
+
* contiguous-seq check would then drop the late one. (Nonce reuse itself is
|
|
1617
|
+
* already prevented by the synchronous seq reservation in KeyExchange.)
|
|
1618
|
+
*/
|
|
1619
|
+
sendEncrypted(message) {
|
|
1620
|
+
const task = this.outboundQueue.then(() => this.sendEncryptedNow(message));
|
|
1621
|
+
this.outboundQueue = task.then(
|
|
1622
|
+
() => void 0,
|
|
1623
|
+
() => void 0
|
|
1624
|
+
);
|
|
1625
|
+
return task;
|
|
1626
|
+
}
|
|
1627
|
+
async sendEncryptedNow(message) {
|
|
1332
1628
|
if (!this.keyExchange?.areKeysExchanged()) {
|
|
1333
1629
|
throw new Error("sendEncrypted: not connected");
|
|
1334
1630
|
}
|
|
1335
1631
|
const encrypted = await this.keyExchange.encryptMessage(JSON.stringify(message));
|
|
1632
|
+
await this.persistSession();
|
|
1336
1633
|
await this.socketClient.sendMessage({
|
|
1337
1634
|
id: this.channelId,
|
|
1338
1635
|
clientType: "dapp",
|
|
@@ -1373,7 +1670,7 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1373
1670
|
const persistedKex = await this.keyExchange.exportPersisted();
|
|
1374
1671
|
if (!persistedKex) return;
|
|
1375
1672
|
const session = {
|
|
1376
|
-
version:
|
|
1673
|
+
version: 3,
|
|
1377
1674
|
channelId: this.channelId,
|
|
1378
1675
|
keyExchange: persistedKex,
|
|
1379
1676
|
dappMetadata: this.dappMetadata,
|
|
@@ -1393,13 +1690,13 @@ var _ConnectionManager = class _ConnectionManager extends import_eventemitter33.
|
|
|
1393
1690
|
try {
|
|
1394
1691
|
const raw = localStorage.getItem(this.storageKey);
|
|
1395
1692
|
if (!raw) return null;
|
|
1396
|
-
const session =
|
|
1397
|
-
if (session
|
|
1398
|
-
log("ConnectionManager", "Dropping legacy
|
|
1693
|
+
const session = parseStoredSession(raw);
|
|
1694
|
+
if (!session) {
|
|
1695
|
+
log("ConnectionManager", "Dropping legacy or malformed session from storage");
|
|
1399
1696
|
localStorage.removeItem(this.storageKey);
|
|
1400
1697
|
return null;
|
|
1401
1698
|
}
|
|
1402
|
-
if (
|
|
1699
|
+
if (Date.now() - session.createdAt > SESSION_TTL_MS) {
|
|
1403
1700
|
localStorage.removeItem(this.storageKey);
|
|
1404
1701
|
return null;
|
|
1405
1702
|
}
|
|
@@ -1429,11 +1726,27 @@ function getAppStoreUrl() {
|
|
|
1429
1726
|
}
|
|
1430
1727
|
|
|
1431
1728
|
// src/QRLConnectProvider.ts
|
|
1432
|
-
var
|
|
1729
|
+
var QRL_CONNECT_PROVIDER_INFO = {
|
|
1730
|
+
name: "MyQRLWallet (Mobile)",
|
|
1731
|
+
rdns: "com.qrlwallet.connect",
|
|
1732
|
+
icon: "data:image/png;base64,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"
|
|
1733
|
+
};
|
|
1734
|
+
var EIP6963_ANNOUNCE_EVENT = "eip6963:announceProvider";
|
|
1735
|
+
var EIP6963_REQUEST_EVENT = "eip6963:requestProvider";
|
|
1433
1736
|
var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
1434
1737
|
constructor(options) {
|
|
1435
1738
|
super();
|
|
1436
1739
|
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
1740
|
+
this.eip6963Detail = null;
|
|
1741
|
+
this.eip6963RequestListener = null;
|
|
1742
|
+
this.resumeListener = null;
|
|
1743
|
+
this.resumeDebounce = null;
|
|
1744
|
+
// Random per-instance prefix keeps request ids unique across page loads.
|
|
1745
|
+
// A bare counter restarts at 1 on reload, and the relay buffers messages
|
|
1746
|
+
// for 5 minutes, so a stale buffered response could otherwise be matched
|
|
1747
|
+
// to a fresh request that drew the same small id.
|
|
1748
|
+
this.requestIdPrefix = randomUuid().slice(0, 8);
|
|
1749
|
+
this.requestCounter = 0;
|
|
1437
1750
|
this.isQRLConnect = true;
|
|
1438
1751
|
this.options = options;
|
|
1439
1752
|
if (options.debug) {
|
|
@@ -1447,8 +1760,83 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
1447
1760
|
});
|
|
1448
1761
|
this.setupConnectionListeners();
|
|
1449
1762
|
if (options.autoReconnect !== false) {
|
|
1450
|
-
this.connectionManager.reconnect();
|
|
1763
|
+
void this.connectionManager.reconnect();
|
|
1764
|
+
}
|
|
1765
|
+
this.setupResumeListeners();
|
|
1766
|
+
if (options.announceProvider !== false) {
|
|
1767
|
+
this.startEip6963Announce();
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
startEip6963Announce() {
|
|
1771
|
+
if (typeof window === "undefined" || typeof CustomEvent === "undefined") {
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
const overrides = this.options.providerInfo ?? {};
|
|
1775
|
+
this.eip6963Detail = Object.freeze({
|
|
1776
|
+
info: Object.freeze({
|
|
1777
|
+
uuid: randomUuid(),
|
|
1778
|
+
name: overrides.name ?? QRL_CONNECT_PROVIDER_INFO.name,
|
|
1779
|
+
icon: overrides.icon ?? QRL_CONNECT_PROVIDER_INFO.icon,
|
|
1780
|
+
rdns: overrides.rdns ?? QRL_CONNECT_PROVIDER_INFO.rdns
|
|
1781
|
+
}),
|
|
1782
|
+
provider: this
|
|
1783
|
+
});
|
|
1784
|
+
const announce = () => {
|
|
1785
|
+
if (!this.eip6963Detail) return;
|
|
1786
|
+
window.dispatchEvent(new CustomEvent(EIP6963_ANNOUNCE_EVENT, { detail: this.eip6963Detail }));
|
|
1787
|
+
};
|
|
1788
|
+
this.eip6963RequestListener = announce;
|
|
1789
|
+
window.addEventListener(EIP6963_REQUEST_EVENT, announce);
|
|
1790
|
+
announce();
|
|
1791
|
+
}
|
|
1792
|
+
/**
|
|
1793
|
+
* Re-open the relay socket when the dApp tab regains focus or the network
|
|
1794
|
+
* comes back. Debounced because visibilitychange + online can fire together.
|
|
1795
|
+
* No-op when already connected (ConnectionManager.resume guards that).
|
|
1796
|
+
*/
|
|
1797
|
+
setupResumeListeners() {
|
|
1798
|
+
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
1799
|
+
if (this.resumeListener) return;
|
|
1800
|
+
const resume = () => {
|
|
1801
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
|
|
1802
|
+
if (this.resumeDebounce) clearTimeout(this.resumeDebounce);
|
|
1803
|
+
this.resumeDebounce = setTimeout(() => {
|
|
1804
|
+
this.resumeDebounce = null;
|
|
1805
|
+
this.connectionManager.resume();
|
|
1806
|
+
}, 300);
|
|
1807
|
+
};
|
|
1808
|
+
this.resumeListener = resume;
|
|
1809
|
+
document.addEventListener("visibilitychange", resume);
|
|
1810
|
+
window.addEventListener("online", resume);
|
|
1811
|
+
window.addEventListener("pageshow", resume);
|
|
1812
|
+
}
|
|
1813
|
+
/**
|
|
1814
|
+
* Remove the foreground/online resume listeners. Called on disconnect so a
|
|
1815
|
+
* torn-down session can't be silently revived by a later tab focus; the
|
|
1816
|
+
* listeners are re-armed by getConnectionURI()/newConnection() if the same
|
|
1817
|
+
* provider is re-paired. Safe in any env.
|
|
1818
|
+
*/
|
|
1819
|
+
teardownResumeListeners() {
|
|
1820
|
+
if (typeof window !== "undefined" && typeof document !== "undefined" && this.resumeListener) {
|
|
1821
|
+
document.removeEventListener("visibilitychange", this.resumeListener);
|
|
1822
|
+
window.removeEventListener("online", this.resumeListener);
|
|
1823
|
+
window.removeEventListener("pageshow", this.resumeListener);
|
|
1824
|
+
}
|
|
1825
|
+
this.resumeListener = null;
|
|
1826
|
+
if (this.resumeDebounce) {
|
|
1827
|
+
clearTimeout(this.resumeDebounce);
|
|
1828
|
+
this.resumeDebounce = null;
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Stop announcing this provider over EIP-6963. Safe to call from any env.
|
|
1833
|
+
*/
|
|
1834
|
+
stopEip6963Announce() {
|
|
1835
|
+
if (typeof window !== "undefined" && this.eip6963RequestListener) {
|
|
1836
|
+
window.removeEventListener(EIP6963_REQUEST_EVENT, this.eip6963RequestListener);
|
|
1451
1837
|
}
|
|
1838
|
+
this.eip6963RequestListener = null;
|
|
1839
|
+
this.eip6963Detail = null;
|
|
1452
1840
|
}
|
|
1453
1841
|
setupConnectionListeners() {
|
|
1454
1842
|
this.connectionManager.on("status_changed", (status) => {
|
|
@@ -1506,6 +1894,7 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
1506
1894
|
* Generate a connection URI for QR code display or deep link redirect.
|
|
1507
1895
|
*/
|
|
1508
1896
|
async getConnectionURI() {
|
|
1897
|
+
this.setupResumeListeners();
|
|
1509
1898
|
return this.connectionManager.getConnectionURI();
|
|
1510
1899
|
}
|
|
1511
1900
|
/**
|
|
@@ -1538,7 +1927,7 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
1538
1927
|
if (this.connectionManager.getStatus() !== "connected" /* CONNECTED */) {
|
|
1539
1928
|
throw new Error("Not connected to QRL Wallet");
|
|
1540
1929
|
}
|
|
1541
|
-
const id = ++requestCounter
|
|
1930
|
+
const id = `${this.requestIdPrefix}-${++this.requestCounter}`;
|
|
1542
1931
|
return new Promise((resolve, reject) => {
|
|
1543
1932
|
const pending = {
|
|
1544
1933
|
id,
|
|
@@ -1619,7 +2008,7 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
1619
2008
|
/**
|
|
1620
2009
|
* Disconnect from wallet and clean up. Returns once the TERMINATE has
|
|
1621
2010
|
* either been flushed to the relay or the 800ms best-effort window has
|
|
1622
|
-
* elapsed
|
|
2011
|
+
* elapsed - the wallet gets an instant disconnect instead of landing in
|
|
1623
2012
|
* its stale-session grace period.
|
|
1624
2013
|
*/
|
|
1625
2014
|
async disconnect() {
|
|
@@ -1627,9 +2016,373 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
1627
2016
|
pending.reject(new Error("Disconnected"));
|
|
1628
2017
|
}
|
|
1629
2018
|
this.pendingRequests.clear();
|
|
2019
|
+
this.teardownResumeListeners();
|
|
1630
2020
|
await this.connectionManager.disconnect();
|
|
1631
2021
|
}
|
|
1632
2022
|
};
|
|
2023
|
+
|
|
2024
|
+
// src/signing/ctx.ts
|
|
2025
|
+
var SCHEME_VERSION_MSG = "QRL-SIGN-MSG-v1";
|
|
2026
|
+
var SCHEME_VERSION_TYPED = "QRL-SIGN-TYPED-v1";
|
|
2027
|
+
var SCHEME_TAG_MSG = new TextEncoder().encode(SCHEME_VERSION_MSG);
|
|
2028
|
+
var SCHEME_TAG_TYPED = new TextEncoder().encode(SCHEME_VERSION_TYPED);
|
|
2029
|
+
var DIGEST_LEN = 64;
|
|
2030
|
+
|
|
2031
|
+
// src/signing/bytes.ts
|
|
2032
|
+
function hexToBytes(hex) {
|
|
2033
|
+
if (typeof hex !== "string") throw new Error("hex must be a string");
|
|
2034
|
+
if (!/^0x([0-9a-fA-F]{2})*$/.test(hex)) {
|
|
2035
|
+
throw new Error("hex must be 0x-prefixed even-length");
|
|
2036
|
+
}
|
|
2037
|
+
const n = (hex.length - 2) / 2;
|
|
2038
|
+
const out = new Uint8Array(n);
|
|
2039
|
+
for (let i = 0; i < n; i++) {
|
|
2040
|
+
out[i] = parseInt(hex.slice(2 + i * 2, 4 + i * 2), 16);
|
|
2041
|
+
}
|
|
2042
|
+
return out;
|
|
2043
|
+
}
|
|
2044
|
+
function bytesToHex(bytes) {
|
|
2045
|
+
if (!(bytes instanceof Uint8Array)) {
|
|
2046
|
+
throw new Error("bytes must be a Uint8Array");
|
|
2047
|
+
}
|
|
2048
|
+
let s = "0x";
|
|
2049
|
+
for (const v of bytes) s += v.toString(16).padStart(2, "0");
|
|
2050
|
+
return s;
|
|
2051
|
+
}
|
|
2052
|
+
function concatBytes(...parts) {
|
|
2053
|
+
return concatBytesArr(parts);
|
|
2054
|
+
}
|
|
2055
|
+
function concatBytesArr(parts) {
|
|
2056
|
+
let total = 0;
|
|
2057
|
+
for (const p of parts) total += p.length;
|
|
2058
|
+
const out = new Uint8Array(total);
|
|
2059
|
+
let off = 0;
|
|
2060
|
+
for (const p of parts) {
|
|
2061
|
+
out.set(p, off);
|
|
2062
|
+
off += p.length;
|
|
2063
|
+
}
|
|
2064
|
+
return out;
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
// src/signing/messageDigest.ts
|
|
2068
|
+
function computeMessageDigest(messageBytes) {
|
|
2069
|
+
if (!(messageBytes instanceof Uint8Array)) {
|
|
2070
|
+
throw new Error("messageBytes must be a Uint8Array");
|
|
2071
|
+
}
|
|
2072
|
+
return shake256Digest(concatBytes(SCHEME_TAG_MSG, messageBytes), DIGEST_LEN);
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
// src/signing/typedData.ts
|
|
2076
|
+
var SLOT = 32;
|
|
2077
|
+
function isMessageObject(v) {
|
|
2078
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2079
|
+
}
|
|
2080
|
+
var ATOMIC_RE = /^(?:(address|bool|string|bytes)|(u?int)(\d+)|bytes(\d+)|(.+?)\[(\d*)\])$/;
|
|
2081
|
+
var MAX_TYPE_DEPTH = 12;
|
|
2082
|
+
function parseFieldType(type, types, depth = 0) {
|
|
2083
|
+
if (depth > MAX_TYPE_DEPTH) throw new Error(`type nesting too deep: ${type}`);
|
|
2084
|
+
if (Object.prototype.hasOwnProperty.call(types, type)) {
|
|
2085
|
+
return { kind: "ref", name: type };
|
|
2086
|
+
}
|
|
2087
|
+
const m = ATOMIC_RE.exec(type);
|
|
2088
|
+
if (!m) throw new Error(`unknown type: ${type}`);
|
|
2089
|
+
const [, atomic, intKind, intWidthStr, bytesWidthStr, innerType, sizeStr] = m;
|
|
2090
|
+
if (atomic === "address" || atomic === "bool" || atomic === "string" || atomic === "bytes") {
|
|
2091
|
+
return { kind: atomic };
|
|
2092
|
+
}
|
|
2093
|
+
if (intKind) {
|
|
2094
|
+
const width = Number(intWidthStr);
|
|
2095
|
+
if (!Number.isInteger(width) || width < 8 || width > 256 || width % 8 !== 0) {
|
|
2096
|
+
throw new Error(`invalid int width: ${type}`);
|
|
2097
|
+
}
|
|
2098
|
+
return { kind: intKind === "uint" ? "uintN" : "intN", width };
|
|
2099
|
+
}
|
|
2100
|
+
if (bytesWidthStr) {
|
|
2101
|
+
const width = Number(bytesWidthStr);
|
|
2102
|
+
if (!Number.isInteger(width) || width < 1 || width > 32) {
|
|
2103
|
+
throw new Error(`invalid bytesN width: ${type}`);
|
|
2104
|
+
}
|
|
2105
|
+
return { kind: "bytesN", width };
|
|
2106
|
+
}
|
|
2107
|
+
if (innerType !== void 0) {
|
|
2108
|
+
parseFieldType(innerType, types, depth + 1);
|
|
2109
|
+
if (sizeStr) {
|
|
2110
|
+
const size = Number(sizeStr);
|
|
2111
|
+
if (!Number.isInteger(size) || size <= 0) {
|
|
2112
|
+
throw new Error(`invalid array size: ${type}`);
|
|
2113
|
+
}
|
|
2114
|
+
return { kind: "array", inner: innerType, size };
|
|
2115
|
+
}
|
|
2116
|
+
return { kind: "array", inner: innerType };
|
|
2117
|
+
}
|
|
2118
|
+
throw new Error(`unhandled type: ${type}`);
|
|
2119
|
+
}
|
|
2120
|
+
function baseTypeName(type) {
|
|
2121
|
+
return type.replace(/(\[\d*\])+$/, "");
|
|
2122
|
+
}
|
|
2123
|
+
function collectDependencies(primary, types) {
|
|
2124
|
+
if (!Object.prototype.hasOwnProperty.call(types, primary)) {
|
|
2125
|
+
throw new Error(`unknown primary type: ${primary}`);
|
|
2126
|
+
}
|
|
2127
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2128
|
+
const visit = (name, path) => {
|
|
2129
|
+
if (path.includes(name)) {
|
|
2130
|
+
throw new Error(`cyclic type reference: ${[...path, name].join(" -> ")}`);
|
|
2131
|
+
}
|
|
2132
|
+
if (visited.has(name)) return;
|
|
2133
|
+
visited.add(name);
|
|
2134
|
+
const def = types[name];
|
|
2135
|
+
if (!def) throw new Error(`unknown type: ${name}`);
|
|
2136
|
+
for (const f of def) {
|
|
2137
|
+
const base = baseTypeName(f.type);
|
|
2138
|
+
if (Object.prototype.hasOwnProperty.call(types, base)) {
|
|
2139
|
+
visit(base, [...path, name]);
|
|
2140
|
+
}
|
|
2141
|
+
parseFieldType(f.type, types);
|
|
2142
|
+
}
|
|
2143
|
+
};
|
|
2144
|
+
visit(primary, []);
|
|
2145
|
+
return visited;
|
|
2146
|
+
}
|
|
2147
|
+
function validateTypeMap(types) {
|
|
2148
|
+
for (const [name, def] of Object.entries(types)) {
|
|
2149
|
+
const defUnknown = def;
|
|
2150
|
+
if (!Array.isArray(defUnknown) || def.length === 0) {
|
|
2151
|
+
throw new Error(`empty or invalid struct: ${name}`);
|
|
2152
|
+
}
|
|
2153
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2154
|
+
for (const f of def) {
|
|
2155
|
+
if (!f || typeof f.name !== "string" || !f.name) {
|
|
2156
|
+
throw new Error(`bad field in ${name}`);
|
|
2157
|
+
}
|
|
2158
|
+
if (typeof f.type !== "string" || !f.type) {
|
|
2159
|
+
throw new Error(`bad field type in ${name}.${f.name}`);
|
|
2160
|
+
}
|
|
2161
|
+
if (seen.has(f.name)) {
|
|
2162
|
+
throw new Error(`duplicate field "${f.name}" in ${name}`);
|
|
2163
|
+
}
|
|
2164
|
+
seen.add(f.name);
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
function encodeType(primary, types) {
|
|
2169
|
+
const deps = collectDependencies(primary, types);
|
|
2170
|
+
const others = [...deps].filter((n) => n !== primary).sort();
|
|
2171
|
+
return [primary, ...others].map((name) => {
|
|
2172
|
+
const fields = types[name];
|
|
2173
|
+
if (!fields) throw new Error(`unknown type: ${name}`);
|
|
2174
|
+
const inner = fields.map((f) => `${f.type} ${f.name}`).join(",");
|
|
2175
|
+
return `${name}(${inner})`;
|
|
2176
|
+
}).join("");
|
|
2177
|
+
}
|
|
2178
|
+
function typeHash(primary, types) {
|
|
2179
|
+
return shake256Digest(new TextEncoder().encode(encodeType(primary, types)), DIGEST_LEN);
|
|
2180
|
+
}
|
|
2181
|
+
function parseQAddress(addr) {
|
|
2182
|
+
if (typeof addr !== "string" || !/^Q[0-9a-fA-F]{40}$/.test(addr)) {
|
|
2183
|
+
throw new Error(`invalid Q-address: ${addr}`);
|
|
2184
|
+
}
|
|
2185
|
+
return hexToBytes("0x" + addr.slice(1).toLowerCase());
|
|
2186
|
+
}
|
|
2187
|
+
function padLeft32(bytes) {
|
|
2188
|
+
if (bytes.length > SLOT) throw new Error("cannot pad: bytes > 32");
|
|
2189
|
+
const out = new Uint8Array(SLOT);
|
|
2190
|
+
out.set(bytes, SLOT - bytes.length);
|
|
2191
|
+
return out;
|
|
2192
|
+
}
|
|
2193
|
+
function padRight32(bytes) {
|
|
2194
|
+
if (bytes.length > SLOT) throw new Error("cannot pad: bytes > 32");
|
|
2195
|
+
const out = new Uint8Array(SLOT);
|
|
2196
|
+
out.set(bytes, 0);
|
|
2197
|
+
return out;
|
|
2198
|
+
}
|
|
2199
|
+
function bigIntToSlot(value, width, signed) {
|
|
2200
|
+
if (signed) {
|
|
2201
|
+
const limit = 1n << BigInt(width - 1);
|
|
2202
|
+
if (value >= limit || value < -limit) {
|
|
2203
|
+
throw new Error(`int${width} out of range: ${value}`);
|
|
2204
|
+
}
|
|
2205
|
+
} else {
|
|
2206
|
+
if (value < 0n) throw new Error(`uint${width} negative: ${value}`);
|
|
2207
|
+
const limit = 1n << BigInt(width);
|
|
2208
|
+
if (value >= limit) throw new Error(`uint${width} out of range: ${value}`);
|
|
2209
|
+
}
|
|
2210
|
+
const slotMask = (1n << 256n) - 1n;
|
|
2211
|
+
const repr = value < 0n ? value + (1n << 256n) & slotMask : value;
|
|
2212
|
+
const out = new Uint8Array(SLOT);
|
|
2213
|
+
let v = repr;
|
|
2214
|
+
for (let i = SLOT - 1; i >= 0; i--) {
|
|
2215
|
+
out[i] = Number(v & 0xffn);
|
|
2216
|
+
v >>= 8n;
|
|
2217
|
+
}
|
|
2218
|
+
return out;
|
|
2219
|
+
}
|
|
2220
|
+
function parseIntValue(v, typeLabel) {
|
|
2221
|
+
if (typeof v === "bigint") return v;
|
|
2222
|
+
if (typeof v === "string") {
|
|
2223
|
+
if (/^-?0x[0-9a-fA-F]+$/i.test(v)) {
|
|
2224
|
+
const isNegative = v.startsWith("-");
|
|
2225
|
+
const abs = BigInt(isNegative ? v.slice(1) : v);
|
|
2226
|
+
return isNegative ? -abs : abs;
|
|
2227
|
+
}
|
|
2228
|
+
if (!/^-?(0|[1-9]\d*)$/.test(v)) {
|
|
2229
|
+
throw new Error(`invalid ${typeLabel} string: ${v}`);
|
|
2230
|
+
}
|
|
2231
|
+
return BigInt(v);
|
|
2232
|
+
}
|
|
2233
|
+
if (typeof v === "number") {
|
|
2234
|
+
if (!Number.isInteger(v) || !Number.isSafeInteger(v)) {
|
|
2235
|
+
throw new Error(`unsafe ${typeLabel} number: ${v}, pass as string`);
|
|
2236
|
+
}
|
|
2237
|
+
return BigInt(v);
|
|
2238
|
+
}
|
|
2239
|
+
throw new Error(`unsupported ${typeLabel} value: ${typeof v}`);
|
|
2240
|
+
}
|
|
2241
|
+
function encodeField(type, value, types) {
|
|
2242
|
+
const parsed = parseFieldType(type, types);
|
|
2243
|
+
switch (parsed.kind) {
|
|
2244
|
+
case "ref":
|
|
2245
|
+
if (!isMessageObject(value)) {
|
|
2246
|
+
throw new Error(`struct field expects object: ${type}`);
|
|
2247
|
+
}
|
|
2248
|
+
return hashStruct(parsed.name, value, types);
|
|
2249
|
+
case "address":
|
|
2250
|
+
if (typeof value !== "string") {
|
|
2251
|
+
throw new Error(`address field expects string: ${typeof value}`);
|
|
2252
|
+
}
|
|
2253
|
+
return padLeft32(parseQAddress(value));
|
|
2254
|
+
case "bool":
|
|
2255
|
+
if (typeof value !== "boolean") {
|
|
2256
|
+
throw new Error(`bool field expects boolean: ${typeof value}`);
|
|
2257
|
+
}
|
|
2258
|
+
return padLeft32(new Uint8Array([value ? 1 : 0]));
|
|
2259
|
+
case "string":
|
|
2260
|
+
if (typeof value !== "string") {
|
|
2261
|
+
throw new Error(`string field expects string: ${typeof value}`);
|
|
2262
|
+
}
|
|
2263
|
+
return shake256Digest(new TextEncoder().encode(value), DIGEST_LEN);
|
|
2264
|
+
case "bytes":
|
|
2265
|
+
if (typeof value !== "string") {
|
|
2266
|
+
throw new Error(`bytes field expects 0x-hex string: ${typeof value}`);
|
|
2267
|
+
}
|
|
2268
|
+
return shake256Digest(hexToBytes(value), DIGEST_LEN);
|
|
2269
|
+
case "uintN":
|
|
2270
|
+
return bigIntToSlot(parseIntValue(value, `uint${parsed.width}`), parsed.width, false);
|
|
2271
|
+
case "intN":
|
|
2272
|
+
return bigIntToSlot(parseIntValue(value, `int${parsed.width}`), parsed.width, true);
|
|
2273
|
+
case "bytesN": {
|
|
2274
|
+
if (typeof value !== "string") {
|
|
2275
|
+
throw new Error(`bytes${parsed.width} expects 0x-hex string: ${typeof value}`);
|
|
2276
|
+
}
|
|
2277
|
+
const raw = hexToBytes(value);
|
|
2278
|
+
if (raw.length !== parsed.width) {
|
|
2279
|
+
throw new Error(`bytes${parsed.width} requires ${parsed.width} bytes, got ${raw.length}`);
|
|
2280
|
+
}
|
|
2281
|
+
return padRight32(raw);
|
|
2282
|
+
}
|
|
2283
|
+
case "array": {
|
|
2284
|
+
if (!Array.isArray(value)) {
|
|
2285
|
+
throw new Error(`array field expects array: ${typeof value}`);
|
|
2286
|
+
}
|
|
2287
|
+
if (parsed.size !== void 0 && value.length !== parsed.size) {
|
|
2288
|
+
throw new Error(`fixed array ${type} requires length ${parsed.size}, got ${value.length}`);
|
|
2289
|
+
}
|
|
2290
|
+
const chunks = value.map((v) => encodeField(parsed.inner, v, types));
|
|
2291
|
+
return shake256Digest(concatBytesArr(chunks), DIGEST_LEN);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
function hashStruct(primary, data, types) {
|
|
2296
|
+
const fields = types[primary];
|
|
2297
|
+
if (!fields) throw new Error(`unknown struct: ${primary}`);
|
|
2298
|
+
const expected = new Set(fields.map((f) => f.name));
|
|
2299
|
+
for (const k of Object.keys(data)) {
|
|
2300
|
+
if (!expected.has(k)) throw new Error(`unknown field in ${primary}: ${k}`);
|
|
2301
|
+
}
|
|
2302
|
+
const parts = [typeHash(primary, types)];
|
|
2303
|
+
for (const f of fields) {
|
|
2304
|
+
if (!(f.name in data)) throw new Error(`missing field ${primary}.${f.name}`);
|
|
2305
|
+
parts.push(encodeField(f.type, data[f.name], types));
|
|
2306
|
+
}
|
|
2307
|
+
return shake256Digest(concatBytesArr(parts), DIGEST_LEN);
|
|
2308
|
+
}
|
|
2309
|
+
var RESERVED_DOMAIN_FIELDS = {
|
|
2310
|
+
name: "string",
|
|
2311
|
+
version: "string",
|
|
2312
|
+
chainId: "uint256",
|
|
2313
|
+
verifyingContract: "address",
|
|
2314
|
+
salt: "bytes32"
|
|
2315
|
+
};
|
|
2316
|
+
function validateDomainTypes(types) {
|
|
2317
|
+
const def = types.QRLDomain;
|
|
2318
|
+
if (!def) throw new Error("QRLDomain type is required");
|
|
2319
|
+
let hasName = false;
|
|
2320
|
+
for (const f of def) {
|
|
2321
|
+
const expected = RESERVED_DOMAIN_FIELDS[f.name];
|
|
2322
|
+
if (!expected) {
|
|
2323
|
+
throw new Error(
|
|
2324
|
+
`QRLDomain field "${f.name}" not in reserved set (name, version, chainId, verifyingContract, salt)`
|
|
2325
|
+
);
|
|
2326
|
+
}
|
|
2327
|
+
if (f.type !== expected) {
|
|
2328
|
+
throw new Error(`QRLDomain field "${f.name}" must be type "${expected}", got "${f.type}"`);
|
|
2329
|
+
}
|
|
2330
|
+
if (f.name === "name") hasName = true;
|
|
2331
|
+
}
|
|
2332
|
+
if (!hasName) throw new Error("QRLDomain.name is required");
|
|
2333
|
+
}
|
|
2334
|
+
function validatePayloadReachability(primary, types) {
|
|
2335
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
2336
|
+
for (const root of ["QRLDomain", primary]) {
|
|
2337
|
+
for (const t of collectDependencies(root, types)) reachable.add(t);
|
|
2338
|
+
}
|
|
2339
|
+
for (const k of Object.keys(types)) {
|
|
2340
|
+
if (!reachable.has(k)) throw new Error(`unused referenced type: ${k}`);
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
function computeTypedDataDigest(payload) {
|
|
2344
|
+
if (!payload || typeof payload !== "object") {
|
|
2345
|
+
throw new Error("invalid typed data payload");
|
|
2346
|
+
}
|
|
2347
|
+
validateTypeMap(payload.types);
|
|
2348
|
+
validateDomainTypes(payload.types);
|
|
2349
|
+
validatePayloadReachability(payload.primaryType, payload.types);
|
|
2350
|
+
const domainHash = hashStruct("QRLDomain", payload.domain, payload.types);
|
|
2351
|
+
const messageHash = hashStruct(payload.primaryType, payload.message, payload.types);
|
|
2352
|
+
return shake256Digest(concatBytes(SCHEME_TAG_TYPED, domainHash, messageHash), DIGEST_LEN);
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
// src/signing/verify.ts
|
|
2356
|
+
function bytesOrHex(v) {
|
|
2357
|
+
if (v instanceof Uint8Array) return v;
|
|
2358
|
+
if (typeof v === "string") return hexToBytes(v);
|
|
2359
|
+
throw new Error("expected Uint8Array or 0x-hex string");
|
|
2360
|
+
}
|
|
2361
|
+
function verifyMessage({
|
|
2362
|
+
signature,
|
|
2363
|
+
publicKey,
|
|
2364
|
+
messageBytes
|
|
2365
|
+
}) {
|
|
2366
|
+
try {
|
|
2367
|
+
const sig = bytesOrHex(signature);
|
|
2368
|
+
const pk = bytesOrHex(publicKey);
|
|
2369
|
+
const msg = bytesOrHex(messageBytes);
|
|
2370
|
+
const digest = computeMessageDigest(msg);
|
|
2371
|
+
return mldsaVerify(sig, digest, pk, SCHEME_TAG_MSG);
|
|
2372
|
+
} catch {
|
|
2373
|
+
return false;
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
function verifyTypedData({ signature, publicKey, payload }) {
|
|
2377
|
+
try {
|
|
2378
|
+
const sig = bytesOrHex(signature);
|
|
2379
|
+
const pk = bytesOrHex(publicKey);
|
|
2380
|
+
const digest = computeTypedDataDigest(payload);
|
|
2381
|
+
return mldsaVerify(sig, digest, pk, SCHEME_TAG_TYPED);
|
|
2382
|
+
} catch {
|
|
2383
|
+
return false;
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
1633
2386
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1634
2387
|
0 && (module.exports = {
|
|
1635
2388
|
BLOB_LEN,
|
|
@@ -1637,6 +2390,7 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
1637
2390
|
ConnectionManager,
|
|
1638
2391
|
ConnectionStatus,
|
|
1639
2392
|
DEFAULT_RELAY_URL,
|
|
2393
|
+
DIGEST_LEN,
|
|
1640
2394
|
FP_LEN,
|
|
1641
2395
|
KeyExchange,
|
|
1642
2396
|
KeyExchangeMessageType,
|
|
@@ -1644,16 +2398,32 @@ var QRLConnectProvider = class extends import_eventemitter34.default {
|
|
|
1644
2398
|
PROTOCOL_VERSION,
|
|
1645
2399
|
QRLConnect,
|
|
1646
2400
|
QRLConnectProvider,
|
|
2401
|
+
QRL_CONNECT_PROVIDER_INFO,
|
|
1647
2402
|
RESTRICTED_METHODS,
|
|
2403
|
+
SCHEME_TAG_MSG,
|
|
2404
|
+
SCHEME_TAG_TYPED,
|
|
2405
|
+
SCHEME_VERSION_MSG,
|
|
2406
|
+
SCHEME_VERSION_TYPED,
|
|
1648
2407
|
SocketClient,
|
|
1649
2408
|
UNRESTRICTED_METHODS,
|
|
2409
|
+
bytesToHex,
|
|
1650
2410
|
cidFromString,
|
|
1651
2411
|
cidRandom,
|
|
1652
2412
|
cidToString,
|
|
1653
2413
|
computeFingerprint,
|
|
2414
|
+
computeMessageDigest,
|
|
2415
|
+
computeTypedDataDigest,
|
|
2416
|
+
concatBytes,
|
|
2417
|
+
encodeField,
|
|
2418
|
+
encodeType,
|
|
1654
2419
|
fingerprintEquals,
|
|
1655
2420
|
generateConnectionURI,
|
|
1656
2421
|
getAppStoreUrl,
|
|
2422
|
+
hashStruct,
|
|
2423
|
+
hexToBytes,
|
|
1657
2424
|
isMobileBrowser,
|
|
1658
|
-
parseConnectionURI
|
|
2425
|
+
parseConnectionURI,
|
|
2426
|
+
typeHash,
|
|
2427
|
+
verifyMessage,
|
|
2428
|
+
verifyTypedData
|
|
1659
2429
|
});
|