@xtr-dev/rondevu-client 0.20.1 → 0.21.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.
- package/README.md +83 -385
- package/dist/api/batcher.d.ts +60 -38
- package/dist/api/batcher.js +121 -77
- package/dist/api/client.d.ts +104 -61
- package/dist/api/client.js +273 -185
- package/dist/connections/answerer.d.ts +15 -6
- package/dist/connections/answerer.js +56 -19
- package/dist/connections/base.d.ts +6 -4
- package/dist/connections/base.js +26 -16
- package/dist/connections/config.d.ts +30 -0
- package/dist/connections/config.js +20 -0
- package/dist/connections/events.d.ts +6 -6
- package/dist/connections/offerer.d.ts +37 -8
- package/dist/connections/offerer.js +92 -24
- package/dist/core/ice-config.d.ts +35 -0
- package/dist/core/ice-config.js +111 -0
- package/dist/core/index.d.ts +18 -18
- package/dist/core/index.js +18 -13
- package/dist/core/offer-pool.d.ts +30 -11
- package/dist/core/offer-pool.js +90 -76
- package/dist/core/peer.d.ts +158 -0
- package/dist/core/peer.js +254 -0
- package/dist/core/polling-manager.d.ts +71 -0
- package/dist/core/polling-manager.js +122 -0
- package/dist/core/rondevu-errors.d.ts +59 -0
- package/dist/core/rondevu-errors.js +75 -0
- package/dist/core/rondevu-types.d.ts +125 -0
- package/dist/core/rondevu-types.js +6 -0
- package/dist/core/rondevu.d.ts +106 -209
- package/dist/core/rondevu.js +222 -349
- package/dist/crypto/adapter.d.ts +25 -9
- package/dist/crypto/node.d.ts +27 -5
- package/dist/crypto/node.js +96 -25
- package/dist/crypto/web.d.ts +26 -4
- package/dist/crypto/web.js +102 -25
- package/dist/utils/message-buffer.js +4 -4
- package/dist/webrtc/adapter.d.ts +22 -0
- package/dist/webrtc/adapter.js +5 -0
- package/dist/webrtc/browser.d.ts +12 -0
- package/dist/webrtc/browser.js +15 -0
- package/dist/webrtc/node.d.ts +32 -0
- package/dist/webrtc/node.js +32 -0
- package/package.json +17 -6
package/dist/crypto/adapter.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Crypto adapter interface for platform-independent cryptographic operations
|
|
3
3
|
*/
|
|
4
|
-
export interface
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
export interface Credential {
|
|
5
|
+
name: string;
|
|
6
|
+
secret: string;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* Platform-independent crypto adapter interface
|
|
@@ -11,17 +11,33 @@ export interface Keypair {
|
|
|
11
11
|
*/
|
|
12
12
|
export interface CryptoAdapter {
|
|
13
13
|
/**
|
|
14
|
-
* Generate
|
|
14
|
+
* Generate HMAC-SHA256 signature for message authentication
|
|
15
|
+
* @param secret - The credential secret (hex string)
|
|
16
|
+
* @param message - The message to sign
|
|
17
|
+
* @returns Base64-encoded signature
|
|
15
18
|
*/
|
|
16
|
-
|
|
19
|
+
generateSignature(secret: string, message: string): Promise<string>;
|
|
17
20
|
/**
|
|
18
|
-
*
|
|
21
|
+
* Verify HMAC-SHA256 signature
|
|
22
|
+
* @param secret - The credential secret (hex string)
|
|
23
|
+
* @param message - The message that was signed
|
|
24
|
+
* @param signature - The signature to verify (base64)
|
|
25
|
+
* @returns True if signature is valid
|
|
19
26
|
*/
|
|
20
|
-
|
|
27
|
+
verifySignature(secret: string, message: string, signature: string): Promise<boolean>;
|
|
21
28
|
/**
|
|
22
|
-
*
|
|
29
|
+
* Generate a random secret (256-bit hex string)
|
|
30
|
+
* @returns 64-character hex string
|
|
23
31
|
*/
|
|
24
|
-
|
|
32
|
+
generateSecret(): string;
|
|
33
|
+
/**
|
|
34
|
+
* Convert hex string to bytes
|
|
35
|
+
*/
|
|
36
|
+
hexToBytes(hex: string): Uint8Array;
|
|
37
|
+
/**
|
|
38
|
+
* Convert bytes to hex string
|
|
39
|
+
*/
|
|
40
|
+
bytesToHex(bytes: Uint8Array): string;
|
|
25
41
|
/**
|
|
26
42
|
* Convert Uint8Array to base64 string
|
|
27
43
|
*/
|
package/dist/crypto/node.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Node.js Crypto adapter for Node.js environments
|
|
3
3
|
* Requires Node.js 19+ or Node.js 18 with --experimental-global-webcrypto flag
|
|
4
4
|
*/
|
|
5
|
-
import { CryptoAdapter
|
|
5
|
+
import { CryptoAdapter } from './adapter.js';
|
|
6
6
|
/**
|
|
7
7
|
* Node.js Crypto implementation using Node.js built-in APIs
|
|
8
8
|
* Uses Buffer for base64 encoding and crypto.randomBytes for random generation
|
|
@@ -19,16 +19,38 @@ import { CryptoAdapter, Keypair } from './adapter.js';
|
|
|
19
19
|
* const api = new RondevuAPI(
|
|
20
20
|
* 'https://signal.example.com',
|
|
21
21
|
* 'alice',
|
|
22
|
-
*
|
|
22
|
+
* { name: 'alice', secret: '...' },
|
|
23
23
|
* new NodeCryptoAdapter()
|
|
24
24
|
* )
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
27
|
export declare class NodeCryptoAdapter implements CryptoAdapter {
|
|
28
28
|
constructor();
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Generate HMAC-SHA256 signature
|
|
31
|
+
*/
|
|
32
|
+
generateSignature(secret: string, message: string): Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Verify HMAC-SHA256 signature
|
|
35
|
+
* Uses constant-time comparison via Web Crypto API to prevent timing attacks
|
|
36
|
+
*
|
|
37
|
+
* @returns false for invalid signatures, throws for malformed input
|
|
38
|
+
* @throws Error if secret/signature format is invalid (not a verification failure)
|
|
39
|
+
*/
|
|
40
|
+
verifySignature(secret: string, message: string, signature: string): Promise<boolean>;
|
|
41
|
+
/**
|
|
42
|
+
* Generate a random secret (256-bit hex string)
|
|
43
|
+
*/
|
|
44
|
+
generateSecret(): string;
|
|
45
|
+
/**
|
|
46
|
+
* Convert hex string to bytes
|
|
47
|
+
* @throws Error if hex string is invalid
|
|
48
|
+
*/
|
|
49
|
+
hexToBytes(hex: string): Uint8Array;
|
|
50
|
+
/**
|
|
51
|
+
* Convert bytes to hex string
|
|
52
|
+
*/
|
|
53
|
+
bytesToHex(bytes: Uint8Array): string;
|
|
32
54
|
bytesToBase64(bytes: Uint8Array): string;
|
|
33
55
|
base64ToBytes(base64: string): Uint8Array;
|
|
34
56
|
randomBytes(length: number): Uint8Array;
|
package/dist/crypto/node.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* Node.js Crypto adapter for Node.js environments
|
|
3
3
|
* Requires Node.js 19+ or Node.js 18 with --experimental-global-webcrypto flag
|
|
4
4
|
*/
|
|
5
|
-
import * as ed25519 from '@noble/ed25519';
|
|
6
5
|
/**
|
|
7
6
|
* Node.js Crypto implementation using Node.js built-in APIs
|
|
8
7
|
* Uses Buffer for base64 encoding and crypto.randomBytes for random generation
|
|
@@ -19,55 +18,127 @@ import * as ed25519 from '@noble/ed25519';
|
|
|
19
18
|
* const api = new RondevuAPI(
|
|
20
19
|
* 'https://signal.example.com',
|
|
21
20
|
* 'alice',
|
|
22
|
-
*
|
|
21
|
+
* { name: 'alice', secret: '...' },
|
|
23
22
|
* new NodeCryptoAdapter()
|
|
24
23
|
* )
|
|
25
24
|
* ```
|
|
26
25
|
*/
|
|
27
26
|
export class NodeCryptoAdapter {
|
|
28
27
|
constructor() {
|
|
29
|
-
// Set SHA-512 hash function for ed25519 using Node's crypto.subtle
|
|
30
28
|
if (typeof crypto === 'undefined' || !crypto.subtle) {
|
|
31
29
|
throw new Error('crypto.subtle is not available. ' +
|
|
32
30
|
'Node.js 19+ is required, or Node.js 18 with --experimental-global-webcrypto flag');
|
|
33
31
|
}
|
|
34
|
-
ed25519.hashes.sha512Async = async (message) => {
|
|
35
|
-
const hash = await crypto.subtle.digest('SHA-512', message);
|
|
36
|
-
return new Uint8Array(hash);
|
|
37
|
-
};
|
|
38
32
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Generate HMAC-SHA256 signature
|
|
35
|
+
*/
|
|
36
|
+
async generateSignature(secret, message) {
|
|
37
|
+
if (!secret || typeof secret !== 'string') {
|
|
38
|
+
throw new Error('Invalid secret: must be a non-empty string');
|
|
39
|
+
}
|
|
40
|
+
if (typeof message !== 'string') {
|
|
41
|
+
throw new Error('Invalid message: must be a string');
|
|
42
|
+
}
|
|
43
|
+
const secretBytes = this.hexToBytes(secret);
|
|
44
|
+
// Import secret as HMAC key
|
|
45
|
+
const key = await crypto.subtle.importKey('raw', secretBytes, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
46
|
+
// Convert message to bytes
|
|
49
47
|
const encoder = new TextEncoder();
|
|
50
48
|
const messageBytes = encoder.encode(message);
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
// Generate HMAC signature
|
|
50
|
+
const signatureBytes = await crypto.subtle.sign('HMAC', key, messageBytes);
|
|
51
|
+
// Convert to base64
|
|
52
|
+
return this.bytesToBase64(new Uint8Array(signatureBytes));
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Verify HMAC-SHA256 signature
|
|
56
|
+
* Uses constant-time comparison via Web Crypto API to prevent timing attacks
|
|
57
|
+
*
|
|
58
|
+
* @returns false for invalid signatures, throws for malformed input
|
|
59
|
+
* @throws Error if secret/signature format is invalid (not a verification failure)
|
|
60
|
+
*/
|
|
61
|
+
async verifySignature(secret, message, signature) {
|
|
62
|
+
// Validate inputs first
|
|
63
|
+
// Use generic error messages to prevent timing attacks and information leakage
|
|
64
|
+
let secretBytes;
|
|
65
|
+
let signatureBytes;
|
|
66
|
+
try {
|
|
67
|
+
secretBytes = this.hexToBytes(secret);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
// Generic error message - don't leak format details
|
|
71
|
+
throw new Error('Invalid credential format');
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
signatureBytes = this.base64ToBytes(signature);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
// Generic error message - don't leak format details
|
|
78
|
+
throw new Error('Invalid signature format');
|
|
79
|
+
}
|
|
80
|
+
// Perform HMAC verification
|
|
55
81
|
try {
|
|
56
|
-
|
|
57
|
-
const
|
|
82
|
+
// Import secret as HMAC key for verification
|
|
83
|
+
const key = await crypto.subtle.importKey('raw', secretBytes, { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
|
|
84
|
+
// Convert message to bytes
|
|
58
85
|
const encoder = new TextEncoder();
|
|
59
86
|
const messageBytes = encoder.encode(message);
|
|
60
|
-
|
|
87
|
+
// Use Web Crypto API's verify() for constant-time comparison
|
|
88
|
+
// Returns false for signature mismatch (auth failure)
|
|
89
|
+
return await crypto.subtle.verify('HMAC', key, signatureBytes, messageBytes);
|
|
61
90
|
}
|
|
62
|
-
catch {
|
|
63
|
-
|
|
91
|
+
catch (error) {
|
|
92
|
+
// System/crypto errors - unexpected failures
|
|
93
|
+
throw new Error(`Signature verification error: ${error instanceof Error ? error.message : String(error)}`);
|
|
64
94
|
}
|
|
65
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Generate a random secret (256-bit hex string)
|
|
98
|
+
*/
|
|
99
|
+
generateSecret() {
|
|
100
|
+
const bytes = this.randomBytes(32); // 32 bytes = 256 bits
|
|
101
|
+
return this.bytesToHex(bytes);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Convert hex string to bytes
|
|
105
|
+
* @throws Error if hex string is invalid
|
|
106
|
+
*/
|
|
107
|
+
hexToBytes(hex) {
|
|
108
|
+
if (hex.length % 2 !== 0) {
|
|
109
|
+
throw new Error('Hex string must have even length');
|
|
110
|
+
}
|
|
111
|
+
// Validate all characters are valid hex (0-9, a-f, A-F)
|
|
112
|
+
if (!/^[0-9a-fA-F]*$/.test(hex)) {
|
|
113
|
+
throw new Error('Invalid hex string: contains non-hex characters');
|
|
114
|
+
}
|
|
115
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
116
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
117
|
+
bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
|
|
118
|
+
}
|
|
119
|
+
return bytes;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Convert bytes to hex string
|
|
123
|
+
*/
|
|
124
|
+
bytesToHex(bytes) {
|
|
125
|
+
return Array.from(bytes)
|
|
126
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
127
|
+
.join('');
|
|
128
|
+
}
|
|
66
129
|
bytesToBase64(bytes) {
|
|
67
130
|
// Node.js Buffer provides native base64 encoding
|
|
68
131
|
return Buffer.from(bytes).toString('base64');
|
|
69
132
|
}
|
|
70
133
|
base64ToBytes(base64) {
|
|
134
|
+
// Validate base64 string format
|
|
135
|
+
if (typeof base64 !== 'string' || base64.length === 0) {
|
|
136
|
+
throw new Error('Invalid base64 string');
|
|
137
|
+
}
|
|
138
|
+
// Base64 length must be divisible by 4 (with padding), + requires at least one char
|
|
139
|
+
if (base64.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(base64)) {
|
|
140
|
+
throw new Error('Invalid base64 string');
|
|
141
|
+
}
|
|
71
142
|
// Node.js Buffer provides native base64 decoding
|
|
72
143
|
return new Uint8Array(Buffer.from(base64, 'base64'));
|
|
73
144
|
}
|
package/dist/crypto/web.d.ts
CHANGED
|
@@ -1,15 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Web Crypto adapter for browser environments
|
|
3
3
|
*/
|
|
4
|
-
import { CryptoAdapter
|
|
4
|
+
import { CryptoAdapter } from './adapter.js';
|
|
5
5
|
/**
|
|
6
6
|
* Web Crypto implementation using browser APIs
|
|
7
7
|
* Uses btoa/atob for base64 encoding and crypto.getRandomValues for random bytes
|
|
8
8
|
*/
|
|
9
9
|
export declare class WebCryptoAdapter implements CryptoAdapter {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Generate HMAC-SHA256 signature
|
|
12
|
+
*/
|
|
13
|
+
generateSignature(secret: string, message: string): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Verify HMAC-SHA256 signature
|
|
16
|
+
* Uses constant-time comparison via Web Crypto API to prevent timing attacks
|
|
17
|
+
*
|
|
18
|
+
* @returns false for invalid signatures, throws for malformed input
|
|
19
|
+
* @throws Error if secret/signature format is invalid (not a verification failure)
|
|
20
|
+
*/
|
|
21
|
+
verifySignature(secret: string, message: string, signature: string): Promise<boolean>;
|
|
22
|
+
/**
|
|
23
|
+
* Generate a random secret (256-bit hex string)
|
|
24
|
+
*/
|
|
25
|
+
generateSecret(): string;
|
|
26
|
+
/**
|
|
27
|
+
* Convert hex string to bytes
|
|
28
|
+
* @throws Error if hex string is invalid
|
|
29
|
+
*/
|
|
30
|
+
hexToBytes(hex: string): Uint8Array;
|
|
31
|
+
/**
|
|
32
|
+
* Convert bytes to hex string
|
|
33
|
+
*/
|
|
34
|
+
bytesToHex(bytes: Uint8Array): string;
|
|
13
35
|
bytesToBase64(bytes: Uint8Array): string;
|
|
14
36
|
base64ToBytes(base64: string): Uint8Array;
|
|
15
37
|
randomBytes(length: number): Uint8Array;
|
package/dist/crypto/web.js
CHANGED
|
@@ -1,50 +1,127 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Web Crypto adapter for browser environments
|
|
3
3
|
*/
|
|
4
|
-
import * as ed25519 from '@noble/ed25519';
|
|
5
|
-
// Set SHA-512 hash function for ed25519 (required in @noble/ed25519 v3+)
|
|
6
|
-
ed25519.hashes.sha512Async = async (message) => {
|
|
7
|
-
return new Uint8Array(await crypto.subtle.digest('SHA-512', message));
|
|
8
|
-
};
|
|
9
4
|
/**
|
|
10
5
|
* Web Crypto implementation using browser APIs
|
|
11
6
|
* Uses btoa/atob for base64 encoding and crypto.getRandomValues for random bytes
|
|
12
7
|
*/
|
|
13
8
|
export class WebCryptoAdapter {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Generate HMAC-SHA256 signature
|
|
11
|
+
*/
|
|
12
|
+
async generateSignature(secret, message) {
|
|
13
|
+
if (!secret || typeof secret !== 'string') {
|
|
14
|
+
throw new Error('Invalid secret: must be a non-empty string');
|
|
15
|
+
}
|
|
16
|
+
if (typeof message !== 'string') {
|
|
17
|
+
throw new Error('Invalid message: must be a string');
|
|
18
|
+
}
|
|
19
|
+
const secretBytes = this.hexToBytes(secret);
|
|
20
|
+
// Import secret as HMAC key
|
|
21
|
+
const key = await crypto.subtle.importKey('raw', secretBytes, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
22
|
+
// Convert message to bytes
|
|
24
23
|
const encoder = new TextEncoder();
|
|
25
24
|
const messageBytes = encoder.encode(message);
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
// Generate HMAC signature
|
|
26
|
+
const signatureBytes = await crypto.subtle.sign('HMAC', key, messageBytes);
|
|
27
|
+
// Convert to base64
|
|
28
|
+
return this.bytesToBase64(new Uint8Array(signatureBytes));
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Verify HMAC-SHA256 signature
|
|
32
|
+
* Uses constant-time comparison via Web Crypto API to prevent timing attacks
|
|
33
|
+
*
|
|
34
|
+
* @returns false for invalid signatures, throws for malformed input
|
|
35
|
+
* @throws Error if secret/signature format is invalid (not a verification failure)
|
|
36
|
+
*/
|
|
37
|
+
async verifySignature(secret, message, signature) {
|
|
38
|
+
// Validate inputs first
|
|
39
|
+
// Use generic error messages to prevent timing attacks and information leakage
|
|
40
|
+
let secretBytes;
|
|
41
|
+
let signatureBytes;
|
|
42
|
+
try {
|
|
43
|
+
secretBytes = this.hexToBytes(secret);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
// Generic error message - don't leak format details
|
|
47
|
+
throw new Error('Invalid credential format');
|
|
48
|
+
}
|
|
30
49
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
50
|
+
signatureBytes = this.base64ToBytes(signature);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
// Generic error message - don't leak format details
|
|
54
|
+
throw new Error('Invalid signature format');
|
|
55
|
+
}
|
|
56
|
+
// Perform HMAC verification
|
|
57
|
+
try {
|
|
58
|
+
// Import secret as HMAC key for verification
|
|
59
|
+
const key = await crypto.subtle.importKey('raw', secretBytes, { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
|
|
60
|
+
// Convert message to bytes
|
|
33
61
|
const encoder = new TextEncoder();
|
|
34
62
|
const messageBytes = encoder.encode(message);
|
|
35
|
-
|
|
63
|
+
// Use Web Crypto API's verify() for constant-time comparison
|
|
64
|
+
// Returns false for signature mismatch (auth failure)
|
|
65
|
+
return await crypto.subtle.verify('HMAC', key, signatureBytes, messageBytes);
|
|
36
66
|
}
|
|
37
|
-
catch {
|
|
38
|
-
|
|
67
|
+
catch (error) {
|
|
68
|
+
// System/crypto errors - unexpected failures
|
|
69
|
+
throw new Error(`Signature verification error: ${error instanceof Error ? error.message : String(error)}`);
|
|
39
70
|
}
|
|
40
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Generate a random secret (256-bit hex string)
|
|
74
|
+
*/
|
|
75
|
+
generateSecret() {
|
|
76
|
+
const bytes = this.randomBytes(32); // 32 bytes = 256 bits
|
|
77
|
+
return this.bytesToHex(bytes);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Convert hex string to bytes
|
|
81
|
+
* @throws Error if hex string is invalid
|
|
82
|
+
*/
|
|
83
|
+
hexToBytes(hex) {
|
|
84
|
+
if (hex.length % 2 !== 0) {
|
|
85
|
+
throw new Error('Hex string must have even length');
|
|
86
|
+
}
|
|
87
|
+
// Validate all characters are valid hex (0-9, a-f, A-F)
|
|
88
|
+
if (!/^[0-9a-fA-F]*$/.test(hex)) {
|
|
89
|
+
throw new Error('Invalid hex string: contains non-hex characters');
|
|
90
|
+
}
|
|
91
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
92
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
93
|
+
bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
|
|
94
|
+
}
|
|
95
|
+
return bytes;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Convert bytes to hex string
|
|
99
|
+
*/
|
|
100
|
+
bytesToHex(bytes) {
|
|
101
|
+
return Array.from(bytes)
|
|
102
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
103
|
+
.join('');
|
|
104
|
+
}
|
|
41
105
|
bytesToBase64(bytes) {
|
|
42
106
|
const binString = Array.from(bytes, byte => String.fromCodePoint(byte)).join('');
|
|
43
107
|
return btoa(binString);
|
|
44
108
|
}
|
|
45
109
|
base64ToBytes(base64) {
|
|
46
|
-
|
|
47
|
-
|
|
110
|
+
// Validate base64 string format
|
|
111
|
+
if (typeof base64 !== 'string' || base64.length === 0) {
|
|
112
|
+
throw new Error('Invalid base64 string');
|
|
113
|
+
}
|
|
114
|
+
// Base64 length must be divisible by 4 (with padding), + requires at least one char
|
|
115
|
+
if (base64.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(base64)) {
|
|
116
|
+
throw new Error('Invalid base64 string');
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const binString = atob(base64);
|
|
120
|
+
return Uint8Array.from(binString, char => char.codePointAt(0));
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
throw new Error('Invalid base64 string');
|
|
124
|
+
}
|
|
48
125
|
}
|
|
49
126
|
randomBytes(length) {
|
|
50
127
|
return crypto.getRandomValues(new Uint8Array(length));
|
|
@@ -40,7 +40,7 @@ export class MessageBuffer {
|
|
|
40
40
|
*/
|
|
41
41
|
getValid() {
|
|
42
42
|
const now = Date.now();
|
|
43
|
-
return this.buffer.filter(
|
|
43
|
+
return this.buffer.filter(msg => now - msg.timestamp < this.config.maxAge);
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* Get and remove expired messages
|
|
@@ -48,7 +48,7 @@ export class MessageBuffer {
|
|
|
48
48
|
getExpired() {
|
|
49
49
|
const now = Date.now();
|
|
50
50
|
const expired = [];
|
|
51
|
-
this.buffer = this.buffer.filter(
|
|
51
|
+
this.buffer = this.buffer.filter(msg => {
|
|
52
52
|
if (now - msg.timestamp >= this.config.maxAge) {
|
|
53
53
|
expired.push(msg);
|
|
54
54
|
return false;
|
|
@@ -61,7 +61,7 @@ export class MessageBuffer {
|
|
|
61
61
|
* Remove a specific message by ID
|
|
62
62
|
*/
|
|
63
63
|
remove(messageId) {
|
|
64
|
-
const index = this.buffer.findIndex(
|
|
64
|
+
const index = this.buffer.findIndex(msg => msg.id === messageId);
|
|
65
65
|
if (index === -1)
|
|
66
66
|
return null;
|
|
67
67
|
const [removed] = this.buffer.splice(index, 1);
|
|
@@ -79,7 +79,7 @@ export class MessageBuffer {
|
|
|
79
79
|
* Increment attempt count for a message
|
|
80
80
|
*/
|
|
81
81
|
incrementAttempt(messageId) {
|
|
82
|
-
const message = this.buffer.find(
|
|
82
|
+
const message = this.buffer.find(msg => msg.id === messageId);
|
|
83
83
|
if (!message)
|
|
84
84
|
return false;
|
|
85
85
|
message.attempts++;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebRTC adapter interface for platform-independent WebRTC operations
|
|
3
|
+
* Allows using native browser APIs or polyfills like `wrtc` for Node.js
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Platform-independent WebRTC adapter interface
|
|
7
|
+
* Implementations provide platform-specific WebRTC constructors
|
|
8
|
+
*/
|
|
9
|
+
export interface WebRTCAdapter {
|
|
10
|
+
/**
|
|
11
|
+
* Create a new RTCPeerConnection
|
|
12
|
+
* @param config - RTCConfiguration for the peer connection
|
|
13
|
+
* @returns A new RTCPeerConnection instance
|
|
14
|
+
*/
|
|
15
|
+
createPeerConnection(config?: RTCConfiguration): RTCPeerConnection;
|
|
16
|
+
/**
|
|
17
|
+
* Create a new RTCIceCandidate
|
|
18
|
+
* @param candidateInit - RTCIceCandidateInit to create the candidate from
|
|
19
|
+
* @returns A new RTCIceCandidate instance
|
|
20
|
+
*/
|
|
21
|
+
createIceCandidate(candidateInit: RTCIceCandidateInit): RTCIceCandidate;
|
|
22
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser WebRTC adapter using native browser APIs
|
|
3
|
+
*/
|
|
4
|
+
import { WebRTCAdapter } from './adapter.js';
|
|
5
|
+
/**
|
|
6
|
+
* Browser WebRTC implementation using native browser APIs
|
|
7
|
+
* This is the default adapter for browser environments
|
|
8
|
+
*/
|
|
9
|
+
export declare class BrowserWebRTCAdapter implements WebRTCAdapter {
|
|
10
|
+
createPeerConnection(config?: RTCConfiguration): RTCPeerConnection;
|
|
11
|
+
createIceCandidate(candidateInit: RTCIceCandidateInit): RTCIceCandidate;
|
|
12
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser WebRTC adapter using native browser APIs
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Browser WebRTC implementation using native browser APIs
|
|
6
|
+
* This is the default adapter for browser environments
|
|
7
|
+
*/
|
|
8
|
+
export class BrowserWebRTCAdapter {
|
|
9
|
+
createPeerConnection(config) {
|
|
10
|
+
return new RTCPeerConnection(config);
|
|
11
|
+
}
|
|
12
|
+
createIceCandidate(candidateInit) {
|
|
13
|
+
return new RTCIceCandidate(candidateInit);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js WebRTC adapter using polyfills like `wrtc`
|
|
3
|
+
*/
|
|
4
|
+
import { WebRTCAdapter } from './adapter.js';
|
|
5
|
+
export interface NodeWebRTCPolyfills {
|
|
6
|
+
RTCPeerConnection: typeof RTCPeerConnection;
|
|
7
|
+
RTCIceCandidate: typeof RTCIceCandidate;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Node.js WebRTC implementation using polyfills
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import wrtc from 'wrtc'
|
|
15
|
+
*
|
|
16
|
+
* const adapter = new NodeWebRTCAdapter({
|
|
17
|
+
* RTCPeerConnection: wrtc.RTCPeerConnection,
|
|
18
|
+
* RTCIceCandidate: wrtc.RTCIceCandidate,
|
|
19
|
+
* })
|
|
20
|
+
*
|
|
21
|
+
* const rondevu = await Rondevu.connect({
|
|
22
|
+
* apiUrl: 'https://api.ronde.vu',
|
|
23
|
+
* webrtcAdapter: adapter,
|
|
24
|
+
* })
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare class NodeWebRTCAdapter implements WebRTCAdapter {
|
|
28
|
+
private readonly polyfills;
|
|
29
|
+
constructor(polyfills: NodeWebRTCPolyfills);
|
|
30
|
+
createPeerConnection(config?: RTCConfiguration): RTCPeerConnection;
|
|
31
|
+
createIceCandidate(candidateInit: RTCIceCandidateInit): RTCIceCandidate;
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js WebRTC adapter using polyfills like `wrtc`
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Node.js WebRTC implementation using polyfills
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import wrtc from 'wrtc'
|
|
10
|
+
*
|
|
11
|
+
* const adapter = new NodeWebRTCAdapter({
|
|
12
|
+
* RTCPeerConnection: wrtc.RTCPeerConnection,
|
|
13
|
+
* RTCIceCandidate: wrtc.RTCIceCandidate,
|
|
14
|
+
* })
|
|
15
|
+
*
|
|
16
|
+
* const rondevu = await Rondevu.connect({
|
|
17
|
+
* apiUrl: 'https://api.ronde.vu',
|
|
18
|
+
* webrtcAdapter: adapter,
|
|
19
|
+
* })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export class NodeWebRTCAdapter {
|
|
23
|
+
constructor(polyfills) {
|
|
24
|
+
this.polyfills = polyfills;
|
|
25
|
+
}
|
|
26
|
+
createPeerConnection(config) {
|
|
27
|
+
return new this.polyfills.RTCPeerConnection(config);
|
|
28
|
+
}
|
|
29
|
+
createIceCandidate(candidateInit) {
|
|
30
|
+
return new this.polyfills.RTCIceCandidate(candidateInit);
|
|
31
|
+
}
|
|
32
|
+
}
|