This repository has been archived by the owner on Nov 26, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathClient.js
258 lines (238 loc) · 8.07 KB
/
Client.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
//Get worker url
const url = new URL(import.meta.url).pathname.replace("Client","Worker");
/**
* SFrame library
* @namespace Sframe
*/
export const SFrame =
{
/**
* Create a new SFrame client context.
*
* This method will create client which communicates with web worker in which the SFrame context will be executed.
* @memberof SFrame
* @param {Number} senderId - Numeric id for this sender.
* @param {Object} config - Congiguration parameters [Optional].
* @param {Boolean} config.skipVp8PayloadHeader - Sends the vp8 payload header in clear (Note: it will be applied to all video frames as encoded chunks does not contain codec info yet).
* @returns {Promise<Client>} Promise that resolves to the client object when the web worker is initialized.
*/
createClient : async function(senderId,config)
{
//Create client
const client = new Client();
//Init worker async
await client.init(senderId, config);
//Return client
return client;
}
};
async function transferKey(key)
{
if (key instanceof CryptoKey && key.type=="private")
return await crypto.subtle.exportKey("pkcs8", key);
if (key instanceof CryptoKey)
return await crypto.subtle.exportKey("raw", key);
if (key instanceof Uint8Array)
return key.buffer.slice(0);
return key.slice(0);
}
/**
* The SFrame client object which acts as a proxy for web worker context.
*/
class Client extends EventTarget
{
/**
* @ignore
* @hideconstructor
* private constructor
*/
constructor()
{
super();
//Create new worker
this.worker = new Worker(url, {type: "module"});
//Cutrent transactions
this.transId = 1;
this.transactions = new Map();
//Listen for worker messages
this.worker.addEventListener("message",async (event)=>{
//Get data
const data = event.data;
//If it is a transaction response
if (data.transId)
{
//Get transaction
const transaction = this.transactions.get(data.transId);
//Delete transaction
this.transactions.delete(data.transId);
//Check result
if (data.error)
//Reject with error
transaction.reject(new Error(data.error));
else
//Resolve promise
transaction.resolve(data.result);
} else if (data.event) {
/**
* The authenticated event will be fired when a new sender is received on the receiver.
*
* @name "authenticated"
* @memberof Client
* @kind event
* @argument {String} id - The id for the associated RTCRtpReceiver
* @argument {Number} senderId - The senderId of the authenticated sender received.
*/
//Create event
const event = new Event(data.event.name);
//Set id and senderId
event.id = data.event.data.id;
event.senderId = data.event.data.senderId;
//Disptach event
this.dispatchEvent(event);
}
});
//Private method
this.postMessage = (cmd, args, transferList)=>{
//Create new promise
return new Promise((resolve,reject)=>{
//Get new transaction
const transId = this.transId++;
//Sent to worker
this.worker.postMessage({transId,cmd,args},transferList);
//Add it to pending transactions
this.transactions.set(transId,{resolve,reject});
});
};
}
async init(senderId, config)
{
return this.postMessage("init", {senderId, config});
}
/**
* Set the sender encryption key.
*
* @param {ArrayBuffer|Uint8Array|CryptoKey} key - 32 bytes encryption key. If the value is a CryptoKey the algorithm must be "HKDF".
* @returns {Promise<void>} Promise which will be resolved when the key is set on the web worker.
*/
async setSenderEncryptionKey(key)
{
const transfered = await transferKey(key);
return this.postMessage("setSenderEncryptionKey", [transfered], [transfered]);
}
/**
* Ratchert the sender encryption key.
*
* @returns {Promise<void>} Promise which will be resolved when the key is ratcheted on the web worker.
*/
async ratchetSenderEncryptionKey()
{
return this.postMessage("ratchetSenderEncryptionKey");
}
/**
* Set the sender signing key.
*
* @param {ArrayBuffer|Uint8Array|CryptoKey} key - Private key used for singing. If the value is a CryptoKey the algorithm must be "ECDSA".
* @returns {Promise<void>} Promise which will be resolved when the signing key is set on the web worker.
*/
async setSenderSigningKey(key)
{
const transfered = await transferKey(key);
return this.postMessage("setSenderSigningKey", [transfered]);
}
/**
* Add receiver for a remote sender.
*
* @param {Number} receiverkKeyId - The remote senderId.
* @returns {Promise<void>} Promise which will be resolved when the receiver is added on the web worker.
*/
async addReceiver(receiverkKeyId)
{
return this.postMessage("addReceiver", [receiverkKeyId]);
}
/**
* Set the receiver encryption key associated to a remote sender.
*
* @param {Number} receiverkKeyId - The remote senderId.
* @param {ArrayBuffer|Uint8Array|CryptoKey} key - 32 bytes encryption key. If the value is a CryptoKey the algorithm must be "HKDF".
* @returns {Promise<void>} Promise which will be resolved when the key is set on the web worker.
*/
async setReceiverEncryptionKey(receiverkKeyId,key)
{
const transfered = await transferKey(key);
return this.postMessage("setReceiverEncryptionKey", [receiverkKeyId, transfered], [transfered]);
}
/**
* Set the receiver signing key associated to a remote sender.
*
* @param {Number} receiverkKeyId - The remote senderId.
* @param {ArrayBuffer|Uint8Array|CryptoKey} key - Private key used for singing. If the value is a CryptoKey the algorithm must be "ECDSA".
* @returns {Promise<void>} Promise which will be resolved when the signing key is set on the web worker.
*/
async setReceiverVerifyKey(receiverkKeyId,key)
{
const transfered = await transferKey(key);
return this.postMessage("setReceiverVerifyKey", [receiverkKeyId, transfered], [transfered]);
}
/**
* Remove receiver for a remote sender.
*
* @param {Number} receiverkKeyId - The remote senderId.
* @returns {Promise<void>} Promise which will be resolved when the receiver is removed on the web worker.
*/
deleteReceiver(receiverkKeyId)
{
return this.postMessage("deleteReceiver", [receiverkKeyId]);
}
/**
* Encrypt frames for a RTCRtpSender.
*
* @param {String} id - An unique identifier associated to this sender (for example transceiver.mid).
* @param {RTCRtpSender} sender - The sender object, associated track must be not null.
*/
async encrypt(id,sender)
{
//We need the media kind until it is set as metadata on the chunk frame
const kind = sender.track.kind;
//Get the sender insertable streams
const {readable: readableStream, writable: writableStream} = sender.createEncodedStreams ? sender.createEncodedStreams() :
sender.createEncodedVideoStreams ? sender.createEncodedVideoStreams() : sender.createEncodedAudioStreams();
//Pass to worker
return this.postMessage("encrypt",
{id, kind, readableStream, writableStream},
[readableStream, writableStream]
);
}
/**
* Decrypt frames fpr a RTCPRtpReceiver.
*
* @param {String} id - An unique identifier associated to this sender (for example transceiver.mid), it will be used for the authentication and signing events.
* @param {RTCRtpReceiver} receiver - The receiver object.
*/
async decrypt(id,receiver)
{
//We need the media kind until it is set as metadata on the chunk frame
const kind = receiver.track.kind;
//Get the receiver insertable streams
const {readable: readableStream, writable: writableStream} = receiver.createEncodedStreams ? receiver.createEncodedStreams() :
receiver.createEncodedVideoStreams ? receiver.createEncodedVideoStreams() : receiver.createEncodedAudioStreams();
//Pass to worker
return this.postMessage("decrypt",
{id, kind, readableStream, writableStream},
[readableStream, writableStream]
);
}
/**
* Close client and terminate web worker.
*/
close()
{
//Terminate worker
this.worker.terminate();
//End all pending transactions
for (let transaction of this.transactions.values())
//Reject with terminated error
transaction.reject(new Error("Client closed"));
//Clear transactions
this.transactions.clear();
}
};