@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/api/batcher.js
CHANGED
|
@@ -1,111 +1,155 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* RPC Batcher
|
|
2
|
+
* RPC Request Batcher with throttling
|
|
3
|
+
*
|
|
4
|
+
* Collects RPC requests over a short time window and sends them efficiently.
|
|
5
|
+
*
|
|
6
|
+
* Due to server authentication design (signature covers method+params),
|
|
7
|
+
* authenticated requests are sent individually while unauthenticated
|
|
8
|
+
* requests can be truly batched together.
|
|
3
9
|
*/
|
|
4
10
|
/**
|
|
5
|
-
*
|
|
11
|
+
* RpcBatcher - Batches RPC requests with throttling
|
|
6
12
|
*
|
|
7
13
|
* @example
|
|
8
14
|
* ```typescript
|
|
9
|
-
* const batcher = new RpcBatcher(
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* )
|
|
15
|
+
* const batcher = new RpcBatcher('https://api.example.com', {
|
|
16
|
+
* delay: 10,
|
|
17
|
+
* maxBatchSize: 50
|
|
18
|
+
* })
|
|
13
19
|
*
|
|
14
|
-
* //
|
|
15
|
-
* const result1 = await
|
|
16
|
-
*
|
|
17
|
-
*
|
|
20
|
+
* // Requests made within the delay window are batched
|
|
21
|
+
* const [result1, result2] = await Promise.all([
|
|
22
|
+
* batcher.add({ method: 'getOffer', params: {...} }, null),
|
|
23
|
+
* batcher.add({ method: 'getOffer', params: {...} }, null)
|
|
24
|
+
* ])
|
|
18
25
|
* ```
|
|
19
26
|
*/
|
|
20
27
|
export class RpcBatcher {
|
|
21
|
-
constructor(
|
|
28
|
+
constructor(baseUrl, options = {}) {
|
|
29
|
+
this.baseUrl = baseUrl;
|
|
22
30
|
this.queue = [];
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
25
|
-
this.
|
|
26
|
-
this.options = {
|
|
27
|
-
maxBatchSize: options?.maxBatchSize ?? 10,
|
|
28
|
-
maxWaitTime: options?.maxWaitTime ?? 50,
|
|
29
|
-
throttleInterval: options?.throttleInterval ?? 10,
|
|
30
|
-
};
|
|
31
|
+
this.flushTimer = null;
|
|
32
|
+
this.delay = options.delay ?? 10;
|
|
33
|
+
this.maxBatchSize = options.maxBatchSize ?? 50;
|
|
31
34
|
}
|
|
32
35
|
/**
|
|
33
|
-
* Add
|
|
34
|
-
*
|
|
36
|
+
* Add a request to the batch queue
|
|
37
|
+
* @param request - The RPC request
|
|
38
|
+
* @param authHeaders - Auth headers for authenticated requests, null for unauthenticated
|
|
39
|
+
* @returns Promise that resolves with the request result
|
|
35
40
|
*/
|
|
36
|
-
|
|
41
|
+
add(request, authHeaders) {
|
|
37
42
|
return new Promise((resolve, reject) => {
|
|
38
|
-
this.queue.push({ request, resolve, reject });
|
|
39
|
-
|
|
40
|
-
if (this.queue.length >= this.options.maxBatchSize) {
|
|
41
|
-
this.flush();
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
// Schedule batch if not already scheduled
|
|
45
|
-
if (!this.batchTimeout) {
|
|
46
|
-
this.batchTimeout = setTimeout(() => {
|
|
47
|
-
this.flush();
|
|
48
|
-
}, this.options.maxWaitTime);
|
|
49
|
-
}
|
|
43
|
+
this.queue.push({ request, authHeaders, resolve, reject });
|
|
44
|
+
this.scheduleFlush();
|
|
50
45
|
});
|
|
51
46
|
}
|
|
52
47
|
/**
|
|
53
|
-
*
|
|
48
|
+
* Schedule a flush after the delay
|
|
49
|
+
*/
|
|
50
|
+
scheduleFlush() {
|
|
51
|
+
if (this.flushTimer)
|
|
52
|
+
return;
|
|
53
|
+
this.flushTimer = setTimeout(() => {
|
|
54
|
+
this.flushTimer = null;
|
|
55
|
+
this.flush();
|
|
56
|
+
}, this.delay);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Flush all queued requests
|
|
54
60
|
*/
|
|
55
61
|
async flush() {
|
|
56
|
-
|
|
57
|
-
if (this.batchTimeout) {
|
|
58
|
-
clearTimeout(this.batchTimeout);
|
|
59
|
-
this.batchTimeout = null;
|
|
60
|
-
}
|
|
61
|
-
// Nothing to flush
|
|
62
|
-
if (this.queue.length === 0) {
|
|
62
|
+
if (this.queue.length === 0)
|
|
63
63
|
return;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
// Extract requests from queue
|
|
73
|
-
const batch = this.queue.splice(0, this.options.maxBatchSize);
|
|
74
|
-
const requests = batch.map(item => item.request);
|
|
75
|
-
this.lastBatchTime = Date.now();
|
|
76
|
-
try {
|
|
77
|
-
// Send batch request
|
|
78
|
-
const results = await this.sendBatch(requests);
|
|
79
|
-
// Resolve individual promises
|
|
80
|
-
for (let i = 0; i < batch.length; i++) {
|
|
81
|
-
batch[i].resolve(results[i]);
|
|
64
|
+
const items = this.queue;
|
|
65
|
+
this.queue = [];
|
|
66
|
+
// Separate authenticated vs unauthenticated requests
|
|
67
|
+
const unauthenticated = [];
|
|
68
|
+
const authenticated = [];
|
|
69
|
+
for (const item of items) {
|
|
70
|
+
if (item.authHeaders) {
|
|
71
|
+
authenticated.push(item);
|
|
82
72
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// Reject all promises in batch
|
|
86
|
-
for (const item of batch) {
|
|
87
|
-
item.reject(error);
|
|
73
|
+
else {
|
|
74
|
+
unauthenticated.push(item);
|
|
88
75
|
}
|
|
89
76
|
}
|
|
77
|
+
// Process unauthenticated requests in batches
|
|
78
|
+
await this.processUnauthenticatedBatches(unauthenticated);
|
|
79
|
+
// Process authenticated requests individually (each needs unique signature)
|
|
80
|
+
await this.processAuthenticatedRequests(authenticated);
|
|
90
81
|
}
|
|
91
82
|
/**
|
|
92
|
-
*
|
|
83
|
+
* Process unauthenticated requests in batches
|
|
93
84
|
*/
|
|
94
|
-
|
|
95
|
-
|
|
85
|
+
async processUnauthenticatedBatches(items) {
|
|
86
|
+
if (items.length === 0)
|
|
87
|
+
return;
|
|
88
|
+
// Split into chunks of maxBatchSize
|
|
89
|
+
for (let i = 0; i < items.length; i += this.maxBatchSize) {
|
|
90
|
+
const chunk = items.slice(i, i + this.maxBatchSize);
|
|
91
|
+
await this.sendBatch(chunk, null);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Process authenticated requests individually
|
|
96
|
+
* Each authenticated request needs its own HTTP call because
|
|
97
|
+
* the signature covers the specific method+params
|
|
98
|
+
*/
|
|
99
|
+
async processAuthenticatedRequests(items) {
|
|
100
|
+
// Send all authenticated requests in parallel, each as its own batch of 1
|
|
101
|
+
await Promise.all(items.map(item => this.sendBatch([item], item.authHeaders)));
|
|
96
102
|
}
|
|
97
103
|
/**
|
|
98
|
-
*
|
|
104
|
+
* Send a batch of requests
|
|
99
105
|
*/
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
async sendBatch(items, authHeaders) {
|
|
107
|
+
try {
|
|
108
|
+
const requests = items.map(item => item.request);
|
|
109
|
+
const headers = {
|
|
110
|
+
'Content-Type': 'application/json',
|
|
111
|
+
};
|
|
112
|
+
if (authHeaders) {
|
|
113
|
+
Object.assign(headers, authHeaders);
|
|
114
|
+
}
|
|
115
|
+
const response = await fetch(`${this.baseUrl}/rpc`, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers,
|
|
118
|
+
body: JSON.stringify(requests), // Always send as array
|
|
119
|
+
});
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
122
|
+
items.forEach(item => item.reject(error));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const results = await response.json();
|
|
126
|
+
// Match responses to requests (server returns array in same order)
|
|
127
|
+
items.forEach((item, index) => {
|
|
128
|
+
const result = results[index];
|
|
129
|
+
if (!result) {
|
|
130
|
+
item.reject(new Error('Missing response from server'));
|
|
131
|
+
}
|
|
132
|
+
else if (!result.success) {
|
|
133
|
+
item.reject(new Error(result.error || 'RPC call failed'));
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
item.resolve(result.result);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
104
139
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
item.reject(
|
|
140
|
+
catch (error) {
|
|
141
|
+
// Network or parsing error - reject all
|
|
142
|
+
items.forEach(item => item.reject(error));
|
|
108
143
|
}
|
|
109
|
-
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Flush immediately (useful for cleanup/testing)
|
|
147
|
+
*/
|
|
148
|
+
async flushNow() {
|
|
149
|
+
if (this.flushTimer) {
|
|
150
|
+
clearTimeout(this.flushTimer);
|
|
151
|
+
this.flushTimer = null;
|
|
152
|
+
}
|
|
153
|
+
await this.flush();
|
|
110
154
|
}
|
|
111
155
|
}
|
package/dist/api/client.d.ts
CHANGED
|
@@ -1,29 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Rondevu API Client - RPC interface
|
|
3
3
|
*/
|
|
4
|
-
import { CryptoAdapter,
|
|
4
|
+
import { CryptoAdapter, Credential } from '../crypto/adapter.js';
|
|
5
5
|
import { BatcherOptions } from './batcher.js';
|
|
6
|
-
export type {
|
|
6
|
+
export type { Credential } from '../crypto/adapter.js';
|
|
7
7
|
export type { BatcherOptions } from './batcher.js';
|
|
8
8
|
export interface OfferRequest {
|
|
9
9
|
sdp: string;
|
|
10
10
|
}
|
|
11
|
-
export interface
|
|
12
|
-
|
|
11
|
+
export interface PublishRequest {
|
|
12
|
+
tags: string[];
|
|
13
13
|
offers: OfferRequest[];
|
|
14
14
|
ttl?: number;
|
|
15
15
|
}
|
|
16
|
-
export interface
|
|
16
|
+
export interface DiscoverRequest {
|
|
17
|
+
tags: string[];
|
|
18
|
+
limit?: number;
|
|
19
|
+
offset?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface TaggedOffer {
|
|
17
22
|
offerId: string;
|
|
23
|
+
username: string;
|
|
24
|
+
tags: string[];
|
|
18
25
|
sdp: string;
|
|
19
26
|
createdAt: number;
|
|
20
27
|
expiresAt: number;
|
|
21
28
|
}
|
|
22
|
-
export interface
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
export interface DiscoverResponse {
|
|
30
|
+
offers: TaggedOffer[];
|
|
31
|
+
count: number;
|
|
32
|
+
limit: number;
|
|
33
|
+
offset: number;
|
|
34
|
+
}
|
|
35
|
+
export interface PublishResponse {
|
|
25
36
|
username: string;
|
|
26
|
-
|
|
37
|
+
tags: string[];
|
|
38
|
+
offers: Array<{
|
|
39
|
+
offerId: string;
|
|
40
|
+
sdp: string;
|
|
41
|
+
createdAt: number;
|
|
42
|
+
expiresAt: number;
|
|
43
|
+
}>;
|
|
27
44
|
createdAt: number;
|
|
28
45
|
expiresAt: number;
|
|
29
46
|
}
|
|
@@ -37,84 +54,111 @@ export interface IceCandidate {
|
|
|
37
54
|
*/
|
|
38
55
|
export declare class RondevuAPI {
|
|
39
56
|
private baseUrl;
|
|
40
|
-
private
|
|
41
|
-
private
|
|
57
|
+
private credential;
|
|
58
|
+
private static readonly DEFAULT_MAX_RETRIES;
|
|
59
|
+
private static readonly DEFAULT_TIMEOUT_MS;
|
|
60
|
+
private static readonly DEFAULT_CREDENTIAL_NAME_MAX_LENGTH;
|
|
61
|
+
private static readonly DEFAULT_SECRET_MIN_LENGTH;
|
|
62
|
+
private static readonly MAX_BACKOFF_MS;
|
|
63
|
+
private static readonly MAX_CANONICALIZE_DEPTH;
|
|
42
64
|
private crypto;
|
|
43
65
|
private batcher;
|
|
44
|
-
constructor(baseUrl: string,
|
|
66
|
+
constructor(baseUrl: string, credential: Credential, cryptoAdapter?: CryptoAdapter, batcherOptions?: BatcherOptions);
|
|
45
67
|
/**
|
|
46
|
-
*
|
|
68
|
+
* Canonical JSON serialization with sorted keys
|
|
69
|
+
* Ensures deterministic output regardless of property insertion order
|
|
47
70
|
*/
|
|
48
71
|
private canonicalJSON;
|
|
49
72
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
73
|
+
* Build signature message following server format
|
|
74
|
+
* Format: timestamp:nonce:method:canonicalJSON(params || {})
|
|
75
|
+
*
|
|
76
|
+
* Uses canonical JSON (sorted keys) to ensure deterministic serialization
|
|
77
|
+
* across different JavaScript engines and platforms.
|
|
78
|
+
*
|
|
79
|
+
* Note: When params is undefined, it's serialized as "{}" (empty object).
|
|
80
|
+
* This matches the server's expectation for parameterless RPC calls.
|
|
52
81
|
*/
|
|
53
|
-
private
|
|
82
|
+
private buildSignatureMessage;
|
|
54
83
|
/**
|
|
55
|
-
* Generate
|
|
56
|
-
*
|
|
84
|
+
* Generate cryptographically secure nonce
|
|
85
|
+
* Uses crypto.randomUUID() if available, falls back to secure random bytes
|
|
86
|
+
*
|
|
87
|
+
* Note: this.crypto is always initialized in constructor (WebCryptoAdapter or NodeCryptoAdapter)
|
|
88
|
+
* and TypeScript enforces that both implement randomBytes(), so the fallback is always safe.
|
|
57
89
|
*/
|
|
58
|
-
private
|
|
59
|
-
/**
|
|
60
|
-
* Execute RPC call with optional batching
|
|
61
|
-
*/
|
|
62
|
-
private rpc;
|
|
90
|
+
private generateNonce;
|
|
63
91
|
/**
|
|
64
|
-
*
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Sign a message with an Ed25519 private key
|
|
79
|
-
* @param cryptoAdapter - Optional crypto adapter (defaults to WebCryptoAdapter)
|
|
92
|
+
* Generate authentication headers for RPC request
|
|
93
|
+
* Uses HMAC-SHA256 signature with nonce for replay protection
|
|
94
|
+
*
|
|
95
|
+
* Security notes:
|
|
96
|
+
* - Nonce: Cryptographically secure random value (UUID or 128-bit hex)
|
|
97
|
+
* - Timestamp: Prevents replay attacks outside the server's time window
|
|
98
|
+
* - Server validates timestamp is within acceptable range (typically ±5 minutes)
|
|
99
|
+
* - Tolerates reasonable clock skew between client and server
|
|
100
|
+
* - Requests with stale timestamps are rejected
|
|
101
|
+
* - Signature: HMAC-SHA256 ensures message integrity and authenticity
|
|
102
|
+
* - Server validates nonce uniqueness to prevent replay within time window
|
|
103
|
+
* - Each nonce can only be used once within the timestamp validity window
|
|
104
|
+
* - Server maintains nonce cache with expiration matching timestamp window
|
|
80
105
|
*/
|
|
81
|
-
|
|
106
|
+
private generateAuthHeaders;
|
|
82
107
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
108
|
+
* Execute RPC call via batcher
|
|
109
|
+
* Requests are batched with throttling for efficiency
|
|
85
110
|
*/
|
|
86
|
-
|
|
111
|
+
private rpc;
|
|
87
112
|
/**
|
|
88
|
-
*
|
|
113
|
+
* Generate new credentials (name + secret pair)
|
|
114
|
+
* This is the entry point for new users - no authentication required
|
|
115
|
+
* Credentials are generated server-side to ensure security and uniqueness
|
|
116
|
+
*
|
|
117
|
+
* ⚠️ SECURITY NOTE:
|
|
118
|
+
* - Store the returned credential securely
|
|
119
|
+
* - The secret provides full access to this identity
|
|
120
|
+
* - Credentials should be persisted encrypted and never logged
|
|
121
|
+
*
|
|
122
|
+
* @param baseUrl - Rondevu server URL
|
|
123
|
+
* @param expiresAt - Optional custom expiry timestamp (defaults to 1 year)
|
|
124
|
+
* @param options - Optional: { maxRetries: number, timeout: number }
|
|
125
|
+
* @returns Generated credential with name and secret
|
|
89
126
|
*/
|
|
90
|
-
|
|
127
|
+
static generateCredentials(baseUrl: string, options?: {
|
|
128
|
+
name?: string;
|
|
129
|
+
expiresAt?: number;
|
|
130
|
+
maxRetries?: number;
|
|
131
|
+
timeout?: number;
|
|
132
|
+
}): Promise<Credential>;
|
|
91
133
|
/**
|
|
92
|
-
*
|
|
134
|
+
* Generate a random secret locally (for advanced use cases)
|
|
135
|
+
* @param cryptoAdapter - Optional crypto adapter
|
|
93
136
|
*/
|
|
94
|
-
|
|
137
|
+
static generateSecret(cryptoAdapter?: CryptoAdapter): string;
|
|
95
138
|
/**
|
|
96
|
-
* Publish
|
|
139
|
+
* Publish offers with tags
|
|
97
140
|
*/
|
|
98
|
-
|
|
141
|
+
publish(request: PublishRequest): Promise<PublishResponse>;
|
|
99
142
|
/**
|
|
100
|
-
*
|
|
143
|
+
* Discover offers by tags
|
|
144
|
+
* @param request - Discovery request with tags and optional pagination
|
|
145
|
+
* @returns Paginated response if limit provided, single offer if not
|
|
101
146
|
*/
|
|
102
|
-
|
|
103
|
-
limit?: number;
|
|
104
|
-
offset?: number;
|
|
105
|
-
}): Promise<any>;
|
|
147
|
+
discover(request: DiscoverRequest): Promise<DiscoverResponse | TaggedOffer>;
|
|
106
148
|
/**
|
|
107
|
-
* Delete
|
|
149
|
+
* Delete an offer by ID
|
|
108
150
|
*/
|
|
109
|
-
|
|
151
|
+
deleteOffer(offerId: string): Promise<{
|
|
152
|
+
success: boolean;
|
|
153
|
+
}>;
|
|
110
154
|
/**
|
|
111
155
|
* Answer an offer
|
|
112
156
|
*/
|
|
113
|
-
answerOffer(
|
|
157
|
+
answerOffer(offerId: string, sdp: string): Promise<void>;
|
|
114
158
|
/**
|
|
115
159
|
* Get answer for a specific offer (offerer polls this)
|
|
116
160
|
*/
|
|
117
|
-
getOfferAnswer(
|
|
161
|
+
getOfferAnswer(offerId: string): Promise<{
|
|
118
162
|
sdp: string;
|
|
119
163
|
offerId: string;
|
|
120
164
|
answererId: string;
|
|
@@ -126,7 +170,6 @@ export declare class RondevuAPI {
|
|
|
126
170
|
poll(since?: number): Promise<{
|
|
127
171
|
answers: Array<{
|
|
128
172
|
offerId: string;
|
|
129
|
-
serviceId?: string;
|
|
130
173
|
answererId: string;
|
|
131
174
|
sdp: string;
|
|
132
175
|
answeredAt: number;
|
|
@@ -141,14 +184,14 @@ export declare class RondevuAPI {
|
|
|
141
184
|
/**
|
|
142
185
|
* Add ICE candidates to a specific offer
|
|
143
186
|
*/
|
|
144
|
-
addOfferIceCandidates(
|
|
187
|
+
addOfferIceCandidates(offerId: string, candidates: RTCIceCandidateInit[]): Promise<{
|
|
145
188
|
count: number;
|
|
146
189
|
offerId: string;
|
|
147
190
|
}>;
|
|
148
191
|
/**
|
|
149
192
|
* Get ICE candidates for a specific offer
|
|
150
193
|
*/
|
|
151
|
-
getOfferIceCandidates(
|
|
194
|
+
getOfferIceCandidates(offerId: string, since?: number): Promise<{
|
|
152
195
|
candidates: IceCandidate[];
|
|
153
196
|
offerId: string;
|
|
154
197
|
}>;
|