@openlv/session 0.0.1-beta.2 → 0.0.1-beta.3

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.
Files changed (82) hide show
  1. package/package.json +3 -3
  2. package/dist/connection.d.ts +0 -57
  3. package/dist/connection.js +0 -757
  4. package/dist/connection.js.map +0 -1
  5. package/dist/encryption/asymmetric.d.ts +0 -22
  6. package/dist/encryption/asymmetric.js +0 -90
  7. package/dist/encryption/asymmetric.js.map +0 -1
  8. package/dist/encryption/asymmetric.test.d.ts +0 -1
  9. package/dist/encryption/asymmetric.test.js +0 -38
  10. package/dist/encryption/asymmetric.test.js.map +0 -1
  11. package/dist/encryption/base64.d.ts +0 -2
  12. package/dist/encryption/base64.js +0 -16
  13. package/dist/encryption/base64.js.map +0 -1
  14. package/dist/encryption/base64.test.d.ts +0 -1
  15. package/dist/encryption/base64.test.js +0 -21
  16. package/dist/encryption/base64.test.js.map +0 -1
  17. package/dist/encryption/handshake.d.ts +0 -10
  18. package/dist/encryption/handshake.js +0 -37
  19. package/dist/encryption/handshake.js.map +0 -1
  20. package/dist/encryption/handshake.test.d.ts +0 -1
  21. package/dist/encryption/handshake.test.js +0 -16
  22. package/dist/encryption/handshake.test.js.map +0 -1
  23. package/dist/encryption/hash.d.ts +0 -13
  24. package/dist/encryption/hash.js +0 -32
  25. package/dist/encryption/hash.js.map +0 -1
  26. package/dist/encryption/index.d.ts +0 -4
  27. package/dist/encryption/index.js +0 -5
  28. package/dist/encryption/index.js.map +0 -1
  29. package/dist/encryption/random.d.ts +0 -8
  30. package/dist/encryption/random.js +0 -21
  31. package/dist/encryption/random.js.map +0 -1
  32. package/dist/provider.d.ts +0 -11
  33. package/dist/provider.js +0 -285
  34. package/dist/provider.js.map +0 -1
  35. package/dist/session/index.d.ts +0 -41
  36. package/dist/session/index.js +0 -99
  37. package/dist/session/index.js.map +0 -1
  38. package/dist/session/index.test.d.ts +0 -1
  39. package/dist/session/index.test.js +0 -32
  40. package/dist/session/index.test.js.map +0 -1
  41. package/dist/signaling/base.d.ts +0 -14
  42. package/dist/signaling/base.js +0 -2
  43. package/dist/signaling/base.js.map +0 -1
  44. package/dist/signaling/index.d.ts +0 -28
  45. package/dist/signaling/index.js +0 -152
  46. package/dist/signaling/index.js.map +0 -1
  47. package/dist/signaling/index.test.d.ts +0 -1
  48. package/dist/signaling/index.test.js +0 -62
  49. package/dist/signaling/index.test.js.map +0 -1
  50. package/dist/signaling/messages/index.d.ts +0 -48
  51. package/dist/signaling/messages/index.js +0 -2
  52. package/dist/signaling/messages/index.js.map +0 -1
  53. package/dist/signaling/mqtt/index.d.ts +0 -5
  54. package/dist/signaling/mqtt/index.js +0 -49
  55. package/dist/signaling/mqtt/index.js.map +0 -1
  56. package/dist/signaling/mqtt.d.ts +0 -31
  57. package/dist/signaling/mqtt.js +0 -152
  58. package/dist/signaling/mqtt.js.map +0 -1
  59. package/dist/signaling/ntfy/index.d.ts +0 -25
  60. package/dist/signaling/ntfy/index.js +0 -92
  61. package/dist/signaling/ntfy/index.js.map +0 -1
  62. package/dist/signaling/ntfy/url.d.ts +0 -12
  63. package/dist/signaling/ntfy/url.js +0 -37
  64. package/dist/signaling/ntfy/url.js.map +0 -1
  65. package/dist/signaling/ntfy/url.test.d.ts +0 -1
  66. package/dist/signaling/ntfy/url.test.js +0 -54
  67. package/dist/signaling/ntfy/url.test.js.map +0 -1
  68. package/dist/transport/index.d.ts +0 -11
  69. package/dist/transport/index.js +0 -8
  70. package/dist/transport/index.js.map +0 -1
  71. package/dist/transport/webrtc/index.d.ts +0 -1
  72. package/dist/transport/webrtc/index.js +0 -70
  73. package/dist/transport/webrtc/index.js.map +0 -1
  74. package/dist/utils/encryption.d.ts +0 -86
  75. package/dist/utils/encryption.js +0 -243
  76. package/dist/utils/encryption.js.map +0 -1
  77. package/dist/utils/url.d.ts +0 -3
  78. package/dist/utils/url.js +0 -66
  79. package/dist/utils/url.js.map +0 -1
  80. package/dist/utils/url.test.d.ts +0 -1
  81. package/dist/utils/url.test.js +0 -29
  82. package/dist/utils/url.test.js.map +0 -1
@@ -1,757 +0,0 @@
1
- /* eslint-disable sonarjs/cognitive-complexity */
2
- /* eslint-disable sonarjs/no-duplicate-string */
3
- import { MQTTSignaling } from './signaling/mqtt.js';
4
- import { EncryptionUtils } from './utils/encryption.js';
5
- export const encodeConnectionURL = (payload) => {
6
- const params = new URLSearchParams();
7
- params.set('h', payload.pubkeyHash);
8
- params.set('k', payload.sharedKey);
9
- if (payload.server) {
10
- params.set('s', payload.server);
11
- }
12
- if (payload.protocol && payload.protocol !== 'mqtt') {
13
- params.set('p', payload.protocol);
14
- }
15
- return `openlv://${payload.sessionId}?${params.toString()}`;
16
- };
17
- export const decodeConnectionURL = (url) => {
18
- // Type check and validation
19
- if (typeof url !== 'string') {
20
- throw new Error(`Invalid URL type: expected string, got ${typeof url}`);
21
- }
22
- if (!url || url.trim() === '') {
23
- throw new Error('URL cannot be empty');
24
- }
25
- if (!url.startsWith('openlv://')) {
26
- throw new Error(`Invalid URL format: must start with 'openlv://', got: ${url}`);
27
- }
28
- try {
29
- const urlObj = new URL(url);
30
- const sessionId = urlObj.hostname || urlObj.pathname.replace('/', '');
31
- const pubkeyHash = urlObj.searchParams.get('h') || '';
32
- const sharedKey = urlObj.searchParams.get('k') || '';
33
- const server = urlObj.searchParams.get('s') || undefined;
34
- const protocol = urlObj.searchParams.get('p') || 'mqtt';
35
- if (!sessionId) {
36
- throw new Error('Session ID is required in URL');
37
- }
38
- // Validate session ID format (16 characters, URL-safe alphabet)
39
- if (!/^[A-Za-z0-9_-]{16}$/.test(sessionId)) {
40
- throw new Error('Invalid session ID format: must be 16 URL-safe characters');
41
- }
42
- if (!pubkeyHash) {
43
- throw new Error('Public key hash (h parameter) is required in URL');
44
- }
45
- // Validate public key hash format (16 hex characters)
46
- if (!/^[0-9a-f]{16}$/.test(pubkeyHash)) {
47
- throw new Error('Invalid public key hash format: must be 16 hex characters');
48
- }
49
- if (!sharedKey) {
50
- throw new Error('Shared key (k parameter) is required in URL');
51
- }
52
- // Validate shared key format (64 hex characters)
53
- if (!/^[0-9a-f]{64}$/.test(sharedKey)) {
54
- throw new Error('Invalid shared key format: must be 64 hex characters');
55
- }
56
- return {
57
- sessionId,
58
- pubkeyHash,
59
- sharedKey,
60
- server: server ? decodeURIComponent(server) : undefined,
61
- protocol,
62
- };
63
- }
64
- catch (error) {
65
- // If it's already our custom error, re-throw it
66
- if (error instanceof Error && error.message.includes('required')) {
67
- throw error;
68
- }
69
- // Fallback to legacy regex parsing
70
- try {
71
- const legacyMatch = url.match(/openlv:\/\/([^?]+)\?(?:sharedKey|k|h)=([^&]+)/) ?? [];
72
- const [, sessionId, keyOrHash] = legacyMatch;
73
- if (!sessionId || !keyOrHash) {
74
- throw new Error(`Invalid URL format: could not parse session ID or key from ${url}`);
75
- }
76
- return {
77
- sessionId,
78
- pubkeyHash: keyOrHash, // Assume it's pubkeyHash for legacy
79
- sharedKey: keyOrHash, // Use same value for both in legacy mode
80
- };
81
- }
82
- catch {
83
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
84
- throw new Error(`Failed to parse URL: ${url}. Original error: ${errorMessage}`);
85
- }
86
- }
87
- };
88
- export class OpenLVConnection {
89
- constructor(config) {
90
- Object.defineProperty(this, "sessionId", {
91
- enumerable: true,
92
- configurable: true,
93
- writable: true,
94
- value: void 0
95
- });
96
- Object.defineProperty(this, "pubkeyHash", {
97
- enumerable: true,
98
- configurable: true,
99
- writable: true,
100
- value: void 0
101
- });
102
- Object.defineProperty(this, "sharedKey", {
103
- enumerable: true,
104
- configurable: true,
105
- writable: true,
106
- value: void 0
107
- });
108
- Object.defineProperty(this, "privateKey", {
109
- enumerable: true,
110
- configurable: true,
111
- writable: true,
112
- value: void 0
113
- });
114
- Object.defineProperty(this, "publicKey", {
115
- enumerable: true,
116
- configurable: true,
117
- writable: true,
118
- value: void 0
119
- });
120
- Object.defineProperty(this, "peerPublicKey", {
121
- enumerable: true,
122
- configurable: true,
123
- writable: true,
124
- value: void 0
125
- });
126
- Object.defineProperty(this, "peerConnection", {
127
- enumerable: true,
128
- configurable: true,
129
- writable: true,
130
- value: void 0
131
- });
132
- Object.defineProperty(this, "dataChannel", {
133
- enumerable: true,
134
- configurable: true,
135
- writable: true,
136
- value: void 0
137
- });
138
- Object.defineProperty(this, "signaling", {
139
- enumerable: true,
140
- configurable: true,
141
- writable: true,
142
- value: void 0
143
- });
144
- Object.defineProperty(this, "isInitiator", {
145
- enumerable: true,
146
- configurable: true,
147
- writable: true,
148
- value: false
149
- });
150
- Object.defineProperty(this, "connectionState", {
151
- enumerable: true,
152
- configurable: true,
153
- writable: true,
154
- value: 'disconnected'
155
- });
156
- Object.defineProperty(this, "pendingICECandidates", {
157
- enumerable: true,
158
- configurable: true,
159
- writable: true,
160
- value: []
161
- });
162
- Object.defineProperty(this, "webrtcRetryCount", {
163
- enumerable: true,
164
- configurable: true,
165
- writable: true,
166
- value: 0
167
- });
168
- Object.defineProperty(this, "maxWebrtcRetries", {
169
- enumerable: true,
170
- configurable: true,
171
- writable: true,
172
- value: 3
173
- });
174
- Object.defineProperty(this, "webrtcRetryInterval", {
175
- enumerable: true,
176
- configurable: true,
177
- writable: true,
178
- value: void 0
179
- });
180
- Object.defineProperty(this, "connectionTimeout", {
181
- enumerable: true,
182
- configurable: true,
183
- writable: true,
184
- value: void 0
185
- });
186
- // Event handlers
187
- Object.defineProperty(this, "phaseHandlers", {
188
- enumerable: true,
189
- configurable: true,
190
- writable: true,
191
- value: []
192
- });
193
- Object.defineProperty(this, "messageHandlers", {
194
- enumerable: true,
195
- configurable: true,
196
- writable: true,
197
- value: []
198
- });
199
- Object.defineProperty(this, "errorHandlers", {
200
- enumerable: true,
201
- configurable: true,
202
- writable: true,
203
- value: []
204
- });
205
- // Connection state flags
206
- Object.defineProperty(this, "hasSharedPublicKey", {
207
- enumerable: true,
208
- configurable: true,
209
- writable: true,
210
- value: false
211
- });
212
- // Peer identification
213
- Object.defineProperty(this, "peerId", {
214
- enumerable: true,
215
- configurable: true,
216
- writable: true,
217
- value: void 0
218
- });
219
- this.signaling = new MQTTSignaling(config);
220
- this.peerId = EncryptionUtils.generateSessionId(); // Unique identifier for this peer
221
- }
222
- updateConnectionState(newState, description) {
223
- const oldState = this.connectionState;
224
- this.connectionState = newState;
225
- console.log(`Connection: ${oldState} → ${newState} (${description})`);
226
- const phase = {
227
- state: newState,
228
- description,
229
- timestamp: Date.now(),
230
- };
231
- // Notify phase handlers
232
- this.phaseHandlers.forEach((handler) => handler(phase));
233
- // Handle state transitions
234
- if (newState === 'webrtc-connected' && oldState !== 'webrtc-connected') {
235
- this.onWebRTCConnected();
236
- }
237
- else if (oldState === 'webrtc-connected' && newState !== 'webrtc-connected') {
238
- this.onWebRTCDisconnected();
239
- }
240
- }
241
- onWebRTCConnected() {
242
- console.log('WebRTC connected - closing MQTT to save resources');
243
- this.clearTimeouts();
244
- this.signaling.disconnect();
245
- }
246
- onWebRTCDisconnected() {
247
- console.log('WebRTC disconnected - reopening MQTT for fallback');
248
- this.signaling.reconnect();
249
- }
250
- async handleSignalingMessage(message) {
251
- try {
252
- console.log(`Processing: ${message.type} (initiator: ${this.isInitiator})`);
253
- // Ignore our own messages
254
- if (message.senderId === this.peerId) {
255
- console.log(`Ignoring own message: ${message.type}`);
256
- return;
257
- }
258
- switch (message.type) {
259
- case 'ping':
260
- await this.handlePing();
261
- break;
262
- case 'pubkey':
263
- await this.handlePublicKey(message);
264
- break;
265
- case 'hello':
266
- await this.handleHello(message);
267
- break;
268
- case 'webrtc-offer':
269
- await this.handleWebRTCOffer(message);
270
- break;
271
- case 'webrtc-answer':
272
- await this.handleWebRTCAnswer(message);
273
- break;
274
- case 'ice-candidate':
275
- await this.handleICECandidate(message);
276
- break;
277
- case 'data':
278
- await this.handleDataMessage(message);
279
- break;
280
- }
281
- }
282
- catch (error) {
283
- console.error('Error handling signaling message:', error);
284
- this.errorHandlers.forEach((handler) => handler(error));
285
- }
286
- }
287
- async handlePing() {
288
- if (this.isInitiator && !this.hasSharedPublicKey) {
289
- console.log('Received ping from wallet - peer is requesting public key');
290
- this.updateConnectionState('pairing', 'Wallet detected, sharing public key');
291
- await this.sharePublicKey();
292
- }
293
- }
294
- async handlePublicKey(message) {
295
- if (this.isInitiator)
296
- return; // Only wallet processes public keys
297
- console.log('Received public key from dApp, verifying...');
298
- this.updateConnectionState('key-exchange', 'Verifying dApp public key');
299
- // First, compute hash from the raw base64 data to verify it matches
300
- const publicKeyRaw = new Uint8Array(atob(message.payload.publicKey)
301
- .split('')
302
- .map((char) => char.charCodeAt(0)));
303
- const computedHash = await EncryptionUtils.computePublicKeyHashFromRaw(publicKeyRaw);
304
- if (computedHash !== this.pubkeyHash) {
305
- throw new Error(`Public key hash mismatch! Expected: ${this.pubkeyHash}, Got: ${computedHash}`);
306
- }
307
- // Now import the key for use
308
- const publicKeyData = await EncryptionUtils.publicKeyFromBase64(message.payload.publicKey);
309
- this.peerPublicKey = publicKeyData;
310
- this.updateConnectionState('key-exchange', 'Sending encrypted hello message');
311
- await this.sendHello();
312
- }
313
- async handleHello(message) {
314
- if (!this.isInitiator)
315
- return; // Only dApp processes hello messages
316
- console.log('Received hello from wallet');
317
- this.updateConnectionState('key-exchange', 'Received wallet hello, starting WebRTC');
318
- // Decrypt the hello message
319
- const decryptedMessage = await EncryptionUtils.decryptMessage(message.payload, this.privateKey);
320
- this.peerPublicKey = await EncryptionUtils.publicKeyFromBase64(decryptedMessage.publicKey);
321
- this.updateConnectionState('webrtc-negotiating', 'Creating WebRTC offer');
322
- await this.createWebRTCOffer();
323
- }
324
- async handleWebRTCOffer(message) {
325
- if (this.isInitiator)
326
- return;
327
- console.log('Received WebRTC offer');
328
- this.updateConnectionState('webrtc-negotiating', 'Processing WebRTC offer');
329
- const decryptedOffer = await EncryptionUtils.decryptMessage(message.payload, this.privateKey);
330
- await this.peerConnection.setRemoteDescription(decryptedOffer);
331
- // Add any pending ICE candidates
332
- for (const candidate of this.pendingICECandidates) {
333
- await this.peerConnection.addIceCandidate(candidate);
334
- }
335
- this.pendingICECandidates = [];
336
- const answer = await this.peerConnection.createAnswer();
337
- await this.peerConnection.setLocalDescription(answer);
338
- // Send encrypted answer
339
- const encryptedAnswer = await EncryptionUtils.encryptMessage(answer, this.peerPublicKey, this.privateKey, this.publicKey);
340
- await this.signaling.publish({
341
- type: 'webrtc-answer',
342
- payload: encryptedAnswer,
343
- sessionId: this.sessionId,
344
- timestamp: Date.now(),
345
- senderId: this.peerId,
346
- });
347
- }
348
- async handleWebRTCAnswer(message) {
349
- if (!this.isInitiator)
350
- return;
351
- console.log('Received WebRTC answer');
352
- this.updateConnectionState('webrtc-negotiating', 'Processing WebRTC answer');
353
- const decryptedAnswer = await EncryptionUtils.decryptMessage(message.payload, this.privateKey);
354
- await this.peerConnection.setRemoteDescription(decryptedAnswer);
355
- // Add any pending ICE candidates
356
- for (const candidate of this.pendingICECandidates) {
357
- await this.peerConnection.addIceCandidate(candidate);
358
- }
359
- this.pendingICECandidates = [];
360
- }
361
- async handleICECandidate(message) {
362
- const decryptedCandidate = await EncryptionUtils.decryptMessage(message.payload, this.privateKey);
363
- if (this.peerConnection.remoteDescription) {
364
- await this.peerConnection.addIceCandidate(decryptedCandidate);
365
- }
366
- else {
367
- this.pendingICECandidates.push(decryptedCandidate);
368
- }
369
- }
370
- async handleDataMessage(message) {
371
- const decryptedData = await EncryptionUtils.decryptMessage(message.payload, this.privateKey);
372
- // Handle JSON-RPC request
373
- const request = decryptedData;
374
- if (request.method) {
375
- console.log('Received JSON-RPC request:', request.method);
376
- }
377
- if ('result' in request) {
378
- console.log('Received JSON-RPC response:', request.result);
379
- }
380
- if (!request.method && !('result' in request)) {
381
- console.warn('Received unknown payload', request);
382
- return;
383
- }
384
- // Notify message handlers
385
- for (const handler of this.messageHandlers) {
386
- try {
387
- const result = await handler(request);
388
- if (!result) {
389
- console.log('Skipping response');
390
- continue;
391
- }
392
- if ('result' in request) {
393
- console.log('stopping echo');
394
- continue;
395
- }
396
- // Send response back
397
- const response = {
398
- jsonrpc: '2.0',
399
- id: request.id,
400
- result,
401
- };
402
- await this.sendMessage(response);
403
- }
404
- catch (error) {
405
- // Send error response
406
- const errorResponse = {
407
- jsonrpc: '2.0',
408
- id: request.id,
409
- error: {
410
- code: -32603,
411
- message: 'Internal error',
412
- data: error instanceof Error ? error.message : String(error),
413
- },
414
- };
415
- await this.sendMessage(errorResponse);
416
- }
417
- }
418
- }
419
- async sharePublicKey() {
420
- if (!this.publicKey || !this.sessionId || this.hasSharedPublicKey)
421
- return;
422
- const publicKeyBase64 = await EncryptionUtils.publicKeyToBase64(this.publicKey);
423
- const dAppInfo = {
424
- name: 'Open Lavatory dApp',
425
- url: typeof window !== 'undefined' ? window.location?.origin : 'unknown',
426
- };
427
- await this.signaling.publish({
428
- type: 'pubkey',
429
- payload: {
430
- publicKey: publicKeyBase64,
431
- dAppInfo,
432
- },
433
- sessionId: this.sessionId,
434
- timestamp: Date.now(),
435
- senderId: this.peerId,
436
- });
437
- this.hasSharedPublicKey = true;
438
- }
439
- async sendHello() {
440
- if (!this.publicKey || !this.peerPublicKey)
441
- return;
442
- const publicKeyBase64 = await EncryptionUtils.publicKeyToBase64(this.publicKey);
443
- const walletInfo = {
444
- name: 'Open Lavatory Wallet',
445
- version: '1.0.0',
446
- };
447
- const helloMessage = {
448
- publicKey: publicKeyBase64,
449
- walletInfo,
450
- };
451
- const encryptedHello = await EncryptionUtils.encryptMessage(helloMessage, this.peerPublicKey, this.privateKey, this.publicKey);
452
- await this.signaling.publish({
453
- type: 'hello',
454
- payload: encryptedHello,
455
- sessionId: this.sessionId,
456
- timestamp: Date.now(),
457
- senderId: this.peerId,
458
- });
459
- }
460
- async setupWebRTC() {
461
- const iceServers = [
462
- { urls: 'stun:stun.l.google.com:19302' },
463
- { urls: 'stun:stun1.l.google.com:19302' },
464
- { urls: 'stun:stun.services.mozilla.com:3478' },
465
- {
466
- urls: ['turn:openrelay.metered.ca:80', 'turn:openrelay.metered.ca:443'],
467
- username: 'openrelayproject',
468
- credential: 'openrelayproject',
469
- },
470
- ];
471
- this.peerConnection = new RTCPeerConnection({
472
- iceServers,
473
- iceCandidatePoolSize: 10,
474
- iceTransportPolicy: 'all',
475
- bundlePolicy: 'max-bundle',
476
- rtcpMuxPolicy: 'require',
477
- });
478
- // Connection state handlers
479
- this.peerConnection.onconnectionstatechange = () => {
480
- const state = this.peerConnection.connectionState;
481
- console.log('WebRTC connection state:', state);
482
- if (state === 'connected') {
483
- this.updateConnectionState('webrtc-connected', 'Direct P2P connection established');
484
- }
485
- else if (state === 'failed') {
486
- this.retryWebRTC();
487
- }
488
- else if (state === 'connecting') {
489
- this.updateConnectionState('webrtc-negotiating', 'Establishing P2P connection...');
490
- }
491
- };
492
- // ICE candidate handler
493
- this.peerConnection.onicecandidate = async (event) => {
494
- if (event.candidate && this.peerPublicKey) {
495
- const encryptedCandidate = await EncryptionUtils.encryptMessage(event.candidate, this.peerPublicKey, this.privateKey, this.publicKey);
496
- await this.signaling.publish({
497
- type: 'ice-candidate',
498
- payload: encryptedCandidate,
499
- sessionId: this.sessionId,
500
- timestamp: Date.now(),
501
- senderId: this.peerId,
502
- });
503
- }
504
- };
505
- // Data channel setup
506
- if (this.isInitiator) {
507
- this.dataChannel = this.peerConnection.createDataChannel('openlv-data');
508
- this.setupDataChannel();
509
- }
510
- else {
511
- this.peerConnection.ondatachannel = (event) => {
512
- this.dataChannel = event.channel;
513
- this.setupDataChannel();
514
- };
515
- }
516
- }
517
- setupDataChannel() {
518
- if (!this.dataChannel)
519
- return;
520
- this.dataChannel.onopen = () => {
521
- console.log('WebRTC data channel opened');
522
- this.updateConnectionState('webrtc-connected', 'P2P data channel ready');
523
- };
524
- this.dataChannel.onmessage = async (event) => {
525
- try {
526
- const message = JSON.parse(event.data);
527
- // Check if this is a request (has method) or response (has result/error)
528
- if ('method' in message) {
529
- // This is a request
530
- const request = message;
531
- console.log('Received P2P JSON-RPC request:', request.method);
532
- // Handle the request - only send one response, not multiple
533
- if (this.messageHandlers.length > 0) {
534
- try {
535
- // Use the first handler (there should typically be only one)
536
- const result = await this.messageHandlers[0](request);
537
- const response = {
538
- jsonrpc: '2.0',
539
- id: request.id,
540
- result,
541
- };
542
- this.dataChannel.send(JSON.stringify(response));
543
- }
544
- catch (error) {
545
- const errorResponse = {
546
- jsonrpc: '2.0',
547
- id: request.id,
548
- error: {
549
- code: -32603,
550
- message: 'Internal error',
551
- data: error instanceof Error ? error.message : String(error),
552
- },
553
- };
554
- this.dataChannel.send(JSON.stringify(errorResponse));
555
- }
556
- }
557
- }
558
- else {
559
- // This is a response - just log it, don't process further
560
- const response = message;
561
- console.log('Received P2P JSON-RPC response for ID:', response.id, response);
562
- // Responses are typically handled by the original sender, not processed here
563
- // return response;
564
- if (this.messageHandlers.length > 0) {
565
- try {
566
- await this.messageHandlers[0](response);
567
- }
568
- catch (error) {
569
- console.error('Error handling response in message handler:', error);
570
- }
571
- }
572
- }
573
- }
574
- catch (error) {
575
- console.error('Error handling P2P message:', error);
576
- }
577
- };
578
- this.dataChannel.onclose = () => {
579
- this.updateConnectionState('mqtt-connected', 'P2P connection lost, using relay');
580
- };
581
- }
582
- async createWebRTCOffer() {
583
- if (!this.peerConnection || !this.peerPublicKey)
584
- return;
585
- const offer = await this.peerConnection.createOffer();
586
- await this.peerConnection.setLocalDescription(offer);
587
- const encryptedOffer = await EncryptionUtils.encryptMessage(offer, this.peerPublicKey, this.privateKey, this.publicKey);
588
- await this.signaling.publish({
589
- type: 'webrtc-offer',
590
- payload: encryptedOffer,
591
- sessionId: this.sessionId,
592
- timestamp: Date.now(),
593
- senderId: this.peerId,
594
- });
595
- this.setConnectionTimeout();
596
- }
597
- retryWebRTC() {
598
- if (this.webrtcRetryCount >= this.maxWebrtcRetries) {
599
- console.error('Max WebRTC retries reached, falling back to MQTT');
600
- this.updateConnectionState('mqtt-connected', 'WebRTC failed, using relay fallback');
601
- return;
602
- }
603
- this.webrtcRetryCount++;
604
- console.log(`Retrying WebRTC (${this.webrtcRetryCount}/${this.maxWebrtcRetries})`);
605
- this.webrtcRetryInterval = setTimeout(async () => {
606
- await this.setupWebRTC();
607
- if (this.isInitiator) {
608
- await this.createWebRTCOffer();
609
- }
610
- }, 2000 * this.webrtcRetryCount);
611
- }
612
- setConnectionTimeout() {
613
- this.clearTimeouts();
614
- this.connectionTimeout = setTimeout(() => {
615
- console.warn('WebRTC connection timeout, retrying...');
616
- this.retryWebRTC();
617
- }, 15000);
618
- }
619
- clearTimeouts() {
620
- if (this.webrtcRetryInterval) {
621
- clearTimeout(this.webrtcRetryInterval);
622
- this.webrtcRetryInterval = undefined;
623
- }
624
- if (this.connectionTimeout) {
625
- clearTimeout(this.connectionTimeout);
626
- this.connectionTimeout = undefined;
627
- }
628
- this.webrtcRetryCount = 0;
629
- }
630
- // Public API methods
631
- async initSession() {
632
- this.isInitiator = true;
633
- this.sessionId = EncryptionUtils.generateSessionId();
634
- // Generate keypair
635
- const keyPair = await EncryptionUtils.generateKeyPair();
636
- this.privateKey = keyPair.privateKey;
637
- this.publicKey = keyPair.publicKey;
638
- this.pubkeyHash = await EncryptionUtils.computePublicKeyHash(this.publicKey);
639
- // Generate shared key for symmetric encryption during handshake
640
- this.sharedKey = EncryptionUtils.generateSharedKey(); // Random shared key as hex
641
- this.updateConnectionState('mqtt-connecting', 'Connecting to signaling server');
642
- // Setup signaling
643
- await this.signaling.waitForConnection();
644
- await this.signaling.subscribe(this.sessionId, (message) => this.handleSignalingMessage(message));
645
- this.updateConnectionState('pairing', 'Waiting for wallet to connect');
646
- // Setup WebRTC
647
- await this.setupWebRTC();
648
- const openLVUrl = encodeConnectionURL({
649
- sessionId: this.sessionId,
650
- pubkeyHash: this.pubkeyHash,
651
- sharedKey: this.sharedKey,
652
- });
653
- return { openLVUrl };
654
- }
655
- async connectToSession(openLVUrl) {
656
- this.isInitiator = false;
657
- const { sessionId, pubkeyHash, sharedKey } = decodeConnectionURL(openLVUrl);
658
- this.sessionId = sessionId;
659
- this.pubkeyHash = pubkeyHash;
660
- this.sharedKey = sharedKey;
661
- // Generate keypair
662
- const keyPair = await EncryptionUtils.generateKeyPair();
663
- this.privateKey = keyPair.privateKey;
664
- this.publicKey = keyPair.publicKey;
665
- this.updateConnectionState('mqtt-connecting', 'Connecting to signaling server');
666
- // Setup signaling
667
- await this.signaling.waitForConnection();
668
- await this.signaling.subscribe(this.sessionId, (message) => this.handleSignalingMessage(message));
669
- this.updateConnectionState('pairing', 'Requesting dApp public key');
670
- // Setup WebRTC
671
- await this.setupWebRTC();
672
- // Send ping to request public key
673
- await this.signaling.publish({
674
- type: 'ping',
675
- payload: 'request-pubkey',
676
- sessionId: this.sessionId,
677
- timestamp: Date.now(),
678
- senderId: this.peerId,
679
- });
680
- }
681
- async sendMessage(message) {
682
- let jsonRpcMessage;
683
- // If it's a string, wrap it in a custom JSON-RPC method
684
- if (typeof message === 'string') {
685
- jsonRpcMessage = {
686
- jsonrpc: '2.0',
687
- id: Date.now(),
688
- method: 'lv_rawText',
689
- params: [message],
690
- };
691
- }
692
- else {
693
- jsonRpcMessage = message;
694
- }
695
- if (this.dataChannel && this.dataChannel.readyState === 'open') {
696
- // Send via WebRTC
697
- this.dataChannel.send(JSON.stringify(jsonRpcMessage));
698
- }
699
- else if (this.peerPublicKey && this.signaling.connected) {
700
- // Send via encrypted MQTT
701
- const encryptedMessage = await EncryptionUtils.encryptMessage(jsonRpcMessage, this.peerPublicKey, this.privateKey, this.publicKey);
702
- await this.signaling.publish({
703
- type: 'data',
704
- payload: encryptedMessage,
705
- sessionId: this.sessionId,
706
- timestamp: Date.now(),
707
- senderId: this.peerId,
708
- });
709
- }
710
- else {
711
- throw new Error('No connection available to send message');
712
- }
713
- }
714
- // Event handlers
715
- onPhaseChange(handler) {
716
- this.phaseHandlers.push(handler);
717
- }
718
- onMessage(handler) {
719
- this.messageHandlers.push(handler);
720
- }
721
- onError(handler) {
722
- this.errorHandlers.push(handler);
723
- }
724
- // Status getters
725
- getConnectionStatus() {
726
- switch (this.connectionState) {
727
- case 'webrtc-connected':
728
- return 'webrtc-connected';
729
- case 'mqtt-connected':
730
- case 'mqtt-connecting':
731
- case 'pairing':
732
- case 'key-exchange':
733
- case 'webrtc-negotiating':
734
- return 'mqtt-only';
735
- default:
736
- return 'disconnected';
737
- }
738
- }
739
- getConnectionState() {
740
- return this.connectionState;
741
- }
742
- disconnect() {
743
- this.clearTimeouts();
744
- if (this.dataChannel) {
745
- this.dataChannel.close();
746
- }
747
- if (this.peerConnection) {
748
- this.peerConnection.close();
749
- }
750
- this.signaling.disconnect();
751
- this.updateConnectionState('disconnected', 'Connection closed');
752
- // Reset state
753
- this.hasSharedPublicKey = false;
754
- this.pendingICECandidates = [];
755
- }
756
- }
757
- //# sourceMappingURL=connection.js.map