@noatgnu/cupcake-mint-chocolate 1.2.9 → 1.2.11
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.
|
@@ -796,6 +796,7 @@ class WebRTCSignallingService {
|
|
|
796
796
|
this._peers.next([]);
|
|
797
797
|
}
|
|
798
798
|
sendCheck(peerRole = PeerRole.PARTICIPANT) {
|
|
799
|
+
console.log('[Signalling] Sending check with role:', peerRole);
|
|
799
800
|
this.send({
|
|
800
801
|
type: 'check',
|
|
801
802
|
peerRole: peerRole
|
|
@@ -844,6 +845,7 @@ class WebRTCSignallingService {
|
|
|
844
845
|
}
|
|
845
846
|
}
|
|
846
847
|
handleMessage(message) {
|
|
848
|
+
console.log('[Signalling] Received message:', message.type, message);
|
|
847
849
|
switch (message.type) {
|
|
848
850
|
case 'connection.established':
|
|
849
851
|
this.handleConnectionEstablished(message);
|
|
@@ -856,6 +858,7 @@ class WebRTCSignallingService {
|
|
|
856
858
|
case 'answer':
|
|
857
859
|
case 'ice_candidate':
|
|
858
860
|
case 'peer.state_update':
|
|
861
|
+
console.log('[Signalling] Forwarding message to WebRTC service:', message.type);
|
|
859
862
|
this._messages.next(message);
|
|
860
863
|
break;
|
|
861
864
|
case 'error':
|
|
@@ -868,19 +871,26 @@ class WebRTCSignallingService {
|
|
|
868
871
|
handleConnectionEstablished(message) {
|
|
869
872
|
if (message.peerId) {
|
|
870
873
|
this._peerId.next(message.peerId);
|
|
874
|
+
console.log('[Signalling] Peer ID assigned:', message.peerId);
|
|
871
875
|
}
|
|
872
876
|
if (message.sessionId) {
|
|
873
877
|
this._sessionId.next(message.sessionId);
|
|
878
|
+
console.log('[Signalling] Session ID assigned:', message.sessionId);
|
|
874
879
|
}
|
|
875
880
|
if (message.iceServers) {
|
|
876
881
|
this._iceServers.next(message.iceServers);
|
|
882
|
+
console.log('[Signalling] ICE servers received:', message.iceServers);
|
|
877
883
|
}
|
|
878
|
-
console.log('WebRTC
|
|
884
|
+
console.log('[Signalling] WebRTC connection established:', message);
|
|
879
885
|
}
|
|
880
886
|
handleCheckResponse(message) {
|
|
887
|
+
console.log('[Signalling] Received check.response with peers:', message.peers);
|
|
881
888
|
if (message.peers) {
|
|
882
889
|
this._peers.next(message.peers);
|
|
883
|
-
console.log('
|
|
890
|
+
console.log('[Signalling] Peer list updated, count:', message.peers.length);
|
|
891
|
+
}
|
|
892
|
+
else {
|
|
893
|
+
console.log('[Signalling] No peers in check.response');
|
|
884
894
|
}
|
|
885
895
|
}
|
|
886
896
|
get peerId() {
|
|
@@ -914,14 +924,20 @@ class WebRTCService {
|
|
|
914
924
|
peerConnections = new Map();
|
|
915
925
|
localStream;
|
|
916
926
|
signallingSubscription;
|
|
927
|
+
fileTransfers = new Map();
|
|
928
|
+
DEFAULT_CHUNK_SIZE = 16384;
|
|
917
929
|
_localStreamReady = new BehaviorSubject(false);
|
|
918
930
|
_remoteStreams = new BehaviorSubject(new Map());
|
|
919
931
|
_connectionState = new BehaviorSubject('disconnected');
|
|
920
932
|
_activePeers = new BehaviorSubject([]);
|
|
933
|
+
_chatMessages = new Subject();
|
|
934
|
+
_fileTransferProgress = new Subject();
|
|
921
935
|
localStreamReady$ = this._localStreamReady.asObservable();
|
|
922
936
|
remoteStreams$ = this._remoteStreams.asObservable();
|
|
923
937
|
connectionState$ = this._connectionState.asObservable();
|
|
924
938
|
activePeers$ = this._activePeers.asObservable();
|
|
939
|
+
chatMessages$ = this._chatMessages.asObservable();
|
|
940
|
+
fileTransferProgress$ = this._fileTransferProgress.asObservable();
|
|
925
941
|
defaultConfig = {
|
|
926
942
|
iceServers: [
|
|
927
943
|
{ urls: 'stun:stun.l.google.com:19302' },
|
|
@@ -956,9 +972,11 @@ class WebRTCService {
|
|
|
956
972
|
}
|
|
957
973
|
});
|
|
958
974
|
setTimeout(() => {
|
|
975
|
+
console.log('[WebRTC] Sending peer check with role:', role);
|
|
959
976
|
this.signalling.sendCheck(role);
|
|
960
977
|
}, 1000);
|
|
961
978
|
this.signalling.peers$.subscribe(peers => {
|
|
979
|
+
console.log('[WebRTC] Peers list updated:', peers);
|
|
962
980
|
this._activePeers.next(peers);
|
|
963
981
|
});
|
|
964
982
|
}
|
|
@@ -1030,9 +1048,11 @@ class WebRTCService {
|
|
|
1030
1048
|
}
|
|
1031
1049
|
async handleSignallingMessage(message) {
|
|
1032
1050
|
try {
|
|
1051
|
+
console.log('[WebRTC] Received signalling message:', message.type, message);
|
|
1033
1052
|
switch (message.type) {
|
|
1034
1053
|
case 'peer.check':
|
|
1035
1054
|
if (message.fromPeerId) {
|
|
1055
|
+
console.log('[WebRTC] Handling peer check from:', message.fromUsername, message.fromPeerId);
|
|
1036
1056
|
await this.handlePeerCheck(message.fromPeerId, message.fromUserId, message.fromUsername, message.peerRole);
|
|
1037
1057
|
}
|
|
1038
1058
|
break;
|
|
@@ -1210,6 +1230,7 @@ class WebRTCService {
|
|
|
1210
1230
|
};
|
|
1211
1231
|
dataChannel.onmessage = (event) => {
|
|
1212
1232
|
console.log(`Data received from peer ${peerId}:`, event.data);
|
|
1233
|
+
this.handleDataChannelMessage(event.data, peerId);
|
|
1213
1234
|
};
|
|
1214
1235
|
dataChannel.onclose = () => {
|
|
1215
1236
|
console.log(`Data channel closed with peer ${peerId}`);
|
|
@@ -1218,6 +1239,89 @@ class WebRTCService {
|
|
|
1218
1239
|
console.error(`Data channel error with peer ${peerId}:`, error);
|
|
1219
1240
|
};
|
|
1220
1241
|
}
|
|
1242
|
+
handleDataChannelMessage(data, peerId) {
|
|
1243
|
+
if (data instanceof ArrayBuffer) {
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
try {
|
|
1247
|
+
const message = JSON.parse(data);
|
|
1248
|
+
this.zone.run(() => {
|
|
1249
|
+
switch (message.type) {
|
|
1250
|
+
case 'chat':
|
|
1251
|
+
const chatMessage = message.data;
|
|
1252
|
+
this._chatMessages.next(chatMessage);
|
|
1253
|
+
console.log('Chat message received:', chatMessage);
|
|
1254
|
+
break;
|
|
1255
|
+
case 'file_request':
|
|
1256
|
+
this.handleFileRequest(message.data, peerId);
|
|
1257
|
+
break;
|
|
1258
|
+
case 'file_accept':
|
|
1259
|
+
this.handleFileAccept(message.data, peerId);
|
|
1260
|
+
break;
|
|
1261
|
+
case 'file_chunk':
|
|
1262
|
+
this.handleFileChunk(message.data);
|
|
1263
|
+
break;
|
|
1264
|
+
case 'file_complete':
|
|
1265
|
+
this.handleFileComplete(message.data.fileId);
|
|
1266
|
+
break;
|
|
1267
|
+
case 'file_cancel':
|
|
1268
|
+
this.handleFileCancel(message.data.fileId);
|
|
1269
|
+
break;
|
|
1270
|
+
case 'ping':
|
|
1271
|
+
this.sendDataChannelMessage(peerId, { type: 'pong', data: {} });
|
|
1272
|
+
break;
|
|
1273
|
+
case 'pong':
|
|
1274
|
+
console.log(`Received pong from peer ${peerId}`);
|
|
1275
|
+
break;
|
|
1276
|
+
default:
|
|
1277
|
+
console.log('Unknown data channel message type:', message.type);
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
catch (error) {
|
|
1282
|
+
console.error('Error parsing data channel message:', error);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
sendChatMessage(message) {
|
|
1286
|
+
const connection = this.peerConnections.values().next().value;
|
|
1287
|
+
if (!connection) {
|
|
1288
|
+
console.warn('No peer connection available');
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
const chatMessage = {
|
|
1292
|
+
type: 'chat',
|
|
1293
|
+
peerId: this.signalling.peerId || 'unknown',
|
|
1294
|
+
userId: 0,
|
|
1295
|
+
username: 'You',
|
|
1296
|
+
message: message,
|
|
1297
|
+
timestamp: new Date().toISOString()
|
|
1298
|
+
};
|
|
1299
|
+
const dataChannelMessage = {
|
|
1300
|
+
type: 'chat',
|
|
1301
|
+
data: chatMessage
|
|
1302
|
+
};
|
|
1303
|
+
this.peerConnections.forEach((conn) => {
|
|
1304
|
+
this.sendDataChannelMessage(conn.peerId, dataChannelMessage);
|
|
1305
|
+
});
|
|
1306
|
+
this._chatMessages.next(chatMessage);
|
|
1307
|
+
}
|
|
1308
|
+
sendDataChannelMessage(peerId, message) {
|
|
1309
|
+
const connection = this.peerConnections.get(peerId);
|
|
1310
|
+
if (!connection || !connection.dataChannel) {
|
|
1311
|
+
console.warn(`No data channel available for peer ${peerId}`);
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
if (connection.dataChannel.readyState !== 'open') {
|
|
1315
|
+
console.warn(`Data channel not open for peer ${peerId}, state: ${connection.dataChannel.readyState}`);
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
try {
|
|
1319
|
+
connection.dataChannel.send(JSON.stringify(message));
|
|
1320
|
+
}
|
|
1321
|
+
catch (error) {
|
|
1322
|
+
console.error(`Error sending data channel message to peer ${peerId}:`, error);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1221
1325
|
closePeerConnection(peerId) {
|
|
1222
1326
|
const connection = this.peerConnections.get(peerId);
|
|
1223
1327
|
if (!connection)
|
|
@@ -1255,6 +1359,353 @@ class WebRTCService {
|
|
|
1255
1359
|
updatePeerState(connectionState, hasVideo, hasAudio, hasScreenShare) {
|
|
1256
1360
|
this.signalling.sendPeerState(connectionState, hasVideo, hasAudio, hasScreenShare);
|
|
1257
1361
|
}
|
|
1362
|
+
offerFile(file) {
|
|
1363
|
+
const fileId = `${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
1364
|
+
const chunkSize = this.DEFAULT_CHUNK_SIZE;
|
|
1365
|
+
const totalChunks = Math.ceil(file.size / chunkSize);
|
|
1366
|
+
const fileOffer = {
|
|
1367
|
+
fileId,
|
|
1368
|
+
fileName: file.name,
|
|
1369
|
+
fileSize: file.size,
|
|
1370
|
+
fileType: file.type,
|
|
1371
|
+
chunkSize,
|
|
1372
|
+
totalChunks
|
|
1373
|
+
};
|
|
1374
|
+
const fileTransfer = {
|
|
1375
|
+
fileId,
|
|
1376
|
+
fileName: file.name,
|
|
1377
|
+
fileSize: file.size,
|
|
1378
|
+
fileType: file.type,
|
|
1379
|
+
file,
|
|
1380
|
+
chunks: new Map(),
|
|
1381
|
+
totalChunks,
|
|
1382
|
+
chunkSize,
|
|
1383
|
+
sentChunks: 0,
|
|
1384
|
+
receivedChunks: 0,
|
|
1385
|
+
status: 'pending',
|
|
1386
|
+
requestedBy: new Set()
|
|
1387
|
+
};
|
|
1388
|
+
this.fileTransfers.set(fileId, fileTransfer);
|
|
1389
|
+
const chatMessage = {
|
|
1390
|
+
type: 'chat',
|
|
1391
|
+
peerId: this.signalling.peerId || 'unknown',
|
|
1392
|
+
userId: 0,
|
|
1393
|
+
username: 'You',
|
|
1394
|
+
message: `📎 ${file.name} (${this.formatFileSize(file.size)})`,
|
|
1395
|
+
timestamp: new Date().toISOString(),
|
|
1396
|
+
fileOffer
|
|
1397
|
+
};
|
|
1398
|
+
const dataChannelMessage = {
|
|
1399
|
+
type: 'chat',
|
|
1400
|
+
data: chatMessage
|
|
1401
|
+
};
|
|
1402
|
+
this.peerConnections.forEach((conn) => {
|
|
1403
|
+
this.sendDataChannelMessage(conn.peerId, dataChannelMessage);
|
|
1404
|
+
});
|
|
1405
|
+
this._chatMessages.next(chatMessage);
|
|
1406
|
+
console.log('File offered:', fileOffer);
|
|
1407
|
+
}
|
|
1408
|
+
requestFile(fileId) {
|
|
1409
|
+
const request = {
|
|
1410
|
+
type: 'file_request',
|
|
1411
|
+
fileId,
|
|
1412
|
+
requesterId: this.signalling.peerId || 'unknown'
|
|
1413
|
+
};
|
|
1414
|
+
this.peerConnections.forEach((conn) => {
|
|
1415
|
+
this.sendDataChannelMessage(conn.peerId, {
|
|
1416
|
+
type: 'file_request',
|
|
1417
|
+
data: request
|
|
1418
|
+
});
|
|
1419
|
+
});
|
|
1420
|
+
console.log('File requested:', fileId);
|
|
1421
|
+
}
|
|
1422
|
+
handleFileRequest(request, fromPeerId) {
|
|
1423
|
+
const transfer = this.fileTransfers.get(request.fileId);
|
|
1424
|
+
if (!transfer || !transfer.file) {
|
|
1425
|
+
console.warn('File not found for request:', request.fileId);
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
transfer.requestedBy.add(fromPeerId);
|
|
1429
|
+
transfer.status = 'transferring';
|
|
1430
|
+
const accept = {
|
|
1431
|
+
type: 'file_accept',
|
|
1432
|
+
fileId: request.fileId,
|
|
1433
|
+
toPeerId: request.requesterId
|
|
1434
|
+
};
|
|
1435
|
+
this.sendDataChannelMessage(fromPeerId, {
|
|
1436
|
+
type: 'file_accept',
|
|
1437
|
+
data: accept
|
|
1438
|
+
});
|
|
1439
|
+
this.startSendingFile(request.fileId, fromPeerId);
|
|
1440
|
+
}
|
|
1441
|
+
async handleFileAccept(accept, fromPeerId) {
|
|
1442
|
+
console.log('File transfer accepted:', accept.fileId);
|
|
1443
|
+
const chatMessages = this._chatMessages._buffer || [];
|
|
1444
|
+
const relatedMessage = chatMessages.find((msg) => msg.fileOffer?.fileId === accept.fileId);
|
|
1445
|
+
if (!relatedMessage?.fileOffer) {
|
|
1446
|
+
console.error('Cannot find file offer for accepted transfer:', accept.fileId);
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
const fileOffer = relatedMessage.fileOffer;
|
|
1450
|
+
try {
|
|
1451
|
+
const downloadStream = await this.createFileDownloadStream(fileOffer.fileName);
|
|
1452
|
+
const transfer = {
|
|
1453
|
+
fileId: accept.fileId,
|
|
1454
|
+
fileName: fileOffer.fileName,
|
|
1455
|
+
fileSize: fileOffer.fileSize,
|
|
1456
|
+
fileType: fileOffer.fileType,
|
|
1457
|
+
chunks: new Map(),
|
|
1458
|
+
totalChunks: fileOffer.totalChunks,
|
|
1459
|
+
chunkSize: fileOffer.chunkSize,
|
|
1460
|
+
sentChunks: 0,
|
|
1461
|
+
receivedChunks: 0,
|
|
1462
|
+
status: 'transferring',
|
|
1463
|
+
requestedBy: new Set(),
|
|
1464
|
+
downloadStream: downloadStream,
|
|
1465
|
+
streamWriter: downloadStream.getWriter()
|
|
1466
|
+
};
|
|
1467
|
+
this.fileTransfers.set(accept.fileId, transfer);
|
|
1468
|
+
console.log('Streaming download started for:', fileOffer.fileName);
|
|
1469
|
+
}
|
|
1470
|
+
catch (error) {
|
|
1471
|
+
console.error('Failed to create download stream:', error);
|
|
1472
|
+
const transfer = {
|
|
1473
|
+
fileId: accept.fileId,
|
|
1474
|
+
fileName: fileOffer.fileName,
|
|
1475
|
+
fileSize: fileOffer.fileSize,
|
|
1476
|
+
fileType: fileOffer.fileType,
|
|
1477
|
+
chunks: new Map(),
|
|
1478
|
+
totalChunks: fileOffer.totalChunks,
|
|
1479
|
+
chunkSize: fileOffer.chunkSize,
|
|
1480
|
+
sentChunks: 0,
|
|
1481
|
+
receivedChunks: 0,
|
|
1482
|
+
status: 'transferring',
|
|
1483
|
+
requestedBy: new Set()
|
|
1484
|
+
};
|
|
1485
|
+
this.fileTransfers.set(accept.fileId, transfer);
|
|
1486
|
+
console.log('Fallback to in-memory assembly for:', fileOffer.fileName);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
async createFileDownloadStream(fileName) {
|
|
1490
|
+
if ('showSaveFilePicker' in window) {
|
|
1491
|
+
try {
|
|
1492
|
+
const handle = await window.showSaveFilePicker({
|
|
1493
|
+
suggestedName: fileName,
|
|
1494
|
+
types: [{
|
|
1495
|
+
description: 'File',
|
|
1496
|
+
accept: { '*/*': [] }
|
|
1497
|
+
}]
|
|
1498
|
+
});
|
|
1499
|
+
const writable = await handle.createWritable();
|
|
1500
|
+
return writable;
|
|
1501
|
+
}
|
|
1502
|
+
catch (error) {
|
|
1503
|
+
if (error.name === 'AbortError') {
|
|
1504
|
+
throw new Error('User cancelled file save');
|
|
1505
|
+
}
|
|
1506
|
+
throw error;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
return this.createStreamSaverFallback(fileName);
|
|
1510
|
+
}
|
|
1511
|
+
createStreamSaverFallback(fileName) {
|
|
1512
|
+
let chunks = [];
|
|
1513
|
+
return new WritableStream({
|
|
1514
|
+
write(chunk) {
|
|
1515
|
+
chunks.push(chunk);
|
|
1516
|
+
},
|
|
1517
|
+
close() {
|
|
1518
|
+
const blob = new Blob(chunks);
|
|
1519
|
+
const url = URL.createObjectURL(blob);
|
|
1520
|
+
const a = document.createElement('a');
|
|
1521
|
+
a.href = url;
|
|
1522
|
+
a.download = fileName;
|
|
1523
|
+
document.body.appendChild(a);
|
|
1524
|
+
a.click();
|
|
1525
|
+
document.body.removeChild(a);
|
|
1526
|
+
URL.revokeObjectURL(url);
|
|
1527
|
+
chunks = [];
|
|
1528
|
+
},
|
|
1529
|
+
abort(reason) {
|
|
1530
|
+
console.error('Stream aborted:', reason);
|
|
1531
|
+
chunks = [];
|
|
1532
|
+
}
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
async startSendingFile(fileId, toPeerId) {
|
|
1536
|
+
const transfer = this.fileTransfers.get(fileId);
|
|
1537
|
+
if (!transfer || !transfer.file) {
|
|
1538
|
+
console.error('File transfer not found:', fileId);
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
const file = transfer.file;
|
|
1542
|
+
let offset = 0;
|
|
1543
|
+
let chunkIndex = 0;
|
|
1544
|
+
while (offset < file.size) {
|
|
1545
|
+
const chunk = file.slice(offset, offset + transfer.chunkSize);
|
|
1546
|
+
const arrayBuffer = await chunk.arrayBuffer();
|
|
1547
|
+
const fileChunk = {
|
|
1548
|
+
type: 'file_chunk',
|
|
1549
|
+
fileId,
|
|
1550
|
+
chunkIndex,
|
|
1551
|
+
totalChunks: transfer.totalChunks,
|
|
1552
|
+
data: arrayBuffer,
|
|
1553
|
+
isLast: offset + transfer.chunkSize >= file.size
|
|
1554
|
+
};
|
|
1555
|
+
const base64Data = this.arrayBufferToBase64(arrayBuffer);
|
|
1556
|
+
this.sendDataChannelMessage(toPeerId, {
|
|
1557
|
+
type: 'file_chunk',
|
|
1558
|
+
data: {
|
|
1559
|
+
...fileChunk,
|
|
1560
|
+
data: base64Data
|
|
1561
|
+
}
|
|
1562
|
+
});
|
|
1563
|
+
transfer.sentChunks++;
|
|
1564
|
+
offset += transfer.chunkSize;
|
|
1565
|
+
chunkIndex++;
|
|
1566
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
1567
|
+
}
|
|
1568
|
+
this.sendDataChannelMessage(toPeerId, {
|
|
1569
|
+
type: 'file_complete',
|
|
1570
|
+
data: { fileId }
|
|
1571
|
+
});
|
|
1572
|
+
console.log('File sending completed:', fileId);
|
|
1573
|
+
}
|
|
1574
|
+
async handleFileChunk(chunk) {
|
|
1575
|
+
const fileId = chunk.fileId;
|
|
1576
|
+
let transfer = this.fileTransfers.get(fileId);
|
|
1577
|
+
if (!transfer) {
|
|
1578
|
+
console.warn('File transfer not found:', fileId);
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
const arrayBuffer = this.base64ToArrayBuffer(chunk.data);
|
|
1582
|
+
const uint8Array = new Uint8Array(arrayBuffer);
|
|
1583
|
+
if (transfer.streamWriter) {
|
|
1584
|
+
try {
|
|
1585
|
+
await transfer.streamWriter.write(uint8Array);
|
|
1586
|
+
console.log(`Streamed chunk ${chunk.chunkIndex + 1}/${transfer.totalChunks} directly to disk`);
|
|
1587
|
+
}
|
|
1588
|
+
catch (error) {
|
|
1589
|
+
console.error('Error writing to stream, falling back to in-memory:', error);
|
|
1590
|
+
transfer.streamWriter = undefined;
|
|
1591
|
+
transfer.chunks.set(chunk.chunkIndex, arrayBuffer);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
else {
|
|
1595
|
+
transfer.chunks.set(chunk.chunkIndex, arrayBuffer);
|
|
1596
|
+
}
|
|
1597
|
+
transfer.receivedChunks++;
|
|
1598
|
+
const progress = {
|
|
1599
|
+
fileId,
|
|
1600
|
+
fileName: transfer.fileName,
|
|
1601
|
+
fileSize: transfer.fileSize,
|
|
1602
|
+
chunksReceived: transfer.receivedChunks,
|
|
1603
|
+
totalChunks: transfer.totalChunks,
|
|
1604
|
+
bytesReceived: transfer.receivedChunks * transfer.chunkSize,
|
|
1605
|
+
percentage: (transfer.receivedChunks / transfer.totalChunks) * 100
|
|
1606
|
+
};
|
|
1607
|
+
this.zone.run(() => {
|
|
1608
|
+
this._fileTransferProgress.next(progress);
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
async handleFileComplete(fileId) {
|
|
1612
|
+
const transfer = this.fileTransfers.get(fileId);
|
|
1613
|
+
if (!transfer) {
|
|
1614
|
+
console.warn('File transfer not found:', fileId);
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
transfer.status = 'completed';
|
|
1618
|
+
if (transfer.streamWriter) {
|
|
1619
|
+
try {
|
|
1620
|
+
await transfer.streamWriter.close();
|
|
1621
|
+
console.log('File streamed to disk successfully:', transfer.fileName);
|
|
1622
|
+
}
|
|
1623
|
+
catch (error) {
|
|
1624
|
+
console.error('Error closing stream:', error);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
else {
|
|
1628
|
+
const chunks = [];
|
|
1629
|
+
for (let i = 0; i < transfer.totalChunks; i++) {
|
|
1630
|
+
const chunk = transfer.chunks.get(i);
|
|
1631
|
+
if (chunk) {
|
|
1632
|
+
chunks.push(chunk);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
const blob = new Blob(chunks, { type: transfer.fileType });
|
|
1636
|
+
const url = URL.createObjectURL(blob);
|
|
1637
|
+
const a = document.createElement('a');
|
|
1638
|
+
a.href = url;
|
|
1639
|
+
a.download = transfer.fileName;
|
|
1640
|
+
document.body.appendChild(a);
|
|
1641
|
+
a.click();
|
|
1642
|
+
document.body.removeChild(a);
|
|
1643
|
+
URL.revokeObjectURL(url);
|
|
1644
|
+
console.log('File downloaded from memory:', transfer.fileName);
|
|
1645
|
+
}
|
|
1646
|
+
this.fileTransfers.delete(fileId);
|
|
1647
|
+
}
|
|
1648
|
+
async handleFileCancel(fileId) {
|
|
1649
|
+
const transfer = this.fileTransfers.get(fileId);
|
|
1650
|
+
if (transfer) {
|
|
1651
|
+
transfer.status = 'failed';
|
|
1652
|
+
if (transfer.streamWriter) {
|
|
1653
|
+
try {
|
|
1654
|
+
await transfer.streamWriter.abort('Transfer cancelled');
|
|
1655
|
+
}
|
|
1656
|
+
catch (error) {
|
|
1657
|
+
console.error('Error aborting stream:', error);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
this.fileTransfers.delete(fileId);
|
|
1661
|
+
console.log('File transfer cancelled:', fileId);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
async cancelFileTransfer(fileId) {
|
|
1665
|
+
const transfer = this.fileTransfers.get(fileId);
|
|
1666
|
+
if (!transfer)
|
|
1667
|
+
return;
|
|
1668
|
+
if (transfer.streamWriter) {
|
|
1669
|
+
try {
|
|
1670
|
+
await transfer.streamWriter.abort('Transfer cancelled by user');
|
|
1671
|
+
}
|
|
1672
|
+
catch (error) {
|
|
1673
|
+
console.error('Error aborting stream:', error);
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
this.peerConnections.forEach((conn) => {
|
|
1677
|
+
this.sendDataChannelMessage(conn.peerId, {
|
|
1678
|
+
type: 'file_cancel',
|
|
1679
|
+
data: { fileId }
|
|
1680
|
+
});
|
|
1681
|
+
});
|
|
1682
|
+
this.fileTransfers.delete(fileId);
|
|
1683
|
+
console.log('File transfer cancelled:', fileId);
|
|
1684
|
+
}
|
|
1685
|
+
arrayBufferToBase64(buffer) {
|
|
1686
|
+
const bytes = new Uint8Array(buffer);
|
|
1687
|
+
let binary = '';
|
|
1688
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
1689
|
+
binary += String.fromCharCode(bytes[i]);
|
|
1690
|
+
}
|
|
1691
|
+
return btoa(binary);
|
|
1692
|
+
}
|
|
1693
|
+
base64ToArrayBuffer(base64) {
|
|
1694
|
+
const binary = atob(base64);
|
|
1695
|
+
const bytes = new Uint8Array(binary.length);
|
|
1696
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1697
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1698
|
+
}
|
|
1699
|
+
return bytes.buffer;
|
|
1700
|
+
}
|
|
1701
|
+
formatFileSize(bytes) {
|
|
1702
|
+
if (bytes === 0)
|
|
1703
|
+
return '0 Bytes';
|
|
1704
|
+
const k = 1024;
|
|
1705
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
1706
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
1707
|
+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
|
1708
|
+
}
|
|
1258
1709
|
get localMediaStream() {
|
|
1259
1710
|
return this.localStream;
|
|
1260
1711
|
}
|
|
@@ -1287,6 +1738,9 @@ class WebRTCSessionService extends BaseApiService {
|
|
|
1287
1738
|
createSession(data) {
|
|
1288
1739
|
return this.post(`${this.baseUrl}/`, data);
|
|
1289
1740
|
}
|
|
1741
|
+
updateSession(id, data) {
|
|
1742
|
+
return this.patch(`${this.baseUrl}/${id}/`, data);
|
|
1743
|
+
}
|
|
1290
1744
|
endSession(id) {
|
|
1291
1745
|
return this.post(`${this.baseUrl}/${id}/end_session/`, {});
|
|
1292
1746
|
}
|