@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 signalling connection established:', message);
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('Received peer list:', message.peers);
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
  }