skapi-js 1.0.187-beta.4 → 1.0.188-beta.1
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/dist/bundle-report-commonjs.html +2 -2
- package/dist/bundle-report.html +2 -2
- package/dist/skapi.cjs +1 -1
- package/dist/skapi.cjs.map +1 -1
- package/dist/skapi.js +1 -1
- package/dist/skapi.js.map +1 -1
- package/js/Types.d.ts +25 -12
- package/js/main/skapi.d.ts +12 -19
- package/js/main/skapi.js +5 -5
- package/js/main/skapi.js.map +1 -1
- package/js/methods/database.js +59 -19
- package/js/methods/database.js.map +1 -1
- package/js/methods/realtime.d.ts +1 -1
- package/js/methods/realtime.js +54 -21
- package/js/methods/realtime.js.map +1 -1
- package/js/methods/user.js +1 -1
- package/js/methods/user.js.map +1 -1
- package/js/methods/webrtc.d.ts +6 -6
- package/js/methods/webrtc.js +201 -181
- package/js/methods/webrtc.js.map +1 -1
- package/package.json +3 -3
package/js/methods/webrtc.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SkapiError } from "../Main";
|
|
2
|
+
import { extractFormData } from "../utils/utils";
|
|
2
3
|
import validator from "../utils/validator";
|
|
3
4
|
export const __peerConnection = {};
|
|
4
5
|
export const __dataChannel = {};
|
|
@@ -13,51 +14,62 @@ function setBuffer(buffer, recipient, item) {
|
|
|
13
14
|
}
|
|
14
15
|
buffer[recipient].push(item);
|
|
15
16
|
}
|
|
16
|
-
function processBuffer(buffer, recipient, fn) {
|
|
17
|
+
async function processBuffer(buffer, recipient, fn) {
|
|
17
18
|
let proceed = [];
|
|
18
19
|
if (buffer[recipient]) {
|
|
19
|
-
buffer[recipient]
|
|
20
|
-
if (v)
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
for (let v of buffer[recipient]) {
|
|
21
|
+
if (v) {
|
|
22
|
+
let process = fn(v);
|
|
23
|
+
if (process instanceof Promise) {
|
|
24
|
+
process = await process;
|
|
25
|
+
}
|
|
26
|
+
proceed.push(process);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
;
|
|
23
30
|
delete buffer[recipient];
|
|
24
31
|
}
|
|
25
32
|
return proceed;
|
|
26
33
|
}
|
|
27
34
|
export async function answerSdpOffer(offer, recipient) {
|
|
28
|
-
this.log('answerSdpOffer', offer);
|
|
29
35
|
if (!this?.session?.accessToken?.jwtToken) {
|
|
30
36
|
throw new SkapiError('Access token is required.', { code: 'INVALID_PARAMETER' });
|
|
31
37
|
}
|
|
32
38
|
let socket = await this.__socket;
|
|
39
|
+
async function sendAnswer(offer, recipient, socket) {
|
|
40
|
+
this.log('answerSdpOffer from', recipient);
|
|
41
|
+
await __peerConnection[recipient].setRemoteDescription(new RTCSessionDescription(offer));
|
|
42
|
+
const answer = await __peerConnection[recipient].createAnswer();
|
|
43
|
+
await __peerConnection[recipient].setLocalDescription(answer);
|
|
44
|
+
socket.send(JSON.stringify({
|
|
45
|
+
action: 'rtc',
|
|
46
|
+
uid: recipient,
|
|
47
|
+
content: { sdpanswer: answer },
|
|
48
|
+
token: this.session.accessToken.jwtToken
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
33
51
|
if (__peerConnection?.[recipient]) {
|
|
34
52
|
if (!offer) {
|
|
35
|
-
|
|
53
|
+
await processBuffer(__rtcSdpOfferBuffer, recipient, (offer) => sendAnswer.bind(this)(offer, recipient, socket));
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
await processBuffer(__rtcSdpOfferBuffer, recipient, (offer) => sendAnswer.bind(this)(offer, recipient, socket));
|
|
57
|
+
await sendAnswer.bind(this)(offer, recipient, socket);
|
|
36
58
|
}
|
|
37
59
|
}
|
|
38
60
|
else {
|
|
39
61
|
if (offer) {
|
|
40
|
-
|
|
62
|
+
setBuffer(__rtcSdpOfferBuffer, recipient, offer);
|
|
41
63
|
}
|
|
42
64
|
}
|
|
43
|
-
await Promise.all(processBuffer(__rtcSdpOfferBuffer, recipient, (offer) => answerSdpOffer.bind(this)(offer, recipient)));
|
|
44
|
-
await __peerConnection[recipient].setRemoteDescription(new RTCSessionDescription(offer));
|
|
45
|
-
const answer = await __peerConnection[recipient].createAnswer();
|
|
46
|
-
await __peerConnection[recipient].setLocalDescription(answer);
|
|
47
|
-
socket.send(JSON.stringify({
|
|
48
|
-
action: 'rtc',
|
|
49
|
-
uid: recipient,
|
|
50
|
-
content: { sdpanswer: answer },
|
|
51
|
-
token: this.session.accessToken.jwtToken
|
|
52
|
-
}));
|
|
53
65
|
}
|
|
54
66
|
export async function receiveIceCandidate(candidate, recipient) {
|
|
55
67
|
this.log('receiveIceCandidate', candidate);
|
|
56
68
|
if (__peerConnection?.[recipient] && __peerConnection[recipient]?.remoteDescription && __peerConnection[recipient]?.remoteDescription?.type) {
|
|
57
69
|
if (!candidate) {
|
|
58
|
-
return processBuffer(__rtcCandidatesBuffer, recipient, (candidate) =>
|
|
70
|
+
return processBuffer(__rtcCandidatesBuffer, recipient, (candidate) => __peerConnection[recipient].addIceCandidate(candidate));
|
|
59
71
|
}
|
|
60
|
-
await
|
|
72
|
+
await processBuffer(__rtcCandidatesBuffer, recipient, (candidate) => __peerConnection[recipient].addIceCandidate(candidate));
|
|
61
73
|
await __peerConnection[recipient].addIceCandidate(candidate);
|
|
62
74
|
}
|
|
63
75
|
else {
|
|
@@ -66,81 +78,93 @@ export async function receiveIceCandidate(candidate, recipient) {
|
|
|
66
78
|
}
|
|
67
79
|
export async function closeRTC(params) {
|
|
68
80
|
validator.Params(params, {
|
|
69
|
-
|
|
81
|
+
cid: v => {
|
|
82
|
+
if (v && typeof v !== 'string') {
|
|
83
|
+
throw new SkapiError(`"cid" should be type: <string>.`, { code: 'INVALID_PARAMETER' });
|
|
84
|
+
}
|
|
85
|
+
if (v && v.slice(0, 4) !== 'cid:') {
|
|
86
|
+
throw new SkapiError(`"cid" should be a valid connection id.`, { code: 'INVALID_PARAMETER' });
|
|
87
|
+
}
|
|
88
|
+
return v;
|
|
89
|
+
},
|
|
70
90
|
close: 'boolean'
|
|
71
91
|
});
|
|
72
92
|
let socket = await this.__socket;
|
|
73
|
-
let {
|
|
74
|
-
if (!
|
|
75
|
-
throw new SkapiError(`"
|
|
93
|
+
let { cid, close_all = false } = params || {};
|
|
94
|
+
if (!close_all && !cid) {
|
|
95
|
+
throw new SkapiError(`"cid" is required.`, { code: 'INVALID_PARAMETER' });
|
|
76
96
|
}
|
|
77
|
-
|
|
78
|
-
if (!
|
|
79
|
-
throw new SkapiError(`"
|
|
97
|
+
let close = (cid) => {
|
|
98
|
+
if (!cid) {
|
|
99
|
+
throw new SkapiError(`"cid" is required.`, { code: 'INVALID_PARAMETER' });
|
|
80
100
|
}
|
|
81
|
-
delete __rtcSdpOfferBuffer[
|
|
82
|
-
delete __rtcCandidatesBuffer[
|
|
83
|
-
if (__dataChannel[
|
|
84
|
-
Object.values(__dataChannel[
|
|
101
|
+
delete __rtcSdpOfferBuffer[cid];
|
|
102
|
+
delete __rtcCandidatesBuffer[cid];
|
|
103
|
+
if (__dataChannel[cid]) {
|
|
104
|
+
Object.values(__dataChannel[cid]).forEach(channel => {
|
|
85
105
|
if (channel.readyState !== 'closed') {
|
|
86
106
|
channel.close();
|
|
87
107
|
}
|
|
88
108
|
});
|
|
89
|
-
delete __dataChannel[recipient];
|
|
90
109
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
110
|
+
delete __dataChannel[cid];
|
|
111
|
+
if (__peerConnection?.[cid]) {
|
|
112
|
+
if (__peerConnection[cid].connectionState !== 'closed') {
|
|
113
|
+
__peerConnection[cid].close();
|
|
94
114
|
socket.send(JSON.stringify({
|
|
95
115
|
action: 'rtc',
|
|
96
|
-
uid:
|
|
116
|
+
uid: cid,
|
|
97
117
|
content: { hungup: this.user.user_id },
|
|
98
118
|
token: this.session.accessToken.jwtToken
|
|
99
119
|
}));
|
|
100
120
|
}
|
|
101
121
|
let msg = {
|
|
102
122
|
type: 'connectionstatechange',
|
|
103
|
-
target: __peerConnection[
|
|
123
|
+
target: __peerConnection[cid],
|
|
104
124
|
timestamp: new Date().toISOString(),
|
|
105
|
-
state: __peerConnection[
|
|
106
|
-
iceState: __peerConnection[
|
|
107
|
-
signalingState: __peerConnection[
|
|
125
|
+
state: __peerConnection[cid].connectionState,
|
|
126
|
+
iceState: __peerConnection[cid].iceConnectionState,
|
|
127
|
+
signalingState: __peerConnection[cid].signalingState
|
|
108
128
|
};
|
|
109
|
-
if (__rtcCallbacks[
|
|
110
|
-
__rtcCallbacks[
|
|
129
|
+
if (__rtcCallbacks[cid]) {
|
|
130
|
+
__rtcCallbacks[cid](msg);
|
|
111
131
|
}
|
|
112
|
-
delete __rtcCallbacks[recipient];
|
|
113
|
-
delete __receiver_ringing[recipient];
|
|
114
|
-
delete __caller_ringing[recipient];
|
|
115
132
|
this.log('closeRTC', msg);
|
|
116
133
|
}
|
|
117
|
-
delete
|
|
118
|
-
|
|
119
|
-
|
|
134
|
+
delete __rtcCallbacks[cid];
|
|
135
|
+
delete __receiver_ringing[cid];
|
|
136
|
+
delete __caller_ringing[cid];
|
|
137
|
+
delete __peerConnection[cid];
|
|
138
|
+
};
|
|
139
|
+
if (close_all) {
|
|
120
140
|
for (let key in __peerConnection) {
|
|
121
|
-
close
|
|
141
|
+
close(key);
|
|
122
142
|
}
|
|
123
143
|
}
|
|
124
144
|
else {
|
|
125
|
-
close
|
|
145
|
+
close(cid);
|
|
126
146
|
}
|
|
127
147
|
}
|
|
128
148
|
export async function connectRTC(params, callback) {
|
|
129
149
|
if (typeof callback !== 'function') {
|
|
130
150
|
throw new SkapiError(`Callback is required.`, { code: 'INVALID_PARAMETER' });
|
|
131
151
|
}
|
|
132
|
-
let socket = this.__socket ? await this.__socket : this.__socket;
|
|
133
|
-
if (!socket) {
|
|
134
|
-
throw new SkapiError(`No realtime connection. Execute connectRealtime() before this method.`, { code: 'INVALID_REQUEST' });
|
|
135
|
-
}
|
|
136
152
|
if (!this?.session?.accessToken?.jwtToken) {
|
|
137
153
|
throw new SkapiError('Access token is required.', { code: 'INVALID_PARAMETER' });
|
|
138
154
|
}
|
|
139
155
|
params = validator.Params(params, {
|
|
140
|
-
|
|
156
|
+
cid: v => {
|
|
157
|
+
if (typeof v !== 'string') {
|
|
158
|
+
throw new SkapiError(`"cid" should be type: <string>.`, { code: 'INVALID_PARAMETER' });
|
|
159
|
+
}
|
|
160
|
+
if (v.slice(0, 4) !== 'cid:') {
|
|
161
|
+
throw new SkapiError(`"cid" should be a valid connection id.`, { code: 'INVALID_PARAMETER' });
|
|
162
|
+
}
|
|
163
|
+
return v;
|
|
164
|
+
},
|
|
141
165
|
ice: ['string', () => 'stun:stun.skapi.com:3468'],
|
|
142
|
-
|
|
143
|
-
|
|
166
|
+
media: v => v,
|
|
167
|
+
channels: ['text-chat', 'file-transfer', 'video-chat', 'voice-chat', 'gaming', {
|
|
144
168
|
ordered: 'boolean',
|
|
145
169
|
maxPacketLifeTime: 'number',
|
|
146
170
|
maxRetransmits: 'number',
|
|
@@ -148,142 +172,140 @@ export async function connectRTC(params, callback) {
|
|
|
148
172
|
}, () => {
|
|
149
173
|
return [{ ordered: true, maxPacketLifeTime: 10, protocol: 'default' }];
|
|
150
174
|
}]
|
|
151
|
-
}, ['
|
|
152
|
-
let {
|
|
153
|
-
if (!(params?.
|
|
154
|
-
if (params?.
|
|
175
|
+
}, ['cid']);
|
|
176
|
+
let { cid, ice } = params;
|
|
177
|
+
if (!(params?.media instanceof MediaStream)) {
|
|
178
|
+
if (params?.media?.video || params?.media?.audio) {
|
|
155
179
|
if (location.hostname !== 'localhost' && location.protocol !== 'https:') {
|
|
156
180
|
throw new SkapiError(`Media stream is only supported on either localhost or https.`, { code: 'INVALID_REQUEST' });
|
|
157
181
|
}
|
|
158
182
|
}
|
|
159
183
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
|
|
184
|
+
let socket = this.__socket ? await this.__socket : this.__socket;
|
|
185
|
+
if (!socket) {
|
|
186
|
+
throw new SkapiError(`No realtime connection. Execute connectRealtime() before this method.`, { code: 'INVALID_REQUEST' });
|
|
187
|
+
}
|
|
188
|
+
if (socket.readyState !== 1) {
|
|
189
|
+
throw new SkapiError('Realtime connection is not ready.', { code: 'INVALID_REQUEST' });
|
|
190
|
+
}
|
|
191
|
+
const configuration = {
|
|
192
|
+
iceServers: [
|
|
193
|
+
{ urls: ice }
|
|
194
|
+
]
|
|
195
|
+
};
|
|
196
|
+
if (!__peerConnection?.[cid]) {
|
|
197
|
+
__peerConnection[cid] = new RTCPeerConnection(configuration);
|
|
198
|
+
}
|
|
199
|
+
if (params?.media) {
|
|
200
|
+
if (params?.media instanceof MediaStream || this.__mediaStream) {
|
|
201
|
+
this.__mediaStream = this.__mediaStream || params.media;
|
|
168
202
|
}
|
|
169
|
-
|
|
170
|
-
if (params?.
|
|
171
|
-
this.__mediaStream =
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (params?.mediaStream?.video || params?.mediaStream?.audio) {
|
|
175
|
-
this.__mediaStream = await navigator.mediaDevices.getUserMedia({
|
|
176
|
-
video: params?.mediaStream?.video,
|
|
177
|
-
audio: params?.mediaStream?.audio
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
if (this.__mediaStream)
|
|
182
|
-
this.__mediaStream.getTracks().forEach(track => {
|
|
183
|
-
__peerConnection[recipient].addTrack(track, this.__mediaStream);
|
|
203
|
+
else {
|
|
204
|
+
if (params?.media?.video || params?.media?.audio) {
|
|
205
|
+
this.__mediaStream = await navigator.mediaDevices.getUserMedia({
|
|
206
|
+
video: params?.media?.video,
|
|
207
|
+
audio: params?.media?.audio
|
|
184
208
|
});
|
|
185
|
-
}
|
|
186
|
-
__rtcCallbacks[recipient] = callback;
|
|
187
|
-
if (!__dataChannel[recipient]) {
|
|
188
|
-
__dataChannel[recipient] = {};
|
|
189
|
-
}
|
|
190
|
-
for (let i = 0; i < params.dataChannelSettings.length; i++) {
|
|
191
|
-
let options = params.dataChannelSettings[i];
|
|
192
|
-
if (typeof options === 'string') {
|
|
193
|
-
switch (options) {
|
|
194
|
-
case 'text-chat':
|
|
195
|
-
options = { ordered: true, maxRetransmits: 10, protocol: 'text-chat' };
|
|
196
|
-
break;
|
|
197
|
-
case 'file-transfer':
|
|
198
|
-
options = { ordered: false, maxPacketLifeTime: 3000, protocol: 'file-transfer' };
|
|
199
|
-
break;
|
|
200
|
-
case 'video-chat':
|
|
201
|
-
options = { ordered: true, maxPacketLifeTime: 10, protocol: 'video-chat' };
|
|
202
|
-
break;
|
|
203
|
-
case 'voice-chat':
|
|
204
|
-
options = { ordered: true, maxPacketLifeTime: 10, protocol: 'voice-chat' };
|
|
205
|
-
break;
|
|
206
|
-
default:
|
|
207
|
-
options = { ordered: true, maxRetransmits: 10, protocol: 'default' };
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
209
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
210
|
+
}
|
|
211
|
+
if (this.__mediaStream)
|
|
212
|
+
this.__mediaStream.getTracks().forEach(track => {
|
|
213
|
+
__peerConnection[cid].addTrack(track, this.__mediaStream);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
__rtcCallbacks[cid] = callback;
|
|
217
|
+
if (!__dataChannel[cid]) {
|
|
218
|
+
__dataChannel[cid] = {};
|
|
219
|
+
}
|
|
220
|
+
for (let i = 0; i < params.channels.length; i++) {
|
|
221
|
+
let options = params.channels[i];
|
|
222
|
+
if (typeof options === 'string') {
|
|
223
|
+
switch (options) {
|
|
224
|
+
case 'text-chat':
|
|
225
|
+
options = { ordered: true, maxRetransmits: 10, protocol: 'text-chat' };
|
|
226
|
+
break;
|
|
227
|
+
case 'file-transfer':
|
|
228
|
+
options = { ordered: false, maxPacketLifeTime: 3000, protocol: 'file-transfer' };
|
|
229
|
+
break;
|
|
230
|
+
case 'video-chat':
|
|
231
|
+
options = { ordered: true, maxRetransmits: 10, protocol: 'video-chat' };
|
|
232
|
+
break;
|
|
233
|
+
case 'voice-chat':
|
|
234
|
+
options = { ordered: true, maxRetransmits: 10, protocol: 'voice-chat' };
|
|
235
|
+
break;
|
|
236
|
+
case 'gaming':
|
|
237
|
+
options = { ordered: false, maxPacketLifeTime: 100, protocol: 'gaming' };
|
|
238
|
+
break;
|
|
239
|
+
default:
|
|
240
|
+
options = { ordered: true, maxRetransmits: 10, protocol: 'default' };
|
|
241
|
+
break;
|
|
214
242
|
}
|
|
215
|
-
let dataChannel = __peerConnection[recipient].createDataChannel(protocol, options);
|
|
216
|
-
__dataChannel[recipient][protocol] = dataChannel;
|
|
217
243
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
244
|
+
let protocol = options.protocol || 'default';
|
|
245
|
+
if (Object.keys(__dataChannel[cid]).includes(protocol)) {
|
|
246
|
+
throw new SkapiError(`Data channel with the protocol "${protocol}" already exists.`, { code: 'INVALID_REQUEST' });
|
|
221
247
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
248
|
+
let dataChannel = __peerConnection[cid].createDataChannel(protocol, options);
|
|
249
|
+
__dataChannel[cid][protocol] = dataChannel;
|
|
250
|
+
}
|
|
251
|
+
for (let key in __dataChannel[cid]) {
|
|
252
|
+
let dataChannel = __dataChannel[cid][key];
|
|
253
|
+
handleDataChannel.bind(this)(cid, dataChannel);
|
|
254
|
+
}
|
|
255
|
+
peerConnectionHandler.bind(this)(cid, ['onnegotiationneeded']);
|
|
256
|
+
await sendOffer.bind(this)(cid);
|
|
257
|
+
return {
|
|
258
|
+
hangup: () => __caller_ringing[cid] && __caller_ringing[cid](false),
|
|
259
|
+
connection: new Promise(resolve => {
|
|
260
|
+
__caller_ringing[cid] = ((proceed) => {
|
|
261
|
+
this.log('receiver picked up the call', cid);
|
|
227
262
|
if (!proceed) {
|
|
228
|
-
|
|
229
|
-
return;
|
|
263
|
+
closeRTC.bind(this)({ cid: cid });
|
|
264
|
+
return null;
|
|
230
265
|
}
|
|
231
|
-
__peerConnection[
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
266
|
+
__peerConnection[cid].onnegotiationneeded = () => {
|
|
267
|
+
this.log('onnegotiationneeded', `Sending offer to "${cid}".`);
|
|
268
|
+
sendOffer.bind(this)(cid);
|
|
269
|
+
if (__rtcCallbacks[cid])
|
|
270
|
+
__rtcCallbacks[cid]({
|
|
235
271
|
type: 'negotiationneeded',
|
|
236
|
-
target: __peerConnection[
|
|
272
|
+
target: __peerConnection[cid],
|
|
237
273
|
timestamp: new Date().toISOString(),
|
|
238
|
-
signalingState: __peerConnection[
|
|
239
|
-
connectionState: __peerConnection[
|
|
240
|
-
gatheringState: __peerConnection[
|
|
274
|
+
signalingState: __peerConnection[cid].signalingState,
|
|
275
|
+
connectionState: __peerConnection[cid].iceConnectionState,
|
|
276
|
+
gatheringState: __peerConnection[cid].iceGatheringState
|
|
241
277
|
});
|
|
242
|
-
}
|
|
278
|
+
};
|
|
243
279
|
resolve({
|
|
244
|
-
target: __peerConnection[
|
|
245
|
-
|
|
246
|
-
hangup: (
|
|
247
|
-
|
|
248
|
-
}).bind(this),
|
|
249
|
-
mediaStream: this.__mediaStream
|
|
280
|
+
target: __peerConnection[cid],
|
|
281
|
+
channels: __dataChannel[cid],
|
|
282
|
+
hangup: () => closeRTC.bind(this)({ cid: cid }),
|
|
283
|
+
media: this.__mediaStream
|
|
250
284
|
});
|
|
251
285
|
}).bind(this);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
throw new SkapiError('Realtime connection is not ready.', { code: 'INVALID_REQUEST' });
|
|
257
|
-
}
|
|
286
|
+
})
|
|
287
|
+
};
|
|
258
288
|
}
|
|
259
289
|
export function respondRTC(msg) {
|
|
260
290
|
return async (params, callback) => {
|
|
261
|
-
|
|
262
|
-
|
|
291
|
+
params = params || {};
|
|
292
|
+
params = extractFormData(params).data;
|
|
293
|
+
let sender = msg.sender_cid;
|
|
294
|
+
let socket = await this.__socket;
|
|
263
295
|
if (!__receiver_ringing[sender]) {
|
|
264
296
|
return null;
|
|
265
297
|
}
|
|
266
298
|
if (typeof callback !== 'function') {
|
|
267
299
|
throw new SkapiError(`Callback is required.`, { code: 'INVALID_PARAMETER' });
|
|
268
300
|
}
|
|
269
|
-
if (!(params?.
|
|
270
|
-
if (params?.
|
|
301
|
+
if (!(params?.media instanceof MediaStream)) {
|
|
302
|
+
if (params?.media?.video || params?.media?.audio) {
|
|
271
303
|
if (location.hostname !== 'localhost' && location.protocol !== 'https:') {
|
|
272
304
|
throw new SkapiError(`Media stream is only supported on either localhost or https.`, { code: 'INVALID_REQUEST' });
|
|
273
305
|
}
|
|
274
306
|
}
|
|
275
307
|
}
|
|
276
|
-
let
|
|
277
|
-
if (params?.hangup) {
|
|
278
|
-
socket.send(JSON.stringify({
|
|
279
|
-
action: 'rtc',
|
|
280
|
-
uid: sender,
|
|
281
|
-
content: { hungup: this.user.user_id },
|
|
282
|
-
token: this.session.accessToken.jwtToken
|
|
283
|
-
}));
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
let { ice = 'stun:stun.skapi.com:3468' } = params || {};
|
|
308
|
+
let { ice = 'stun:stun.skapi.com:3468' } = params;
|
|
287
309
|
if (!__peerConnection?.[sender]) {
|
|
288
310
|
__peerConnection[sender] = new RTCPeerConnection({
|
|
289
311
|
iceServers: [
|
|
@@ -291,15 +313,15 @@ export function respondRTC(msg) {
|
|
|
291
313
|
]
|
|
292
314
|
});
|
|
293
315
|
}
|
|
294
|
-
if (params?.
|
|
295
|
-
if (params?.
|
|
296
|
-
this.__mediaStream = params.
|
|
316
|
+
if (params?.media) {
|
|
317
|
+
if (params?.media instanceof MediaStream || this.__mediaStream) {
|
|
318
|
+
this.__mediaStream = this.__mediaStream || params.media;
|
|
297
319
|
}
|
|
298
320
|
else {
|
|
299
|
-
if (params?.
|
|
321
|
+
if (params?.media?.video || params?.media?.audio)
|
|
300
322
|
this.__mediaStream = await navigator.mediaDevices.getUserMedia({
|
|
301
|
-
video: params?.
|
|
302
|
-
audio: params?.
|
|
323
|
+
video: params?.media?.video,
|
|
324
|
+
audio: params?.media?.audio
|
|
303
325
|
});
|
|
304
326
|
}
|
|
305
327
|
if (this.__mediaStream)
|
|
@@ -319,8 +341,8 @@ export function respondRTC(msg) {
|
|
|
319
341
|
handleDataChannel.bind(this)(sender, dataChannel);
|
|
320
342
|
};
|
|
321
343
|
peerConnectionHandler.bind(this)(sender, ['onnegotiationneeded']);
|
|
322
|
-
await answerSdpOffer.bind(this)(
|
|
323
|
-
await receiveIceCandidate.bind(this)(
|
|
344
|
+
await answerSdpOffer.bind(this)(null, sender);
|
|
345
|
+
await receiveIceCandidate.bind(this)(null, sender);
|
|
324
346
|
socket.send(JSON.stringify({
|
|
325
347
|
action: 'rtc',
|
|
326
348
|
uid: sender,
|
|
@@ -329,11 +351,9 @@ export function respondRTC(msg) {
|
|
|
329
351
|
}));
|
|
330
352
|
return {
|
|
331
353
|
target: __peerConnection[sender],
|
|
332
|
-
|
|
333
|
-
hangup: (
|
|
334
|
-
|
|
335
|
-
}).bind(this),
|
|
336
|
-
mediaStream: this.__mediaStream
|
|
354
|
+
channels: __dataChannel[sender],
|
|
355
|
+
hangup: () => closeRTC.bind(this)({ cid: sender }),
|
|
356
|
+
media: this.__mediaStream
|
|
337
357
|
};
|
|
338
358
|
};
|
|
339
359
|
}
|
|
@@ -341,12 +361,12 @@ async function sendOffer(recipient) {
|
|
|
341
361
|
if (!this?.session?.accessToken?.jwtToken) {
|
|
342
362
|
throw new SkapiError('Access token is required.', { code: 'INVALID_PARAMETER' });
|
|
343
363
|
}
|
|
364
|
+
this.log('sendOffer', recipient);
|
|
344
365
|
let socket = await this.__socket;
|
|
345
366
|
const offer = await __peerConnection[recipient].createOffer();
|
|
346
367
|
await __peerConnection[recipient].setLocalDescription(offer);
|
|
347
368
|
let sdpoffer = __peerConnection[recipient].localDescription;
|
|
348
|
-
this.log('rtcSdpOffer', sdpoffer);
|
|
349
|
-
validator.UserId(recipient);
|
|
369
|
+
this.log('rtcSdpOffer to:', sdpoffer);
|
|
350
370
|
socket.send(JSON.stringify({
|
|
351
371
|
action: 'rtc',
|
|
352
372
|
uid: recipient,
|
|
@@ -355,10 +375,10 @@ async function sendOffer(recipient) {
|
|
|
355
375
|
}));
|
|
356
376
|
}
|
|
357
377
|
async function sendIceCandidate(event, recipient) {
|
|
358
|
-
if (this?.session?.accessToken?.jwtToken) {
|
|
378
|
+
if (!this?.session?.accessToken?.jwtToken) {
|
|
359
379
|
throw new SkapiError('Access token is required.', { code: 'INVALID_PARAMETER' });
|
|
360
380
|
}
|
|
361
|
-
|
|
381
|
+
this.log('sendIceCandidate to:', recipient);
|
|
362
382
|
let socket = await this.__socket;
|
|
363
383
|
if (!event.candidate) {
|
|
364
384
|
this.log('candidate-end', 'All ICE candidates have been sent');
|
|
@@ -485,13 +505,13 @@ function peerConnectionHandler(key, skipKey) {
|
|
|
485
505
|
});
|
|
486
506
|
let state = peer.connectionState;
|
|
487
507
|
if (state === 'disconnected' || state === 'failed' || state === 'closed') {
|
|
488
|
-
closeRTC.bind(this)({
|
|
508
|
+
closeRTC.bind(this)({ cid: key });
|
|
489
509
|
}
|
|
490
510
|
}
|
|
491
511
|
};
|
|
492
512
|
for (const [event, handler] of Object.entries(handlers)) {
|
|
493
513
|
if (!skip.has(event)) {
|
|
494
|
-
peer[event] = handler
|
|
514
|
+
peer[event] = handler;
|
|
495
515
|
}
|
|
496
516
|
}
|
|
497
517
|
}
|
|
@@ -540,7 +560,7 @@ function handleDataChannel(key, dataChannel, skipKey) {
|
|
|
540
560
|
if (__dataChannel[key]) {
|
|
541
561
|
delete __dataChannel[key][dataChannel.label];
|
|
542
562
|
if (Object.keys(__dataChannel[key]).length === 0) {
|
|
543
|
-
closeRTC.bind(this)({
|
|
563
|
+
closeRTC.bind(this)({ cid: key });
|
|
544
564
|
}
|
|
545
565
|
}
|
|
546
566
|
},
|
|
@@ -573,7 +593,7 @@ function handleDataChannel(key, dataChannel, skipKey) {
|
|
|
573
593
|
};
|
|
574
594
|
for (const [event, handler] of Object.entries(events)) {
|
|
575
595
|
if (!skip.has(event)) {
|
|
576
|
-
dataChannel[event] = handler
|
|
596
|
+
dataChannel[event] = handler;
|
|
577
597
|
}
|
|
578
598
|
}
|
|
579
599
|
}
|