2018-09-21 16:05:03 +02:00
class ServerConnection {
constructor ( ) {
2022-12-30 23:31:58 +01:00
Events . on ( 'pagehide' , _ => this . _disconnect ( ) ) ;
2023-10-31 19:08:20 +01:00
Events . on ( window . visibilityChangeEvent , _ => this . _onVisibilityChange ( ) ) ;
if ( navigator . connection ) {
navigator . connection . addEventListener ( 'change' , _ => this . _reconnect ( ) ) ;
}
2023-05-11 21:04:10 +02:00
Events . on ( 'room-secrets' , e => this . send ( { type : 'room-secrets' , roomSecrets : e . detail } ) ) ;
2023-11-02 01:22:41 +01:00
Events . on ( 'join-ip-room' , _ => this . send ( { type : 'join-ip-room' } ) ) ;
2023-05-04 17:38:51 +02:00
Events . on ( 'room-secrets-deleted' , e => this . send ( { type : 'room-secrets-deleted' , roomSecrets : e . detail } ) ) ;
2023-05-09 03:17:08 +02:00
Events . on ( 'regenerate-room-secret' , e => this . send ( { type : 'regenerate-room-secret' , roomSecret : e . detail } ) ) ;
2023-01-10 05:07:57 +01:00
Events . on ( 'pair-device-initiate' , _ => this . _onPairDeviceInitiate ( ) ) ;
Events . on ( 'pair-device-join' , e => this . _onPairDeviceJoin ( e . detail ) ) ;
Events . on ( 'pair-device-cancel' , _ => this . send ( { type : 'pair-device-cancel' } ) ) ;
2023-09-13 18:15:01 +02:00
Events . on ( 'create-public-room' , _ => this . _onCreatePublicRoom ( ) ) ;
Events . on ( 'join-public-room' , e => this . _onJoinPublicRoom ( e . detail . roomId , e . detail . createIfInvalid ) ) ;
Events . on ( 'leave-public-room' , _ => this . _onLeavePublicRoom ( ) ) ;
2023-01-17 10:41:50 +01:00
Events . on ( 'offline' , _ => clearTimeout ( this . _reconnectTimer ) ) ;
Events . on ( 'online' , _ => this . _connect ( ) ) ;
2023-10-31 19:08:20 +01:00
2023-11-08 20:31:57 +01:00
this . _getConfig ( ) . then ( ( ) => this . _connect ( ) ) ;
}
_getConfig ( ) {
2024-02-05 15:42:27 +01:00
Logger . log ( "Loading config..." )
2023-11-08 20:31:57 +01:00
return new Promise ( ( resolve , reject ) => {
let xhr = new XMLHttpRequest ( ) ;
xhr . addEventListener ( "load" , ( ) => {
if ( xhr . status === 200 ) {
// Config received
let config = JSON . parse ( xhr . responseText ) ;
2024-02-05 15:42:27 +01:00
Logger . log ( "Config loaded:" , config )
window . _config = config ;
Events . fire ( 'config-loaded' ) ;
2023-11-08 20:31:57 +01:00
resolve ( )
2023-11-09 00:25:40 +01:00
} else if ( xhr . status < 200 || xhr . status >= 300 ) {
retry ( xhr ) ;
2023-11-08 20:31:57 +01:00
}
2023-11-09 00:25:40 +01:00
} )
xhr . addEventListener ( "error" , _ => {
retry ( xhr ) ;
2023-11-08 20:31:57 +01:00
} ) ;
2023-11-09 00:25:40 +01:00
function retry ( request ) {
setTimeout ( function ( ) {
openAndSend ( request )
} , 1000 )
}
function openAndSend ( ) {
xhr . open ( 'GET' , 'config' ) ;
xhr . send ( ) ;
}
openAndSend ( xhr ) ;
2023-11-08 20:31:57 +01:00
} )
}
_setWsConfig ( wsConfig ) {
this . _wsConfig = wsConfig ;
Events . fire ( 'ws-config' , wsConfig ) ;
2018-09-21 16:05:03 +02:00
}
2023-03-01 21:35:00 +01:00
_connect ( ) {
2018-09-21 21:12:11 +02:00
clearTimeout ( this . _reconnectTimer ) ;
2023-10-12 18:57:26 +02:00
if ( this . _isConnected ( ) || this . _isConnecting ( ) || this . _isOffline ( ) ) return ;
2023-10-12 03:39:37 +02:00
if ( this . _isReconnect ) {
Events . fire ( 'notify-user' , {
message : Localization . getTranslation ( "notifications.connecting" ) ,
persistent : true
} ) ;
}
2023-03-01 21:35:00 +01:00
const ws = new WebSocket ( this . _endpoint ( ) ) ;
2018-09-21 16:05:03 +02:00
ws . binaryType = 'arraybuffer' ;
2023-01-07 01:45:52 +01:00
ws . onopen = _ => this . _onOpen ( ) ;
2018-09-21 16:05:03 +02:00
ws . onmessage = e => this . _onMessage ( e . data ) ;
2022-12-23 02:38:56 +01:00
ws . onclose = _ => this . _onDisconnect ( ) ;
ws . onerror = e => this . _onError ( e ) ;
2018-09-21 16:05:03 +02:00
this . _socket = ws ;
}
2023-01-07 01:45:52 +01:00
_onOpen ( ) {
2024-02-05 15:42:27 +01:00
Logger . log ( 'WS: server connected' ) ;
2023-01-10 05:07:57 +01:00
Events . fire ( 'ws-connected' ) ;
2024-02-04 18:02:10 +01:00
if ( this . _isReconnect ) {
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.connected" ) ) ;
}
2023-01-10 05:07:57 +01:00
}
_onPairDeviceInitiate ( ) {
if ( ! this . _isConnected ( ) ) {
2023-09-13 18:15:01 +02:00
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.online-requirement-pairing" ) ) ;
return ;
}
this . send ( { type : 'pair-device-initiate' } ) ;
}
_onPairDeviceJoin ( pairKey ) {
if ( ! this . _isConnected ( ) ) {
2024-02-04 18:02:10 +01:00
// Todo: instead use pending outbound ws queue
2023-11-02 01:22:41 +01:00
setTimeout ( ( ) => this . _onPairDeviceJoin ( pairKey ) , 1000 ) ;
2023-09-13 18:15:01 +02:00
return ;
}
this . send ( { type : 'pair-device-join' , pairKey : pairKey } ) ;
}
_onCreatePublicRoom ( ) {
if ( ! this . _isConnected ( ) ) {
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.online-requirement-public-room" ) ) ;
return ;
}
this . send ( { type : 'create-public-room' } ) ;
}
_onJoinPublicRoom ( roomId , createIfInvalid ) {
if ( ! this . _isConnected ( ) ) {
2023-11-02 01:22:41 +01:00
setTimeout ( ( ) => this . _onJoinPublicRoom ( roomId ) , 1000 ) ;
2023-01-07 01:45:52 +01:00
return ;
}
2023-09-13 18:15:01 +02:00
this . send ( { type : 'join-public-room' , publicRoomId : roomId , createIfInvalid : createIfInvalid } ) ;
2023-01-10 05:07:57 +01:00
}
2023-09-13 18:15:01 +02:00
_onLeavePublicRoom ( ) {
2023-01-10 05:07:57 +01:00
if ( ! this . _isConnected ( ) ) {
2023-11-02 01:22:41 +01:00
setTimeout ( ( ) => this . _onLeavePublicRoom ( ) , 1000 ) ;
2023-01-10 05:07:57 +01:00
return ;
}
2023-09-13 18:15:01 +02:00
this . send ( { type : 'leave-public-room' } ) ;
2023-01-07 01:45:52 +01:00
}
2024-02-05 02:06:53 +01:00
_onMessage ( message ) {
const messageJSON = JSON . parse ( message ) ;
2024-02-05 15:42:27 +01:00
if ( messageJSON . type !== 'ping' && messageJSON . type !== 'ws-relay' ) {
Logger . debug ( 'WS receive:' , messageJSON ) ;
}
2024-02-05 02:06:53 +01:00
switch ( messageJSON . type ) {
2023-11-08 20:31:57 +01:00
case 'ws-config' :
2024-02-05 02:06:53 +01:00
this . _setWsConfig ( messageJSON . wsConfig ) ;
2023-02-24 18:08:48 +01:00
break ;
2018-09-21 16:05:03 +02:00
case 'peers' :
2024-02-05 02:06:53 +01:00
this . _onPeers ( messageJSON ) ;
2018-09-21 16:05:03 +02:00
break ;
case 'peer-joined' :
2024-02-05 02:06:53 +01:00
Events . fire ( 'peer-joined' , messageJSON ) ;
2018-09-21 16:05:03 +02:00
break ;
case 'peer-left' :
2024-02-05 02:06:53 +01:00
Events . fire ( 'peer-left' , messageJSON ) ;
2018-09-21 16:05:03 +02:00
break ;
case 'signal' :
2024-02-05 02:06:53 +01:00
Events . fire ( 'signal' , messageJSON ) ;
2018-09-21 16:05:03 +02:00
break ;
case 'ping' :
this . send ( { type : 'pong' } ) ;
break ;
2020-12-16 04:16:53 +01:00
case 'display-name' :
2024-02-05 02:06:53 +01:00
this . _onDisplayName ( messageJSON ) ;
2023-01-10 05:07:57 +01:00
break ;
case 'pair-device-initiated' :
2024-02-05 02:06:53 +01:00
Events . fire ( 'pair-device-initiated' , messageJSON ) ;
2023-01-10 05:07:57 +01:00
break ;
case 'pair-device-joined' :
2024-02-05 02:06:53 +01:00
Events . fire ( 'pair-device-joined' , messageJSON ) ;
2023-01-10 05:07:57 +01:00
break ;
case 'pair-device-join-key-invalid' :
Events . fire ( 'pair-device-join-key-invalid' ) ;
break ;
case 'pair-device-canceled' :
2024-02-05 02:06:53 +01:00
Events . fire ( 'pair-device-canceled' , messageJSON . pairKey ) ;
2023-01-10 05:07:57 +01:00
break ;
2023-09-13 18:15:01 +02:00
case 'join-key-rate-limit' :
2023-07-06 21:29:36 +02:00
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.rate-limit-join-key" ) ) ;
2023-01-10 05:07:57 +01:00
break ;
case 'secret-room-deleted' :
2024-02-05 02:06:53 +01:00
Events . fire ( 'secret-room-deleted' , messageJSON . roomSecret ) ;
2019-08-28 20:44:51 +05:30
break ;
2023-05-09 03:17:08 +02:00
case 'room-secret-regenerated' :
2024-02-05 02:06:53 +01:00
Events . fire ( 'room-secret-regenerated' , messageJSON ) ;
2023-05-09 03:17:08 +02:00
break ;
2023-09-13 18:15:01 +02:00
case 'public-room-id-invalid' :
2024-02-05 02:06:53 +01:00
Events . fire ( 'public-room-id-invalid' , messageJSON . publicRoomId ) ;
2023-09-13 18:15:01 +02:00
break ;
case 'public-room-created' :
2024-02-05 02:06:53 +01:00
Events . fire ( 'public-room-created' , messageJSON . roomId ) ;
2023-09-13 18:15:01 +02:00
break ;
case 'public-room-left' :
Events . fire ( 'public-room-left' ) ;
break ;
2024-02-05 02:06:53 +01:00
case 'ws-relay' :
2023-11-08 20:31:57 +01:00
// ws-fallback
if ( this . _wsConfig . wsFallback ) {
2024-02-05 02:06:53 +01:00
Events . fire ( 'ws-relay' , { peerId : messageJSON . sender . id , message : message } ) ;
2023-11-08 20:31:57 +01:00
}
else {
2024-02-05 15:42:27 +01:00
Logger . warn ( "WS receive: message type is for websocket fallback only but websocket fallback is not activated on this instance." )
2023-11-08 20:31:57 +01:00
}
break ;
2018-09-21 16:05:03 +02:00
default :
2024-02-05 15:42:27 +01:00
Logger . error ( 'WS receive: unknown message type' , messageJSON ) ;
2018-09-21 16:05:03 +02:00
}
}
2023-01-10 05:07:57 +01:00
send ( msg ) {
2018-10-09 15:45:07 +02:00
if ( ! this . _isConnected ( ) ) return ;
2024-02-05 15:42:27 +01:00
if ( msg . type !== 'pong' && msg . type !== 'ws-relay' ) {
Logger . debug ( "WS send:" , msg )
}
2023-01-10 05:07:57 +01:00
this . _socket . send ( JSON . stringify ( msg ) ) ;
}
2023-05-04 17:38:51 +02:00
_onPeers ( msg ) {
Events . fire ( 'peers' , msg ) ;
}
2023-01-10 05:07:57 +01:00
_onDisplayName ( msg ) {
2023-05-04 17:38:51 +02:00
// Add peerId and peerIdHash to sessionStorage to authenticate as the same device on page reload
2023-10-31 18:32:23 +01:00
sessionStorage . setItem ( 'peer_id' , msg . peerId ) ;
sessionStorage . setItem ( 'peer_id_hash' , msg . peerIdHash ) ;
2023-05-04 17:38:51 +02:00
2023-05-11 21:04:10 +02:00
// Add peerId to localStorage to mark it for other PairDrop tabs on the same browser
2023-11-02 02:56:01 +01:00
BrowserTabsConnector
. addPeerIdToLocalStorage ( )
. then ( peerId => {
if ( ! peerId ) return ;
2024-02-05 15:42:27 +01:00
Logger . debug ( "successfully added peerId to localStorage" ) ;
2023-11-02 02:56:01 +01:00
// Only now join rooms
Events . fire ( 'join-ip-room' ) ;
PersistentStorage . getAllRoomSecrets ( )
. then ( roomSecrets => {
Events . fire ( 'room-secrets' , roomSecrets ) ;
} ) ;
2023-05-11 21:04:10 +02:00
} ) ;
2023-05-04 17:38:51 +02:00
2023-01-10 05:07:57 +01:00
Events . fire ( 'display-name' , msg ) ;
2018-09-21 16:05:03 +02:00
}
2023-03-01 21:35:00 +01:00
_endpoint ( ) {
2018-09-21 16:05:03 +02:00
const protocol = location . protocol . startsWith ( 'https' ) ? 'wss' : 'ws' ;
2023-11-08 20:31:57 +01:00
// Check whether the instance specifies another signaling server otherwise use the current instance for signaling
2024-02-05 15:42:27 +01:00
let wsServerDomain = window . _config . signalingServer
? window . _config . signalingServer
2023-11-08 20:31:57 +01:00
: location . host + location . pathname ;
let wsUrl = new URL ( protocol + '://' + wsServerDomain + 'server' ) ;
wsUrl . searchParams . append ( 'webrtc_supported' , window . isRtcSupported ? 'true' : 'false' ) ;
2023-09-13 18:15:01 +02:00
const peerId = sessionStorage . getItem ( 'peer_id' ) ;
const peerIdHash = sessionStorage . getItem ( 'peer_id_hash' ) ;
2023-03-06 00:06:57 +01:00
if ( peerId && peerIdHash ) {
2023-11-08 20:31:57 +01:00
wsUrl . searchParams . append ( 'peer_id' , peerId ) ;
wsUrl . searchParams . append ( 'peer_id_hash' , peerIdHash ) ;
2023-03-06 00:06:57 +01:00
}
2023-11-08 20:31:57 +01:00
return wsUrl . toString ( ) ;
2023-01-10 05:07:57 +01:00
}
2023-03-06 03:36:46 +01:00
_disconnect ( ) {
this . send ( { type : 'disconnect' } ) ;
2023-05-04 17:38:51 +02:00
2023-09-13 18:15:01 +02:00
const peerId = sessionStorage . getItem ( 'peer_id' ) ;
2023-11-02 02:56:01 +01:00
BrowserTabsConnector
. removePeerIdFromLocalStorage ( peerId )
. then ( _ => {
2024-02-05 15:42:27 +01:00
Logger . debug ( "successfully removed peerId from localStorage" ) ;
2023-11-02 02:56:01 +01:00
} ) ;
2023-05-04 17:38:51 +02:00
if ( ! this . _socket ) return ;
this . _socket . onclose = null ;
this . _socket . close ( ) ;
this . _socket = null ;
Events . fire ( 'ws-disconnected' ) ;
this . _isReconnect = true ;
2018-09-21 16:05:03 +02:00
}
_onDisconnect ( ) {
2024-02-05 15:42:27 +01:00
Logger . log ( 'WS: server disconnected' ) ;
2023-10-06 02:57:46 +02:00
setTimeout ( ( ) => {
2023-10-12 03:39:37 +02:00
this . _isReconnect = true ;
Events . fire ( 'ws-disconnected' ) ;
2023-11-02 01:22:41 +01:00
this . _reconnectTimer = setTimeout ( ( ) => this . _connect ( ) , 1000 ) ;
2023-10-06 02:57:46 +02:00
} , 100 ) ; //delay for 100ms to prevent flickering on page reload
2022-12-23 02:38:56 +01:00
}
2018-09-21 20:51:56 +02:00
_onVisibilityChange ( ) {
2023-05-10 21:21:06 +02:00
if ( window . hiddenProperty ) return ;
2018-09-21 20:51:56 +02:00
this . _connect ( ) ;
}
2018-09-22 04:44:17 +02:00
_isConnected ( ) {
return this . _socket && this . _socket . readyState === this . _socket . OPEN ;
}
_isConnecting ( ) {
return this . _socket && this . _socket . readyState === this . _socket . CONNECTING ;
}
2022-12-23 02:38:56 +01:00
2023-10-12 18:57:26 +02:00
_isOffline ( ) {
return ! navigator . onLine ;
}
2022-12-23 02:38:56 +01:00
_onError ( e ) {
2024-02-05 15:42:27 +01:00
Logger . error ( e ) ;
2022-12-23 02:38:56 +01:00
}
2022-12-31 18:03:37 +01:00
_reconnect ( ) {
this . _disconnect ( ) ;
this . _connect ( ) ;
}
2018-09-21 16:05:03 +02:00
}
class Peer {
2023-09-13 18:15:01 +02:00
constructor ( serverConnection , isCaller , peerId , roomType , roomId ) {
2018-09-21 16:05:03 +02:00
this . _server = serverConnection ;
2023-05-11 21:04:10 +02:00
this . _isCaller = isCaller ;
2018-09-21 16:05:03 +02:00
this . _peerId = peerId ;
2023-09-13 18:15:01 +02:00
this . _roomIds = { } ;
this . _updateRoomIds ( roomType , roomId ) ;
2023-05-04 17:38:51 +02:00
2018-09-21 16:05:03 +02:00
this . _filesQueue = [ ] ;
this . _busy = false ;
2023-05-04 17:38:51 +02:00
2024-02-06 04:36:52 +01:00
this . _state = 'idle' ; // 'idle', 'prepare', 'wait', 'receive', 'transfer', 'text-sent'
2023-05-04 17:38:51 +02:00
// evaluate auto accept
this . _evaluateAutoAccept ( ) ;
2024-02-13 20:14:22 +01:00
Events . on ( 'beforeunload' , e => this . _onBeforeUnload ( e ) ) ;
Events . on ( 'pagehide' , _ => this . _onPageHide ( ) ) ;
2018-09-21 16:05:03 +02:00
}
2024-02-13 20:14:22 +01:00
_reset ( ) {
this . _state = 'idle' ;
this . _busy = false ;
this . _chunker = null ;
this . _pendingRequest = null ;
this . _acceptedRequest = null ;
this . _digester = null ;
this . _filesReceived = [ ] ;
this . _totalBytesReceived = 0 ;
}
2024-02-05 02:06:53 +01:00
_refresh ( ) { }
2024-02-13 20:14:22 +01:00
_disconnect ( ) {
Events . fire ( 'peer-disconnected' , this . _peerId ) ;
}
_onDisconnected ( ) {
this . _reset ( ) ;
}
_onBeforeUnload ( e ) {
if ( this . _busy ) {
e . preventDefault ( ) ;
return Localization . getTranslation ( "notifications.unfinished-transfers-warning" ) ;
}
}
_onPageHide ( ) {
this . _disconnect ( ) ;
}
_onServerSignalMessage ( message ) { }
2024-02-05 02:06:53 +01:00
2024-02-04 18:02:10 +01:00
_setIsCaller ( isCaller ) {
this . _isCaller = isCaller ;
}
2024-02-05 02:06:53 +01:00
_sendMessage ( message ) { }
2018-09-21 16:05:03 +02:00
2024-02-05 02:06:53 +01:00
_sendData ( data ) { }
2023-11-08 20:31:57 +01:00
2024-02-04 18:05:11 +01:00
_sendDisplayName ( displayName ) {
2024-02-05 02:06:53 +01:00
this . _sendMessage ( { type : 'display-name-changed' , displayName : displayName } ) ;
2023-03-06 11:24:19 +01:00
}
2023-05-11 21:04:10 +02:00
_isSameBrowser ( ) {
return BrowserTabsConnector . peerIsSameBrowser ( this . _peerId ) ;
}
2023-09-13 18:15:01 +02:00
_isPaired ( ) {
return ! ! this . _roomIds [ 'secret' ] ;
}
_getPairSecret ( ) {
return this . _roomIds [ 'secret' ] ;
}
2023-11-08 20:31:57 +01:00
_regenerationOfPairSecretNeeded ( ) {
return this . _getPairSecret ( ) && this . _getPairSecret ( ) . length !== 256
}
2023-09-13 18:15:01 +02:00
_getRoomTypes ( ) {
return Object . keys ( this . _roomIds ) ;
}
_updateRoomIds ( roomType , roomId ) {
2023-11-08 20:31:57 +01:00
const roomTypeIsSecret = roomType === "secret" ;
const roomIdIsNotPairSecret = this . _getPairSecret ( ) !== roomId ;
2023-05-04 17:38:51 +02:00
// if peer is another browser tab, peer is not identifiable with roomSecret as browser tabs share all roomSecrets
2023-05-11 21:04:10 +02:00
// -> do not delete duplicates and do not regenerate room secrets
2023-11-08 20:31:57 +01:00
if ( ! this . _isSameBrowser ( )
&& roomTypeIsSecret
&& this . _isPaired ( )
&& roomIdIsNotPairSecret ) {
2023-09-13 18:15:01 +02:00
// multiple roomSecrets with same peer -> delete old roomSecret
2023-11-02 02:56:01 +01:00
PersistentStorage
. deleteRoomSecret ( this . _getPairSecret ( ) )
2023-09-13 18:15:01 +02:00
. then ( deletedRoomSecret => {
2024-02-05 15:42:27 +01:00
if ( deletedRoomSecret ) {
Logger . debug ( "Successfully deleted duplicate room secret with same peer: " , deletedRoomSecret ) ;
}
2023-09-13 18:15:01 +02:00
} ) ;
2023-05-04 17:38:51 +02:00
}
2023-09-13 18:15:01 +02:00
this . _roomIds [ roomType ] = roomId ;
2023-05-04 17:38:51 +02:00
2023-11-08 20:31:57 +01:00
if ( ! this . _isSameBrowser ( )
&& roomTypeIsSecret
&& this . _isPaired ( )
&& this . _regenerationOfPairSecretNeeded ( )
&& this . _isCaller ) {
// increase security by initiating the increase of the roomSecret length
// from 64 chars (<v1.7.0) to 256 chars (v1.7.0+)
2024-02-05 15:42:27 +01:00
Logger . debug ( 'RoomSecret is regenerated to increase security' )
2023-09-13 18:15:01 +02:00
Events . fire ( 'regenerate-room-secret' , this . _getPairSecret ( ) ) ;
2023-05-04 17:38:51 +02:00
}
}
2023-09-13 18:15:01 +02:00
_removeRoomType ( roomType ) {
delete this . _roomIds [ roomType ] ;
Events . fire ( 'room-type-removed' , {
peerId : this . _peerId ,
roomType : roomType
} ) ;
}
2023-05-04 17:38:51 +02:00
_evaluateAutoAccept ( ) {
2023-09-13 18:15:01 +02:00
if ( ! this . _isPaired ( ) ) {
2023-05-04 17:38:51 +02:00
this . _setAutoAccept ( false ) ;
return ;
}
2023-11-02 02:56:01 +01:00
PersistentStorage
. getRoomSecretEntry ( this . _getPairSecret ( ) )
2023-05-04 17:38:51 +02:00
. then ( roomSecretEntry => {
2023-09-13 18:15:01 +02:00
const autoAccept = roomSecretEntry
? roomSecretEntry . entry . auto _accept
: false ;
2023-05-04 17:38:51 +02:00
this . _setAutoAccept ( autoAccept ) ;
} )
. catch ( _ => {
this . _setAutoAccept ( false ) ;
} ) ;
}
_setAutoAccept ( autoAccept ) {
2023-09-13 18:15:01 +02:00
this . _autoAccept = ! this . _isSameBrowser ( )
? autoAccept
: false ;
2023-01-17 10:41:50 +01:00
}
2024-02-04 18:02:10 +01:00
_onPeerConnected ( ) {
2024-02-06 04:36:52 +01:00
this . _sendCurrentState ( ) ;
}
_sendCurrentState ( ) {
this . _sendMessage ( { type : 'state' , state : this . _state } )
}
_onReceiveState ( peerState ) {
if ( this . _state === "receive" ) {
if ( peerState !== "transfer" || ! this . _digester ) {
this . _abortTransfer ( ) ;
return ;
}
2024-02-04 18:02:10 +01:00
// Reconnection during receiving of file. Send request for restart
const offset = this . _digester . _bytesReceived ;
2024-02-05 04:04:04 +01:00
this . _sendResendRequest ( offset ) ;
2024-02-06 04:36:52 +01:00
return
}
if ( this . _state === "transfer" && peerState !== "receive" ) {
this . _abortTransfer ( ) ;
return ;
2024-02-04 18:02:10 +01:00
}
}
2023-01-17 10:41:50 +01:00
async requestFileTransfer ( files ) {
let header = [ ] ;
2023-01-27 01:27:22 +01:00
let totalSize = 0 ;
let imagesOnly = true
2024-02-06 04:36:52 +01:00
this . _state = 'prepare' ;
2024-02-08 04:03:02 +01:00
Events . fire ( 'set-progress' , { peerId : this . _peerId , progress : 0 , status : 'prepare' } ) ;
2024-02-06 04:36:52 +01:00
for ( let i = 0 ; i < files . length ; i ++ ) {
2023-05-04 17:38:51 +02:00
header . push ( {
name : files [ i ] . name ,
mime : files [ i ] . type ,
size : files [ i ] . size
} ) ;
2023-01-27 01:27:22 +01:00
totalSize += files [ i ] . size ;
if ( files [ i ] . type . split ( '/' ) [ 0 ] !== 'image' ) imagesOnly = false ;
2023-01-17 10:41:50 +01:00
}
2023-12-11 17:19:56 +01:00
let dataUrl = '' ;
2023-01-18 15:34:11 +01:00
if ( files [ 0 ] . type . split ( '/' ) [ 0 ] === 'image' ) {
2023-11-24 16:25:30 +01:00
try {
2023-12-11 17:19:56 +01:00
dataUrl = await getThumbnailAsDataUrl ( files [ 0 ] , 400 , null , 0.9 ) ;
2023-11-24 16:25:30 +01:00
} catch ( e ) {
2024-02-05 15:42:27 +01:00
Logger . error ( e ) ;
2023-11-24 16:25:30 +01:00
}
2018-09-21 16:05:03 +02:00
}
2023-01-27 01:27:22 +01:00
Events . fire ( 'set-progress' , { peerId : this . _peerId , progress : 1 , status : 'prepare' } )
this . _filesRequested = files ;
2024-02-08 04:03:02 +01:00
this . _sendMessage ( { type : 'transfer-request' ,
2023-01-27 01:27:22 +01:00
header : header ,
totalSize : totalSize ,
imagesOnly : imagesOnly ,
thumbnailDataUrl : dataUrl
} ) ;
2023-01-17 10:41:50 +01:00
Events . fire ( 'set-progress' , { peerId : this . _peerId , progress : 0 , status : 'wait' } )
2024-02-06 04:36:52 +01:00
this . _state = 'wait' ;
2023-01-17 10:41:50 +01:00
}
2024-02-04 18:02:10 +01:00
sendFiles ( ) {
for ( let i = 0 ; i < this . _filesRequested . length ; i ++ ) {
2023-01-27 01:27:22 +01:00
this . _filesQueue . push ( this . _filesRequested [ i ] ) ;
}
this . _filesRequested = null
2018-09-21 16:05:03 +02:00
if ( this . _busy ) return ;
this . _dequeueFile ( ) ;
}
_dequeueFile ( ) {
this . _busy = true ;
const file = this . _filesQueue . shift ( ) ;
this . _sendFile ( file ) ;
}
2024-02-05 02:06:53 +01:00
_sendHeader ( file ) {
this . _sendMessage ( {
2024-02-08 04:03:02 +01:00
type : 'transfer-header' ,
2023-01-27 01:27:22 +01:00
size : file . size ,
name : file . name ,
mime : file . type
2018-09-21 16:05:03 +02:00
} ) ;
}
2024-02-05 02:06:53 +01:00
// Is overwritten in expanding classes
_sendFile ( file ) { }
2018-09-21 16:05:03 +02:00
2024-02-05 04:04:04 +01:00
_sendResendRequest ( offset ) {
this . _sendMessage ( { type : 'resend-request' , offset : offset } ) ;
2018-09-21 16:05:03 +02:00
}
2024-02-05 04:04:04 +01:00
_sendTransferAbortion ( ) {
this . _sendMessage ( { type : 'file-transfer-complete' , success : false } ) ;
}
_onResendRequest ( offset ) {
2024-02-06 04:36:52 +01:00
if ( this . _state !== 'transfer' || ! this . _chunker ) {
2024-02-05 04:04:04 +01:00
this . _sendTransferAbortion ( ) ;
return ;
}
2024-02-05 15:42:27 +01:00
Logger . debug ( "Resend requested from offset:" , offset )
2024-02-05 04:04:04 +01:00
this . _chunker . _resendFromOffset ( offset ) ;
2024-02-04 18:02:10 +01:00
}
2018-09-21 16:05:03 +02:00
_sendProgress ( progress ) {
2024-02-08 04:03:02 +01:00
this . _sendMessage ( { type : 'receive-progress' , progress : progress } ) ;
2018-09-21 16:05:03 +02:00
}
2024-02-05 02:06:53 +01:00
_onData ( data ) {
this . _onChunkReceived ( data ) ;
}
2024-02-04 18:02:10 +01:00
2024-02-05 02:06:53 +01:00
_onMessage ( message ) {
2024-02-04 18:02:10 +01:00
switch ( message . type ) {
2024-02-08 04:03:02 +01:00
case 'state' :
this . _onReceiveState ( message . state ) ;
2023-01-17 10:41:50 +01:00
break ;
2024-02-08 04:03:02 +01:00
case 'transfer-request' :
this . _onTransferRequest ( message ) ;
2018-09-21 16:05:03 +02:00
break ;
2024-02-08 04:03:02 +01:00
case 'transfer-response' :
this . _onTransferResponse ( message ) ;
break ;
case 'transfer-header' :
this . _onTransferHeader ( message ) ;
break ;
case 'receive-progress' :
this . _onReceiveProgress ( message . progress ) ;
2024-02-04 18:02:10 +01:00
break ;
2024-02-05 04:04:04 +01:00
case 'receive-confirmation' :
this . _onReceiveConfirmation ( message . bytesReceived ) ;
2024-02-05 02:06:53 +01:00
break ;
2024-02-05 04:04:04 +01:00
case 'resend-request' :
this . _onResendRequest ( message . offset ) ;
2018-09-21 16:05:03 +02:00
break ;
2022-11-08 15:43:24 +01:00
case 'file-transfer-complete' :
2024-02-06 04:36:52 +01:00
this . _onFileTransferComplete ( message ) ;
2022-11-08 15:43:24 +01:00
break ;
2018-09-21 16:05:03 +02:00
case 'text' :
2024-02-04 18:02:10 +01:00
this . _onTextReceived ( message ) ;
2018-09-21 16:05:03 +02:00
break ;
2024-02-08 04:03:02 +01:00
case 'text-sent' :
this . _onTextSent ( ) ;
break ;
2023-03-01 21:35:00 +01:00
case 'display-name-changed' :
2024-02-04 18:02:10 +01:00
this . _onDisplayNameChanged ( message ) ;
2023-03-01 21:35:00 +01:00
break ;
2024-02-05 02:06:53 +01:00
default :
2024-02-05 15:42:27 +01:00
Logger . warn ( 'RTC: Unknown message type:' , message . type ) ;
2018-09-21 16:05:03 +02:00
}
}
2024-02-08 04:03:02 +01:00
_onTransferRequest ( request ) {
2024-02-05 04:04:04 +01:00
if ( this . _pendingRequest ) {
2023-05-04 17:38:51 +02:00
// Only accept one request at a time per peer
2024-02-08 04:03:02 +01:00
this . _sendMessage ( { type : 'transfer-response' , accepted : false } ) ;
2023-01-17 10:41:50 +01:00
return ;
}
2023-01-27 01:27:22 +01:00
2024-02-05 04:04:04 +01:00
this . _pendingRequest = request ;
2023-05-04 17:38:51 +02:00
if ( this . _autoAccept ) {
// auto accept if set via Edit Paired Devices Dialog
this . _respondToFileTransferRequest ( true ) ;
return ;
}
// default behavior: show user transfer request
2023-01-17 10:41:50 +01:00
Events . fire ( 'files-transfer-request' , {
request : request ,
peerId : this . _peerId
} ) ;
}
2023-01-27 01:27:22 +01:00
_respondToFileTransferRequest ( accepted ) {
2024-02-08 04:03:02 +01:00
this . _sendMessage ( { type : 'transfer-response' , accepted : accepted } ) ;
2023-01-27 01:27:22 +01:00
if ( accepted ) {
2024-02-08 04:03:02 +01:00
this . _state = 'receive' ;
this . _busy = true ;
2024-02-05 04:04:04 +01:00
this . _acceptedRequest = this . _pendingRequest ;
2024-02-08 04:03:02 +01:00
this . _lastProgress = 0 ;
2023-01-27 01:27:22 +01:00
this . _totalBytesReceived = 0 ;
this . _filesReceived = [ ] ;
}
2024-02-05 04:04:04 +01:00
this . _pendingRequest = null ;
2023-01-17 10:41:50 +01:00
}
2024-02-08 04:03:02 +01:00
_onTransferHeader ( header ) {
if ( this . _state !== "receive" ) {
this . _sendCurrentState ( ) ;
2024-02-05 04:04:04 +01:00
return ;
2023-01-17 10:41:50 +01:00
}
2024-02-05 04:04:04 +01:00
this . _timeStart = Date . now ( ) ;
2024-02-08 04:03:02 +01:00
2024-02-05 04:04:04 +01:00
this . _addFileDigester ( header ) ;
2018-09-21 16:05:03 +02:00
}
2024-02-08 04:03:02 +01:00
_addFileDigester ( header ) {
this . _digester = new FileDigester ( { size : header . size , name : header . name , mime : header . mime } ,
this . _acceptedRequest . totalSize ,
fileBlob => this . _fileReceived ( fileBlob ) ,
bytesReceived => this . _sendReceiveConfirmation ( bytesReceived )
) ;
}
2024-02-05 02:06:53 +01:00
2024-02-05 04:04:04 +01:00
_sendReceiveConfirmation ( bytesReceived ) {
this . _sendMessage ( { type : 'receive-confirmation' , bytesReceived : bytesReceived } ) ;
2024-02-05 02:06:53 +01:00
}
2023-01-27 01:27:22 +01:00
_abortTransfer ( ) {
2024-02-07 23:58:15 +01:00
Events . fire ( 'set-progress' , { peerId : this . _peerId , progress : 0 , status : null } ) ;
2024-02-13 20:14:22 +01:00
this . _reset ( ) ;
2023-01-27 01:27:22 +01:00
}
2018-09-21 16:05:03 +02:00
_onChunkReceived ( chunk ) {
2024-02-08 00:46:00 +01:00
if ( this . _state !== 'receive' || ! this . _digester || ! ( chunk . byteLength || chunk . size ) ) {
this . _sendCurrentState ( ) ;
return ;
}
2022-12-22 20:28:45 +01:00
2018-09-21 16:05:03 +02:00
this . _digester . unchunk ( chunk ) ;
2024-02-04 18:02:10 +01:00
2024-02-08 04:03:02 +01:00
let progress = ( this . _totalBytesReceived + this . _digester . _bytesReceived ) / this . _acceptedRequest . totalSize ;
if ( isNaN ( progress ) ) progress = 1
2023-01-27 01:27:22 +01:00
if ( progress > 1 ) {
this . _abortTransfer ( ) ;
2024-02-06 04:36:52 +01:00
Logger . error ( "Too many bytes received. Abort!" ) ;
2024-02-04 18:02:10 +01:00
return ;
}
Events . fire ( 'set-progress' , { peerId : this . _peerId , progress : progress , status : 'receive' } ) ;
2018-09-21 16:05:03 +02:00
2022-12-22 20:28:45 +01:00
// occasionally notify sender about our progress
2024-02-04 18:02:10 +01:00
if ( progress - this . _lastProgress >= 0.005 || progress === 1 ) {
this . _lastProgress = progress ;
this . _sendProgress ( progress ) ;
}
2018-09-21 16:05:03 +02:00
}
2024-02-08 04:03:02 +01:00
_onReceiveProgress ( progress ) {
2024-02-08 00:46:00 +01:00
if ( this . _state !== 'transfer' ) {
this . _sendCurrentState ( ) ;
return ;
}
2024-02-06 04:36:52 +01:00
2023-01-27 01:27:22 +01:00
Events . fire ( 'set-progress' , { peerId : this . _peerId , progress : progress , status : 'transfer' } ) ;
2018-09-21 16:05:03 +02:00
}
2024-02-05 04:04:04 +01:00
_onReceiveConfirmation ( bytesReceived ) {
2024-02-08 00:46:00 +01:00
if ( ! this . _chunker || this . _state !== 'transfer' ) {
this . _sendCurrentState ( ) ;
return ;
}
2024-02-05 04:04:04 +01:00
this . _chunker . _onReceiveConfirmation ( bytesReceived ) ;
2024-02-05 02:06:53 +01:00
}
2024-02-06 04:36:52 +01:00
_fitsHeader ( file ) {
if ( ! this . _acceptedRequest ) {
return false ;
}
2024-02-08 00:46:00 +01:00
2024-02-06 04:36:52 +01:00
// Check if file fits to header
2024-02-05 04:04:04 +01:00
const acceptedHeader = this . _acceptedRequest . header . shift ( ) ;
2023-01-27 01:27:22 +01:00
2024-02-06 04:36:52 +01:00
const sameSize = file . size === acceptedHeader . size ;
const sameName = file . name === acceptedHeader . name
return sameSize && sameName ;
}
2023-10-13 18:05:51 +02:00
2024-02-08 04:03:02 +01:00
_singleFileTransferComplete ( file ) {
this . _digester = null ;
this . _totalBytesReceived += file . size ;
const duration = ( Date . now ( ) - this . _timeStart ) / 1000 ; // s
const size = Math . round ( 10 * file . size / 1e6 ) / 10 ; // MB
const speed = Math . round ( 100 * size / duration ) / 100 ; // MB/s
// Log speed from request to receive
2024-02-05 15:42:27 +01:00
Logger . log ( ` File received. \n \n Size: ${ size } MB \t Duration: ${ duration } s \t Speed: ${ speed } MB/s ` ) ;
2023-10-13 18:05:51 +02:00
2024-02-06 04:36:52 +01:00
this . _sendMessage ( { type : 'file-transfer-complete' , success : true , duration : duration , size : size , speed : speed } ) ;
2023-01-18 15:34:11 +01:00
2023-05-26 09:52:17 +02:00
// include for compatibility with 'Snapdrop & PairDrop for Android' app
2024-02-05 21:03:09 +01:00
Events . fire ( 'file-received' , file ) ;
2023-03-13 12:15:55 +01:00
2024-02-05 21:03:09 +01:00
this . _filesReceived . push ( file ) ;
2024-02-06 04:36:52 +01:00
}
2024-02-04 18:02:10 +01:00
2024-02-06 04:36:52 +01:00
_allFilesTransferComplete ( ) {
this . _state = 'idle' ;
2024-02-04 18:02:10 +01:00
Events . fire ( 'files-received' , {
peerId : this . _peerId ,
files : this . _filesReceived ,
2024-02-05 04:04:04 +01:00
imagesOnly : this . _acceptedRequest . imagesOnly ,
totalSize : this . _acceptedRequest . totalSize
2024-02-04 18:02:10 +01:00
} ) ;
this . _filesReceived = [ ] ;
2024-02-06 04:36:52 +01:00
this . _acceptedRequest = null ;
this . _busy = false ;
2018-09-21 16:05:03 +02:00
}
2024-02-08 00:46:00 +01:00
async _fileReceived ( file ) {
2024-02-06 04:36:52 +01:00
if ( ! this . _fitsHeader ( file ) ) {
this . _abortTransfer ( ) ;
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.files-incorrect" ) ) ;
Logger . error ( "Received files differ from requested files. Abort!" ) ;
return ;
}
// File transfer complete
2024-02-08 04:03:02 +01:00
this . _singleFileTransferComplete ( file ) ;
2024-02-06 04:36:52 +01:00
if ( this . _acceptedRequest . header . length ) return ;
// We are done receiving
2024-02-07 23:58:15 +01:00
Events . fire ( 'set-progress' , { peerId : this . _peerId , progress : 1 , status : 'receive' } ) ;
2024-02-06 04:36:52 +01:00
this . _allFilesTransferComplete ( ) ;
}
_onFileTransferComplete ( message ) {
2024-02-08 00:46:00 +01:00
if ( this . _state !== "transfer" ) {
this . _sendCurrentState ( ) ;
return ;
}
2023-01-27 01:27:22 +01:00
this . _chunker = null ;
2023-10-13 18:05:51 +02:00
2024-02-05 04:04:04 +01:00
if ( ! message . success ) {
2024-02-05 15:42:27 +01:00
Logger . warn ( 'File could not be sent' ) ;
2024-02-05 04:04:04 +01:00
Events . fire ( 'set-progress' , { peerId : this . _peerId , progress : 0 , status : null } ) ;
2024-02-06 04:36:52 +01:00
this . _state = 'idle' ;
2024-02-05 04:04:04 +01:00
return ;
}
2024-02-05 15:42:27 +01:00
Logger . log ( ` File sent. \n \n Size: ${ message . size } MB \t Duration: ${ message . duration } s \t Speed: ${ message . speed } MB/s ` ) ;
2023-10-13 18:05:51 +02:00
2024-02-04 18:02:10 +01:00
if ( this . _filesQueue . length ) {
2023-01-27 01:27:22 +01:00
this . _dequeueFile ( ) ;
2024-02-04 18:02:10 +01:00
return ;
2023-01-27 01:27:22 +01:00
}
2024-02-04 18:02:10 +01:00
// No more files in queue. Transfer is complete
2024-02-06 04:36:52 +01:00
this . _state = 'idle' ;
2024-02-04 18:02:10 +01:00
this . _busy = false ;
2024-02-07 23:58:15 +01:00
Events . fire ( 'set-progress' , { peerId : this . _peerId , progress : 0 , status : 'transfer-complete' } ) ;
2024-02-04 18:02:10 +01:00
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.file-transfer-completed" ) ) ;
Events . fire ( 'files-sent' ) ; // used by 'Snapdrop & PairDrop for Android' app
2023-01-17 10:41:50 +01:00
}
2024-02-08 04:03:02 +01:00
_onTransferResponse ( message ) {
2024-02-08 00:46:00 +01:00
if ( this . _state !== 'wait' ) {
this . _sendCurrentState ( ) ;
return ;
}
if ( ! message . accepted ) {
2024-02-07 23:58:15 +01:00
Events . fire ( 'set-progress' , { peerId : this . _peerId , progress : 0 , status : null } ) ;
2024-02-06 04:36:52 +01:00
this . _state = 'idle' ;
2023-01-27 01:27:22 +01:00
this . _filesRequested = null ;
2023-01-17 10:41:50 +01:00
return ;
}
2024-02-08 00:46:00 +01:00
2023-01-17 10:41:50 +01:00
Events . fire ( 'file-transfer-accepted' ) ;
2023-02-08 01:04:38 +01:00
Events . fire ( 'set-progress' , { peerId : this . _peerId , progress : 0 , status : 'transfer' } ) ;
2024-02-06 04:36:52 +01:00
this . _state = 'transfer' ;
2023-01-17 10:41:50 +01:00
this . sendFiles ( ) ;
2018-09-21 16:05:03 +02:00
}
2024-02-08 04:03:02 +01:00
_onTextSent ( ) {
2024-02-08 00:46:00 +01:00
if ( this . _state !== 'text-sent' ) {
this . _sendCurrentState ( ) ;
return ;
}
2024-02-06 04:36:52 +01:00
this . _state = 'idle' ;
2023-07-06 21:29:36 +02:00
Events . fire ( 'notify-user' , Localization . getTranslation ( "notifications.message-transfer-completed" ) ) ;
2022-11-08 15:43:24 +01:00
}
2018-09-21 16:05:03 +02:00
sendText ( text ) {
2024-02-06 04:36:52 +01:00
this . _state = 'text-sent' ;
2018-09-22 04:44:17 +02:00
const unescaped = btoa ( unescape ( encodeURIComponent ( text ) ) ) ;
2024-02-05 02:06:53 +01:00
this . _sendMessage ( { type : 'text' , text : unescaped } ) ;
2018-09-21 16:05:03 +02:00
}
_onTextReceived ( message ) {
2023-02-10 03:26:08 +01:00
if ( ! message . text ) return ;
2024-02-06 04:36:52 +01:00
try {
const escaped = decodeURIComponent ( escape ( atob ( message . text ) ) ) ;
Events . fire ( 'text-received' , { text : escaped , peerId : this . _peerId } ) ;
2024-02-08 04:03:02 +01:00
this . _sendMessage ( { type : 'text-sent' } ) ;
2024-02-06 04:36:52 +01:00
}
catch ( e ) {
Logger . error ( e ) ;
}
2018-09-21 16:05:03 +02:00
}
2023-03-01 21:35:00 +01:00
_onDisplayNameChanged ( message ) {
2024-02-04 18:05:11 +01:00
const displayNameHasChanged = message . displayName !== this . _displayName ;
2023-05-04 17:38:51 +02:00
2024-02-04 18:05:11 +01:00
if ( ! message . displayName || ! displayNameHasChanged ) return ;
this . _displayName = message . displayName ;
const roomSecret = this . _getPairSecret ( ) ;
if ( roomSecret ) {
PersistentStorage
. updateRoomSecretDisplayName ( roomSecret , message . displayName )
. then ( roomSecretEntry => {
2024-02-05 15:42:27 +01:00
Logger . debug ( ` Successfully updated DisplayName for roomSecretEntry ${ roomSecretEntry . key } ` ) ;
2024-02-04 18:05:11 +01:00
} )
2023-05-04 17:38:51 +02:00
}
2023-03-01 21:35:00 +01:00
Events . fire ( 'peer-display-name-changed' , { peerId : this . _peerId , displayName : message . displayName } ) ;
2023-05-04 17:38:51 +02:00
Events . fire ( 'notify-peer-display-name-changed' , this . _peerId ) ;
2023-03-01 21:35:00 +01:00
}
2018-09-21 16:05:03 +02:00
}
class RTCPeer extends Peer {
2023-11-08 20:31:57 +01:00
constructor ( serverConnection , isCaller , peerId , roomType , roomId , rtcConfig ) {
2023-09-13 18:15:01 +02:00
super ( serverConnection , isCaller , peerId , roomType , roomId ) ;
2023-11-08 20:31:57 +01:00
2023-03-03 13:09:59 +01:00
this . rtcSupported = true ;
2024-02-04 18:02:10 +01:00
this . rtcConfig = rtcConfig ;
this . pendingInboundMessages = [ ] ;
this . pendingOutboundMessages = [ ] ;
2023-05-11 21:04:10 +02:00
this . _connect ( ) ;
2018-09-21 16:05:03 +02:00
}
2024-02-05 02:06:53 +01:00
_connected ( ) {
2024-02-04 18:02:10 +01:00
return this . _conn && this . _conn . connectionState === 'connected' ;
}
2024-02-05 02:06:53 +01:00
_connecting ( ) {
2024-02-04 18:02:10 +01:00
return this . _conn
&& (
this . _conn . connectionState === 'new'
|| this . _conn . connectionState === 'connecting'
) ;
}
2024-02-05 02:06:53 +01:00
_messageChannelOpen ( ) {
return this . _messageChannel && this . _messageChannel . readyState === 'open' ;
}
_dataChannelOpen ( ) {
return this . _dataChannel && this . _dataChannel . readyState === 'open' ;
}
_messageChannelConnecting ( ) {
return this . _messageChannel && this . _messageChannel . readyState === 'connecting' ;
}
_dataChannelConnecting ( ) {
return this . _dataChannel && this . _dataChannel . readyState === 'connecting' ;
2024-02-04 18:02:10 +01:00
}
2024-02-05 02:06:53 +01:00
_channelOpen ( ) {
return this . _messageChannelOpen ( ) && this . _dataChannelOpen ( ) ;
2024-02-04 18:02:10 +01:00
}
2024-02-05 02:06:53 +01:00
_channelConnecting ( ) {
return ( this . _dataChannelConnecting ( ) || this . _dataChannelOpen ( ) )
&& ( this . _messageChannelConnecting ( ) || this . _messageChannelOpen ( ) ) ;
}
_stable ( ) {
return this . _connected ( ) && this . _channelOpen ( ) ;
2024-02-04 18:02:10 +01:00
}
2023-05-11 21:04:10 +02:00
_connect ( ) {
2024-02-05 02:06:53 +01:00
if ( this . _stable ( ) ) return ;
2018-09-21 16:05:03 +02:00
2024-02-04 18:02:10 +01:00
Events . fire ( 'peer-connecting' , this . _peerId ) ;
this . _openConnection ( ) ;
2024-02-05 02:06:53 +01:00
this . _openMessageChannel ( ) ;
this . _openDataChannel ( ) ;
this . _evaluatePendingInboundMessages ( )
. then ( ( count ) => {
if ( count ) {
2024-02-05 15:42:27 +01:00
Logger . debug ( "Pending inbound messages evaluated." ) ;
2024-02-05 02:06:53 +01:00
}
} ) ;
2018-09-21 16:05:03 +02:00
}
2023-05-11 21:04:10 +02:00
_openConnection ( ) {
2024-02-04 18:02:10 +01:00
const conn = new RTCPeerConnection ( this . rtcConfig ) ;
conn . onnegotiationneeded = _ => this . _onNegotiationNeeded ( ) ;
conn . onsignalingstatechange = _ => this . _onSignalingStateChanged ( ) ;
conn . oniceconnectionstatechange = _ => this . _onIceConnectionStateChange ( ) ;
conn . onicegatheringstatechange = _ => this . _onIceGatheringStateChanged ( ) ;
conn . onconnectionstatechange = _ => this . _onConnectionStateChange ( ) ;
conn . onicecandidate = e => this . _onIceCandidate ( e ) ;
conn . onicecandidateerror = e => this . _onIceCandidateError ( e ) ;
this . _conn = conn ;
2018-09-22 04:44:17 +02:00
}
2024-02-04 18:02:10 +01:00
async _onNegotiationNeeded ( ) {
2024-02-05 15:42:27 +01:00
Logger . debug ( 'RTC: Negotiation needed' ) ;
2023-09-13 18:15:01 +02:00
2024-02-04 18:02:10 +01:00
if ( this . _isCaller ) {
// Creating offer if required
2024-02-05 15:42:27 +01:00
Logger . debug ( 'RTC: Creating offer' ) ;
2024-02-04 18:02:10 +01:00
const description = await this . _conn . createOffer ( ) ;
await this . _handleLocalDescription ( description ) ;
}
}
_onSignalingStateChanged ( ) {
2024-02-05 15:42:27 +01:00
Logger . debug ( 'RTC: Signaling state changed:' , this . _conn . signalingState ) ;
2024-02-04 18:02:10 +01:00
}
2023-09-13 18:15:01 +02:00
2024-02-04 18:02:10 +01:00
_onIceConnectionStateChange ( ) {
2024-02-05 15:42:27 +01:00
Logger . debug ( 'RTC: ICE connection state changed:' , this . _conn . iceConnectionState ) ;
2018-09-21 16:05:03 +02:00
}
2024-02-04 18:02:10 +01:00
_onIceGatheringStateChanged ( ) {
2024-02-05 15:42:27 +01:00
Logger . debug ( 'RTC: ICE gathering state changed:' , this . _conn . iceConnectionState ) ;
2024-02-04 18:02:10 +01:00
}
_onConnectionStateChange ( ) {
2024-02-05 15:42:27 +01:00
Logger . debug ( 'RTC: Connection state changed:' , this . _conn . connectionState ) ;
2024-02-04 18:02:10 +01:00
switch ( this . _conn . connectionState ) {
case 'disconnected' :
this . _refresh ( ) ;
break ;
case 'failed' :
2024-02-05 15:42:27 +01:00
Logger . warn ( 'RTC connection failed' ) ;
2024-02-05 02:06:53 +01:00
// Todo: if error is "TURN server needed" -> fallback to WS if activated
2024-02-04 18:02:10 +01:00
this . _refresh ( ) ;
}
2018-09-21 16:05:03 +02:00
}
_onIceCandidate ( event ) {
2024-02-04 18:02:10 +01:00
this . _handleLocalCandidate ( event . candidate ) ;
2018-09-21 16:05:03 +02:00
}
2024-02-04 18:02:10 +01:00
_onIceCandidateError ( error ) {
2024-02-05 15:42:27 +01:00
Logger . error ( error ) ;
2024-02-04 18:02:10 +01:00
}
2024-02-05 02:06:53 +01:00
_openMessageChannel ( ) {
const messageCallback = e => this . _onMessage ( e . data ) ;
this . _messageChannel = this . _openChannel ( "message-channel" , 1 , "json" , messageCallback ) ;
}
_openDataChannel ( ) {
const messageCallback = e => this . _onData ( e . data ) ;
this . _dataChannel = this . _openChannel ( "data-channel" , 0 , "raw" , messageCallback ) ;
}
_openChannel ( label , id , protocol , messageCallback ) {
const channel = this . _conn . createDataChannel ( label , {
2024-02-04 18:02:10 +01:00
ordered : true ,
negotiated : true ,
2024-02-05 02:06:53 +01:00
id : id ,
protocol : protocol
2024-02-04 18:02:10 +01:00
} ) ;
2024-02-05 02:06:53 +01:00
channel . binaryType = "arraybuffer" ;
channel . onopen = e => this . _onChannelOpened ( e ) ;
channel . onclose = e => this . _onChannelClosed ( e ) ;
2024-02-04 18:02:10 +01:00
channel . onerror = e => this . _onChannelError ( e ) ;
2024-02-05 02:06:53 +01:00
channel . onmessage = messageCallback ;
2024-02-04 18:02:10 +01:00
2024-02-05 02:06:53 +01:00
return channel ;
2024-02-04 18:02:10 +01:00
}
2024-02-05 02:06:53 +01:00
_onChannelOpened ( e ) {
2024-02-05 15:42:27 +01:00
Logger . debug ( ` RTC: Channel ${ e . target . label } opened with ` , this . _peerId ) ;
2024-02-05 02:06:53 +01:00
// wait until all channels are open
if ( ! this . _stable ( ) ) return ;
2023-03-01 21:35:00 +01:00
Events . fire ( 'peer-connected' , { peerId : this . _peerId , connectionHash : this . getConnectionHash ( ) } ) ;
2024-02-04 18:02:10 +01:00
super . _onPeerConnected ( ) ;
2024-02-05 02:06:53 +01:00
this . _sendPendingOutboundMessaged ( ) ;
}
_sendPendingOutboundMessaged ( ) {
while ( this . _stable ( ) && this . pendingOutboundMessages . length > 0 ) {
this . _sendViaMessageChannel ( this . pendingOutboundMessages . shift ( ) ) ;
2024-02-04 18:02:10 +01:00
}
2018-09-21 16:05:03 +02:00
}
2024-02-05 02:06:53 +01:00
_onChannelClosed ( e ) {
2024-02-05 15:42:27 +01:00
Logger . debug ( ` RTC: Channel ${ e . target . label } closed ` , this . _peerId ) ;
2024-02-04 18:02:10 +01:00
this . _refresh ( ) ;
}
2024-02-05 02:06:53 +01:00
_onChannelError ( e ) {
2024-02-05 15:42:27 +01:00
Logger . warn ( ` RTC: Channel ${ e . target . label } error ` , this . _peerId ) ;
Logger . error ( e . error ) ;
2024-02-04 18:02:10 +01:00
}
async _handleLocalDescription ( localDescription ) {
await this . _conn . setLocalDescription ( localDescription ) ;
2024-02-05 15:42:27 +01:00
Logger . debug ( "RTC: Sending local description" ) ;
2024-02-04 18:02:10 +01:00
this . _sendSignal ( { signalType : 'description' , description : localDescription } ) ;
}
async _handleRemoteDescription ( remoteDescription ) {
2024-02-05 15:42:27 +01:00
Logger . debug ( "RTC: Received remote description" ) ;
2024-02-04 18:02:10 +01:00
await this . _conn . setRemoteDescription ( remoteDescription ) ;
if ( ! this . _isCaller ) {
// Creating answer if required
2024-02-05 15:42:27 +01:00
Logger . debug ( 'RTC: Creating answer' ) ;
2024-02-04 18:02:10 +01:00
const localDescription = await this . _conn . createAnswer ( ) ;
await this . _handleLocalDescription ( localDescription ) ;
2023-03-03 12:38:34 +01:00
}
2024-02-04 18:02:10 +01:00
}
_handleLocalCandidate ( candidate ) {
2024-02-05 02:06:53 +01:00
if ( this . localIceCandidatesSent ) return ;
2024-02-05 15:42:27 +01:00
Logger . debug ( "RTC: Local candidate created" , candidate ) ;
2024-02-04 18:02:10 +01:00
if ( candidate === null ) {
this . localIceCandidatesSent = true ;
2024-02-05 02:06:53 +01:00
return ;
2024-02-04 18:02:10 +01:00
}
2024-02-05 02:06:53 +01:00
this . _sendSignal ( { signalType : 'candidate' , candidate : candidate } ) ;
2024-02-04 18:02:10 +01:00
}
async _handleRemoteCandidate ( candidate ) {
2024-02-05 02:06:53 +01:00
if ( this . remoteIceCandidatesReceived ) return ;
2024-02-05 15:42:27 +01:00
Logger . debug ( "RTC: Received remote candidate" , candidate ) ;
2024-02-05 02:06:53 +01:00
if ( candidate === null ) {
2024-02-04 18:02:10 +01:00
this . remoteIceCandidatesReceived = true ;
2024-02-05 02:06:53 +01:00
return ;
2024-02-04 18:02:10 +01:00
}
2024-02-05 02:06:53 +01:00
await this . _conn . addIceCandidate ( candidate ) ;
2024-02-04 18:02:10 +01:00
}
async _evaluatePendingInboundMessages ( ) {
let inboundMessagesEvaluatedCount = 0 ;
while ( this . pendingInboundMessages . length > 0 ) {
const message = this . pendingInboundMessages . shift ( ) ;
2024-02-05 15:42:27 +01:00
Logger . debug ( "Evaluate pending inbound message:" , message ) ;
2024-02-05 02:06:53 +01:00
await this . _onServerSignalMessage ( message ) ;
2024-02-04 18:02:10 +01:00
inboundMessagesEvaluatedCount ++ ;
}
return inboundMessagesEvaluatedCount ;
}
2024-02-05 02:06:53 +01:00
async _onServerSignalMessage ( message ) {
2024-02-04 18:02:10 +01:00
if ( this . _conn === null ) {
this . pendingInboundMessages . push ( message ) ;
return ;
}
switch ( message . signalType ) {
case 'description' :
await this . _handleRemoteDescription ( message . description ) ;
break ;
case 'candidate' :
await this . _handleRemoteCandidate ( message . candidate ) ;
break ;
default :
2024-02-05 15:42:27 +01:00
Logger . warn ( 'Unknown signalType:' , message . signalType ) ;
2024-02-04 18:02:10 +01:00
break ;
}
}
_refresh ( ) {
Events . fire ( 'peer-connecting' , this . _peerId ) ;
this . _closeChannelAndConnection ( ) ;
this . _connect ( ) ; // reopen the channel
}
2024-02-05 02:06:53 +01:00
_onDisconnected ( ) {
2024-02-13 20:14:22 +01:00
super . _onDisconnected ( ) ;
2024-02-05 02:06:53 +01:00
this . _closeChannelAndConnection ( ) ;
}
2024-02-04 18:02:10 +01:00
_closeChannelAndConnection ( ) {
2024-02-05 02:06:53 +01:00
if ( this . _dataChannel ) {
this . _dataChannel . onopen = null ;
this . _dataChannel . onclose = null ;
this . _dataChannel . onerror = null ;
this . _dataChannel . onmessage = null ;
this . _dataChannel . close ( ) ;
this . _dataChannel = null ;
}
if ( this . _messageChannel ) {
this . _messageChannel . onopen = null ;
this . _messageChannel . onclose = null ;
this . _messageChannel . onerror = null ;
this . _messageChannel . onmessage = null ;
this . _messageChannel . close ( ) ;
this . _messageChannel = null ;
2024-02-04 18:02:10 +01:00
}
if ( this . _conn ) {
this . _conn . onnegotiationneeded = null ;
this . _conn . onsignalingstatechange = null ;
this . _conn . oniceconnectionstatechange = null ;
this . _conn . onicegatheringstatechange = null ;
this . _conn . onconnectionstatechange = null ;
this . _conn . onicecandidate = null ;
this . _conn . onicecandidateerror = null ;
this . _conn . close ( ) ;
this . _conn = null ;
}
this . localIceCandidatesSent = false ;
this . remoteIceCandidatesReceived = false ;
}
2024-02-05 02:06:53 +01:00
_sendMessage ( message ) {
if ( ! this . _stable ( ) || this . pendingOutboundMessages . length > 0 ) {
2024-02-04 18:02:10 +01:00
// queue messages if not connected OR if connected AND queue is not empty
this . pendingOutboundMessages . push ( message ) ;
return ;
}
2024-02-05 02:06:53 +01:00
this . _sendViaMessageChannel ( message ) ;
}
_sendViaMessageChannel ( message ) {
2024-02-05 15:42:27 +01:00
Logger . debug ( 'RTC Send:' , message ) ;
2024-02-05 02:06:53 +01:00
this . _messageChannel . send ( JSON . stringify ( message ) ) ;
2024-02-04 18:02:10 +01:00
}
2024-02-05 02:06:53 +01:00
_sendData ( data ) {
this . _sendViaDataChannel ( data )
}
_sendViaDataChannel ( data ) {
this . _dataChannel . send ( data ) ;
2024-02-04 18:02:10 +01:00
}
_sendSignal ( message ) {
message . type = 'signal' ;
message . to = this . _peerId ;
message . roomType = this . _getRoomTypes ( ) [ 0 ] ;
message . roomId = this . _roomIds [ this . _getRoomTypes ( ) [ 0 ] ] ;
this . _server . send ( message ) ;
}
2024-02-05 02:06:53 +01:00
async _sendFile ( file ) {
this . _chunker = new FileChunkerRTC (
file ,
chunk => this . _sendData ( chunk ) ,
this . _conn ,
this . _dataChannel
) ;
this . _chunker . _readChunk ( ) ;
2024-02-06 04:36:52 +01:00
this . _sendHeader ( file ) ;
this . _state = 'transfer' ;
2024-02-05 02:06:53 +01:00
}
_onMessage ( message ) {
2024-02-05 15:42:27 +01:00
Logger . debug ( 'RTC Receive:' , JSON . parse ( message ) ) ;
2024-02-05 02:06:53 +01:00
try {
message = JSON . parse ( message ) ;
} catch ( e ) {
2024-02-05 15:42:27 +01:00
Logger . warn ( "RTCPeer: Received JSON is malformed" ) ;
2024-02-05 02:06:53 +01:00
return ;
}
super . _onMessage ( message ) ;
}
2023-02-16 02:19:14 +01:00
getConnectionHash ( ) {
const localDescriptionLines = this . _conn . localDescription . sdp . split ( "\r\n" ) ;
const remoteDescriptionLines = this . _conn . remoteDescription . sdp . split ( "\r\n" ) ;
let localConnectionFingerprint , remoteConnectionFingerprint ;
for ( let i = 0 ; i < localDescriptionLines . length ; i ++ ) {
if ( localDescriptionLines [ i ] . startsWith ( "a=fingerprint:" ) ) {
localConnectionFingerprint = localDescriptionLines [ i ] . substring ( 14 ) ;
break ;
}
}
for ( let i = 0 ; i < remoteDescriptionLines . length ; i ++ ) {
if ( remoteDescriptionLines [ i ] . startsWith ( "a=fingerprint:" ) ) {
remoteConnectionFingerprint = remoteDescriptionLines [ i ] . substring ( 14 ) ;
break ;
}
}
const combinedFingerprints = this . _isCaller
? localConnectionFingerprint + remoteConnectionFingerprint
: remoteConnectionFingerprint + localConnectionFingerprint ;
let hash = cyrb53 ( combinedFingerprints ) . toString ( ) ;
while ( hash . length < 16 ) {
hash = "0" + hash ;
}
return hash ;
}
2018-09-21 16:05:03 +02:00
}
2023-11-08 20:31:57 +01:00
class WSPeer extends Peer {
constructor ( serverConnection , isCaller , peerId , roomType , roomId ) {
super ( serverConnection , isCaller , peerId , roomType , roomId ) ;
this . rtcSupported = false ;
2024-02-05 02:06:53 +01:00
this . signalSuccessful = false ;
2023-11-08 20:31:57 +01:00
if ( ! this . _isCaller ) return ; // we will listen for a caller
2024-02-05 02:06:53 +01:00
2023-11-08 20:31:57 +01:00
this . _sendSignal ( ) ;
}
2024-02-05 02:06:53 +01:00
_sendFile ( file ) {
this . _sendHeader ( file ) ;
this . _chunker = new FileChunkerWS (
file ,
chunk => this . _sendData ( chunk )
) ;
this . _chunker . _readChunk ( ) ;
}
_sendData ( data ) {
this . _sendMessage ( {
type : 'chunk' ,
chunk : arrayBufferToBase64 ( data )
2023-11-08 20:31:57 +01:00
} ) ;
}
2024-02-05 02:06:53 +01:00
_sendMessage ( message ) {
message = {
type : 'ws-relay' ,
message : message
} ;
this . _sendMessageViaServer ( message ) ;
}
_sendMessageViaServer ( message ) {
2023-11-08 20:31:57 +01:00
message . to = this . _peerId ;
message . roomType = this . _getRoomTypes ( ) [ 0 ] ;
message . roomId = this . _roomIds [ this . _getRoomTypes ( ) [ 0 ] ] ;
this . _server . send ( message ) ;
}
_sendSignal ( connected = false ) {
2024-02-05 02:06:53 +01:00
this . _sendMessageViaServer ( { type : 'signal' , connected : connected } ) ;
2023-11-08 20:31:57 +01:00
}
2024-02-05 02:06:53 +01:00
_onServerSignalMessage ( message ) {
2023-11-08 20:31:57 +01:00
this . _peerId = message . sender . id ;
2024-02-05 02:06:53 +01:00
Events . fire ( 'peer-connected' , { peerId : this . _peerId , connectionHash : this . getConnectionHash ( ) } )
if ( message . connected ) {
this . signalSuccessful = true ;
return ;
}
2023-11-08 20:31:57 +01:00
this . _sendSignal ( true ) ;
}
2024-02-05 02:06:53 +01:00
_onMessage ( message ) {
2024-02-05 15:42:27 +01:00
Logger . debug ( 'WS Receive:' , message ) ;
2024-02-05 02:06:53 +01:00
super . _onMessage ( message ) ;
}
_onWsRelay ( message ) {
try {
message = JSON . parse ( message ) . message ;
}
catch ( e ) {
2024-02-05 15:42:27 +01:00
Logger . warn ( "WSPeer: Received JSON is malformed" ) ;
2024-02-05 02:06:53 +01:00
return ;
}
if ( message . type === 'chunk' ) {
const data = base64ToArrayBuffer ( message . chunk ) ;
this . _onData ( data ) ;
}
else {
this . _onMessage ( message ) ;
}
}
_refresh ( ) {
this . signalSuccessful = true ;
if ( ! this . _isCaller ) return ; // we will listen for a caller
this . _sendSignal ( ) ;
}
_onDisconnected ( ) {
2024-02-13 20:14:22 +01:00
super . _onDisconnected ( ) ;
2024-02-05 02:06:53 +01:00
this . signalSuccessful = false ;
}
2023-11-08 20:31:57 +01:00
getConnectionHash ( ) {
// Todo: implement SubtleCrypto asymmetric encryption and create connectionHash from public keys
return "" ;
}
}
2018-09-21 16:05:03 +02:00
class PeersManager {
constructor ( serverConnection ) {
this . peers = { } ;
this . _server = serverConnection ;
2024-02-05 02:06:53 +01:00
Events . on ( 'signal' , e => this . _onSignal ( e . detail ) ) ;
2018-09-21 16:05:03 +02:00
Events . on ( 'peers' , e => this . _onPeers ( e . detail ) ) ;
Events . on ( 'files-selected' , e => this . _onFilesSelected ( e . detail ) ) ;
2023-01-17 10:41:50 +01:00
Events . on ( 'respond-to-files-transfer-request' , e => this . _onRespondToFileTransferRequest ( e . detail ) )
2018-09-21 16:05:03 +02:00
Events . on ( 'send-text' , e => this . _onSendText ( e . detail ) ) ;
2023-02-10 23:47:39 +01:00
Events . on ( 'peer-left' , e => this . _onPeerLeft ( e . detail ) ) ;
2023-05-04 17:38:51 +02:00
Events . on ( 'peer-joined' , e => this . _onPeerJoined ( e . detail ) ) ;
2023-03-01 21:35:00 +01:00
Events . on ( 'peer-connected' , e => this . _onPeerConnected ( e . detail . peerId ) ) ;
2023-02-10 18:56:13 +01:00
Events . on ( 'peer-disconnected' , e => this . _onPeerDisconnected ( e . detail ) ) ;
2023-09-13 18:15:01 +02:00
// this device closes connection
Events . on ( 'room-secrets-deleted' , e => this . _onRoomSecretsDeleted ( e . detail ) ) ;
Events . on ( 'leave-public-room' , e => this . _onLeavePublicRoom ( e . detail ) ) ;
// peer closes connection
2023-01-10 05:07:57 +01:00
Events . on ( 'secret-room-deleted' , e => this . _onSecretRoomDeleted ( e . detail ) ) ;
2023-09-13 18:15:01 +02:00
2023-05-09 03:17:08 +02:00
Events . on ( 'room-secret-regenerated' , e => this . _onRoomSecretRegenerated ( e . detail ) ) ;
2023-10-31 18:32:23 +01:00
Events . on ( 'display-name' , e => this . _onDisplayName ( e . detail . displayName ) ) ;
2023-03-01 21:35:00 +01:00
Events . on ( 'self-display-name-changed' , e => this . _notifyPeersDisplayNameChanged ( e . detail ) ) ;
2023-05-04 17:38:51 +02:00
Events . on ( 'notify-peer-display-name-changed' , e => this . _notifyPeerDisplayNameChanged ( e . detail ) ) ;
Events . on ( 'auto-accept-updated' , e => this . _onAutoAcceptUpdated ( e . detail . roomSecret , e . detail . autoAccept ) ) ;
2023-11-08 20:31:57 +01:00
Events . on ( 'ws-disconnected' , _ => this . _onWsDisconnected ( ) ) ;
2024-02-05 02:06:53 +01:00
Events . on ( 'ws-relay' , e => this . _onWsRelay ( e . detail . peerId , e . detail . message ) ) ;
2023-11-08 20:31:57 +01:00
Events . on ( 'ws-config' , e => this . _onWsConfig ( e . detail ) ) ;
}
_onWsConfig ( wsConfig ) {
this . _wsConfig = wsConfig ;
2018-09-21 16:05:03 +02:00
}
2024-02-05 02:06:53 +01:00
_onSignal ( message ) {
2023-05-04 17:38:51 +02:00
const peerId = message . sender . id ;
2024-02-05 02:06:53 +01:00
this . peers [ peerId ] . _onServerSignalMessage ( message ) ;
2023-01-10 05:07:57 +01:00
}
2024-02-04 18:02:10 +01:00
_refreshPeer ( isCaller , peerId , roomType , roomId ) {
2023-11-08 20:31:57 +01:00
const peer = this . peers [ peerId ] ;
2023-09-13 18:15:01 +02:00
const roomTypesDiffer = Object . keys ( peer . _roomIds ) [ 0 ] !== roomType ;
const roomIdsDiffer = peer . _roomIds [ roomType ] !== roomId ;
2023-05-04 17:38:51 +02:00
2023-09-13 18:15:01 +02:00
// if roomType or roomId for roomType differs peer is already connected
// -> only update roomSecret and reevaluate auto accept
if ( roomTypesDiffer || roomIdsDiffer ) {
peer . _updateRoomIds ( roomType , roomId ) ;
2023-05-11 21:04:10 +02:00
peer . _evaluateAutoAccept ( ) ;
2023-05-04 17:38:51 +02:00
2023-05-11 21:04:10 +02:00
return true ;
}
2023-05-04 17:38:51 +02:00
2024-02-04 18:02:10 +01:00
// reconnect peer - caller/waiter might be switched
peer . _setIsCaller ( isCaller ) ;
peer . _refresh ( ) ;
2023-05-11 21:04:10 +02:00
return true ;
}
2023-11-08 20:31:57 +01:00
_createOrRefreshPeer ( isCaller , peerId , roomType , roomId , rtcSupported ) {
if ( this . _peerExists ( peerId ) ) {
2024-02-04 18:02:10 +01:00
this . _refreshPeer ( isCaller , peerId , roomType , roomId ) ;
} else {
this . createPeer ( isCaller , peerId , roomType , roomId , rtcSupported ) ;
2023-05-04 17:38:51 +02:00
}
2024-02-04 18:02:10 +01:00
}
2023-05-11 21:04:10 +02:00
2024-02-04 18:02:10 +01:00
createPeer ( isCaller , peerId , roomType , roomId , rtcSupported ) {
2023-11-08 20:31:57 +01:00
if ( window . isRtcSupported && rtcSupported ) {
this . peers [ peerId ] = new RTCPeer ( this . _server , isCaller , peerId , roomType , roomId , this . _wsConfig . rtcConfig ) ;
}
else if ( this . _wsConfig . wsFallback ) {
this . peers [ peerId ] = new WSPeer ( this . _server , isCaller , peerId , roomType , roomId ) ;
}
else {
2024-02-05 15:42:27 +01:00
Logger . warn ( "Websocket fallback is not activated on this instance.\n" +
2023-11-08 20:31:57 +01:00
"Activate WebRTC in this browser or ask the admin of this instance to activate the websocket fallback." )
}
2023-05-04 17:38:51 +02:00
}
_onPeerJoined ( message ) {
2023-11-08 20:31:57 +01:00
this . _createOrRefreshPeer ( false , message . peer . id , message . roomType , message . roomId , message . peer . rtcSupported ) ;
2023-05-04 17:38:51 +02:00
}
_onPeers ( message ) {
2023-05-11 21:04:10 +02:00
message . peers . forEach ( peer => {
2023-11-08 20:31:57 +01:00
this . _createOrRefreshPeer ( true , peer . id , message . roomType , message . roomId , peer . rtcSupported ) ;
2018-09-21 16:05:03 +02:00
} )
}
2024-02-05 02:06:53 +01:00
_onWsRelay ( peerId , message ) {
2023-11-08 20:31:57 +01:00
if ( ! this . _wsConfig . wsFallback ) return ;
2024-02-05 02:06:53 +01:00
const peer = this . peers [ peerId ] ;
if ( ! peer || peer . rtcSupported ) return ;
peer . _onWsRelay ( message ) ;
2023-11-08 20:31:57 +01:00
}
2023-01-17 10:41:50 +01:00
_onRespondToFileTransferRequest ( detail ) {
2023-01-27 01:27:22 +01:00
this . peers [ detail . to ] . _respondToFileTransferRequest ( detail . accepted ) ;
2023-01-17 10:41:50 +01:00
}
2023-12-08 13:49:45 +01:00
async _onFilesSelected ( message ) {
let files = await mime . addMissingMimeTypesToFiles ( message . files ) ;
await this . peers [ message . to ] . requestFileTransfer ( files ) ;
2018-09-21 16:05:03 +02:00
}
_onSendText ( message ) {
this . peers [ message . to ] . sendText ( message . text ) ;
}
2023-05-04 17:38:51 +02:00
_onPeerLeft ( message ) {
2024-02-04 18:02:10 +01:00
if ( this . _peerExists ( message . peerId ) && ! this . _webRtcSupported ( message . peerId ) ) {
2024-02-05 15:42:27 +01:00
Logger . debug ( 'WSPeer left:' , message . peerId ) ;
2023-11-08 20:31:57 +01:00
}
2023-05-04 17:38:51 +02:00
if ( message . disconnect === true ) {
// if user actively disconnected from PairDrop server, disconnect all peer to peer connections immediately
2023-09-13 18:15:01 +02:00
this . _disconnectOrRemoveRoomTypeByPeerId ( message . peerId , message . roomType ) ;
2023-05-11 21:04:10 +02:00
// If no peers are connected anymore, we can safely assume that no other tab on the same browser is connected:
// Tidy up peerIds in localStorage
if ( Object . keys ( this . peers ) . length === 0 ) {
2023-11-08 20:36:46 +01:00
BrowserTabsConnector
. removeOtherPeerIdsFromLocalStorage ( )
2023-11-02 02:56:01 +01:00
. then ( peerIds => {
if ( ! peerIds ) return ;
2024-02-05 15:42:27 +01:00
Logger . debug ( "successfully removed other peerIds from localStorage" ) ;
2023-11-02 02:56:01 +01:00
} ) ;
2023-05-11 21:04:10 +02:00
}
2023-02-10 23:47:39 +01:00
}
}
2023-03-01 21:35:00 +01:00
_onPeerConnected ( peerId ) {
this . _notifyPeerDisplayNameChanged ( peerId ) ;
}
2023-11-08 20:31:57 +01:00
_peerExists ( peerId ) {
return ! ! this . peers [ peerId ] ;
}
_webRtcSupported ( peerId ) {
return this . peers [ peerId ] . rtcSupported
}
_onWsDisconnected ( ) {
if ( ! this . _wsConfig || ! this . _wsConfig . wsFallback ) return ;
for ( const peerId in this . peers ) {
if ( ! this . _webRtcSupported ( peerId ) ) {
Events . fire ( 'peer-disconnected' , peerId ) ;
}
}
}
2023-02-10 18:56:13 +01:00
_onPeerDisconnected ( peerId ) {
2018-09-21 16:05:03 +02:00
const peer = this . peers [ peerId ] ;
delete this . peers [ peerId ] ;
2024-02-04 18:02:10 +01:00
if ( ! peer ) return ;
2024-02-05 02:06:53 +01:00
peer . _onDisconnected ( ) ;
2023-09-13 18:15:01 +02:00
}
_onRoomSecretsDeleted ( roomSecrets ) {
for ( let i = 0 ; i < roomSecrets . length ; i ++ ) {
this . _disconnectOrRemoveRoomTypeByRoomId ( 'secret' , roomSecrets [ i ] ) ;
}
}
_onLeavePublicRoom ( publicRoomId ) {
this . _disconnectOrRemoveRoomTypeByRoomId ( 'public-id' , publicRoomId ) ;
2018-09-21 16:05:03 +02:00
}
2023-01-10 05:07:57 +01:00
_onSecretRoomDeleted ( roomSecret ) {
2023-09-13 18:15:01 +02:00
this . _disconnectOrRemoveRoomTypeByRoomId ( 'secret' , roomSecret ) ;
}
_disconnectOrRemoveRoomTypeByRoomId ( roomType , roomId ) {
const peerIds = this . _getPeerIdsFromRoomId ( roomId ) ;
if ( ! peerIds . length ) return ;
for ( let i = 0 ; i < peerIds . length ; i ++ ) {
this . _disconnectOrRemoveRoomTypeByPeerId ( peerIds [ i ] , roomType ) ;
}
}
_disconnectOrRemoveRoomTypeByPeerId ( peerId , roomType ) {
const peer = this . peers [ peerId ] ;
if ( ! peer ) return ;
if ( peer . _getRoomTypes ( ) . length > 1 ) {
peer . _removeRoomType ( roomType ) ;
2023-11-02 02:56:01 +01:00
}
else {
2023-09-13 18:15:01 +02:00
Events . fire ( 'peer-disconnected' , peerId ) ;
2023-01-10 05:07:57 +01:00
}
2018-09-21 16:05:03 +02:00
}
2023-03-01 21:35:00 +01:00
2023-05-09 03:17:08 +02:00
_onRoomSecretRegenerated ( message ) {
2023-11-02 02:56:01 +01:00
PersistentStorage
. updateRoomSecret ( message . oldRoomSecret , message . newRoomSecret )
. then ( _ => {
2024-02-05 15:42:27 +01:00
Logger . debug ( "successfully regenerated room secret" ) ;
2023-11-02 02:56:01 +01:00
Events . fire ( "room-secrets" , [ message . newRoomSecret ] ) ;
} )
2023-05-09 03:17:08 +02:00
}
2023-03-01 21:35:00 +01:00
_notifyPeersDisplayNameChanged ( newDisplayName ) {
this . _displayName = newDisplayName ? newDisplayName : this . _originalDisplayName ;
for ( const peerId in this . peers ) {
this . _notifyPeerDisplayNameChanged ( peerId ) ;
}
}
_notifyPeerDisplayNameChanged ( peerId ) {
const peer = this . peers [ peerId ] ;
2023-03-06 03:36:46 +01:00
if ( ! peer ) return ;
2024-02-04 18:05:11 +01:00
this . peers [ peerId ] . _sendDisplayName ( this . _displayName ) ;
2023-03-01 21:35:00 +01:00
}
_onDisplayName ( displayName ) {
this . _originalDisplayName = displayName ;
2023-05-04 17:38:51 +02:00
// if the displayName has not been changed (yet) set the displayName to the original displayName
if ( ! this . _displayName ) this . _displayName = displayName ;
}
_onAutoAcceptUpdated ( roomSecret , autoAccept ) {
2023-09-13 18:15:01 +02:00
const peerId = this . _getPeerIdsFromRoomId ( roomSecret ) [ 0 ] ;
2023-05-04 17:38:51 +02:00
if ( ! peerId ) return ;
2023-09-13 18:15:01 +02:00
2023-05-04 17:38:51 +02:00
this . peers [ peerId ] . _setAutoAccept ( autoAccept ) ;
}
2023-09-13 18:15:01 +02:00
_getPeerIdsFromRoomId ( roomId ) {
if ( ! roomId ) return [ ] ;
let peerIds = [ ]
2023-05-04 17:38:51 +02:00
for ( const peerId in this . peers ) {
const peer = this . peers [ peerId ] ;
2023-09-13 18:15:01 +02:00
// peer must have same roomId.
if ( Object . values ( peer . _roomIds ) . includes ( roomId ) ) {
peerIds . push ( peer . _peerId ) ;
2023-05-04 17:38:51 +02:00
}
}
2023-09-13 18:15:01 +02:00
return peerIds ;
2023-03-01 21:35:00 +01:00
}
2018-09-21 16:05:03 +02:00
}
class FileChunker {
2024-02-05 02:06:53 +01:00
constructor ( file , onChunkCallback ) {
this . _chunkSize = 65536 ; // 64 KB
this . _maxBytesSentWithoutConfirmation = 1048576 ; // 1 MB
this . _bytesSent = 0 ;
this . _bytesReceived = 0 ;
2018-09-21 16:05:03 +02:00
this . _file = file ;
2024-02-05 02:06:53 +01:00
this . _onChunk = onChunkCallback ;
2018-09-21 16:05:03 +02:00
this . _reader = new FileReader ( ) ;
this . _reader . addEventListener ( 'load' , e => this . _onChunkRead ( e . target . result ) ) ;
2024-02-05 02:06:53 +01:00
this . _currentlySending = false ;
2018-09-21 16:05:03 +02:00
}
_readChunk ( ) {
2024-02-05 21:03:09 +01:00
if ( this . _currentlySending || ! this . _bufferHasSpaceForChunk ( ) || this . _isFileEnd ( ) ) return ;
2024-02-05 02:06:53 +01:00
this . _currentlySending = true ;
const chunk = this . _file . slice ( this . _bytesSent , this . _bytesSent + this . _chunkSize ) ;
2018-09-21 16:05:03 +02:00
this . _reader . readAsArrayBuffer ( chunk ) ;
}
2024-02-05 21:03:09 +01:00
_onChunkRead ( chunk ) {
if ( ! chunk . byteLength ) return ;
this . _currentlySending = false ;
this . _onChunk ( chunk ) ;
this . _bytesSent += chunk . byteLength ;
// Pause sending when reaching the high watermark or file end
if ( ! this . _bufferHasSpaceForChunk ( ) || this . _isFileEnd ( ) ) return ;
this . _readChunk ( ) ;
}
_bufferHasSpaceForChunk ( ) { }
2024-02-05 02:06:53 +01:00
2024-02-05 04:04:04 +01:00
_onReceiveConfirmation ( bytesReceived ) { }
2024-02-05 02:06:53 +01:00
2024-02-05 04:04:04 +01:00
_resendFromOffset ( offset ) {
2024-02-05 02:06:53 +01:00
this . _bytesSent = offset ;
this . _readChunk ( ) ;
}
_isFileEnd ( ) {
return this . _bytesSent >= this . _file . size ;
}
}
class FileChunkerRTC extends FileChunker {
constructor ( file , onChunkCallback , peerConnection , dataChannel ) {
super ( file , onChunkCallback ) ;
this . _chunkSize = peerConnection && peerConnection . sctp
? Math . min ( peerConnection . sctp . maxMessageSize , 1048576 ) // 1 MB max
: 262144 ; // 256 KB
this . _peerConnection = peerConnection ;
this . _dataChannel = dataChannel ;
2024-02-05 21:03:09 +01:00
this . _highWatermark = 10485760 ; // 10 MB
this . _lowWatermark = 4194304 ; // 4 MB
2024-02-05 02:06:53 +01:00
// Set buffer threshold
this . _dataChannel . bufferedAmountLowThreshold = this . _lowWatermark ;
this . _dataChannel . addEventListener ( 'bufferedamountlow' , _ => this . _readChunk ( ) ) ;
}
2024-02-05 21:03:09 +01:00
_bufferHasSpaceForChunk ( ) {
return this . _dataChannel . bufferedAmount + this . _chunkSize < this . _highWatermark ;
2018-09-21 16:05:03 +02:00
}
2024-02-05 04:04:04 +01:00
_onReceiveConfirmation ( bytesReceived ) {
2024-02-05 02:06:53 +01:00
this . _bytesReceived = bytesReceived ;
2024-02-04 18:02:10 +01:00
}
2024-02-05 02:06:53 +01:00
}
2024-02-04 18:02:10 +01:00
2024-02-05 02:06:53 +01:00
class FileChunkerWS extends FileChunker {
constructor ( file , onChunkCallback ) {
super ( file , onChunkCallback ) ;
2018-09-21 16:05:03 +02:00
}
2024-02-05 21:03:09 +01:00
_bytesCurrentlySent ( ) {
return this . _bytesSent - this . _bytesReceived ;
}
2024-02-05 02:06:53 +01:00
2024-02-05 21:03:09 +01:00
_bufferHasSpaceForChunk ( ) {
return this . _bytesCurrentlySent ( ) + this . _chunkSize <= this . _maxBytesSentWithoutConfirmation ;
2018-09-21 16:05:03 +02:00
}
2024-02-05 04:04:04 +01:00
_onReceiveConfirmation ( bytesReceived ) {
2024-02-05 02:06:53 +01:00
this . _bytesReceived = bytesReceived ;
this . _readChunk ( ) ;
2018-09-21 16:05:03 +02:00
}
}
class FileDigester {
2018-09-22 08:47:40 +02:00
2024-02-08 04:03:02 +01:00
constructor ( meta , totalSize , fileCompleteCallback , receiveConfirmationCallback = null ) {
2018-09-21 16:05:03 +02:00
this . _buffer = [ ] ;
this . _bytesReceived = 0 ;
2024-02-05 02:06:53 +01:00
this . _bytesReceivedSinceLastTime = 0 ;
this . _maxBytesWithoutConfirmation = 1048576 ; // 1 MB
2023-01-27 01:27:22 +01:00
this . _size = meta . size ;
this . _name = meta . name ;
this . _mime = meta . mime ;
this . _totalSize = totalSize ;
2024-02-07 04:11:56 +01:00
this . _fileCompleteCallback = fileCompleteCallback ;
2024-02-05 04:04:04 +01:00
this . _receiveConfimationCallback = receiveConfirmationCallback ;
2018-09-21 16:05:03 +02:00
}
unchunk ( chunk ) {
this . _buffer . push ( chunk ) ;
this . _bytesReceived += chunk . byteLength || chunk . size ;
2024-02-05 02:06:53 +01:00
this . _bytesReceivedSinceLastTime += chunk . byteLength || chunk . size ;
// If more than half of maxBytesWithoutConfirmation received -> request more
2024-02-05 04:04:04 +01:00
if ( this . _receiveConfimationCallback && 2 * this . _bytesReceivedSinceLastTime > this . _maxBytesWithoutConfirmation ) {
this . _receiveConfimationCallback ( this . _bytesReceived ) ;
2024-02-05 02:06:53 +01:00
this . _bytesReceivedSinceLastTime = 0 ;
}
2018-09-21 16:05:03 +02:00
if ( this . _bytesReceived < this . _size ) return ;
2024-02-05 02:06:53 +01:00
2018-09-22 08:47:40 +02:00
// we are done
2024-02-07 04:11:56 +01:00
if ( ! window . Worker && ! window . isSecureContext ) {
this . processFileViaMemory ( ) ;
return ;
}
this . processFileViaWorker ( ) ;
}
processFileViaMemory ( ) {
2024-02-08 00:46:51 +01:00
// Loads complete file into RAM which might lead to a page crash (Memory limit iOS Safari: ~380 MB)
if ( window . iOS && this . _totalSize > 250000000 ) {
alert ( 'File is bigger than 250 MB and might crash the page on iOS. To be able to use a more efficient method use https and avoid private tabs as they have restricted functionality.' )
}
2024-02-07 04:11:56 +01:00
Logger . warn ( 'Big file transfers might exceed the RAM of the receiver. Use a secure context (https) to prevent this.' ) ;
2024-02-05 21:03:09 +01:00
const file = new File ( this . _buffer , this . _name , {
2023-01-27 01:27:22 +01:00
type : this . _mime ,
lastModified : new Date ( ) . getTime ( )
2024-02-05 21:03:09 +01:00
} )
2024-02-07 04:11:56 +01:00
this . _fileCompleteCallback ( file ) ;
}
2024-02-05 21:03:09 +01:00
2024-02-07 04:11:56 +01:00
processFileViaWorker ( ) {
// Use service worker to prevent loading the complete file into RAM
const fileWorker = new Worker ( "scripts/sw-file-digester.js" ) ;
let i = 0 ;
let offset = 0 ;
const _this = this ;
function sendPart ( buffer , offset ) {
fileWorker . postMessage ( {
type : "part" ,
name : _this . _name ,
buffer : buffer ,
offset : offset
} ) ;
}
function getFile ( ) {
fileWorker . postMessage ( {
type : "get-file" ,
name : _this . _name ,
} ) ;
}
2024-02-09 03:34:07 +01:00
function deleteFile ( ) {
fileWorker . postMessage ( {
type : "delete-file" ,
name : _this . _name
} )
}
2024-02-07 04:11:56 +01:00
function onPart ( part ) {
// remove old chunk from buffer
_this . _buffer [ i ] = null ;
if ( i < _this . _buffer . length - 1 ) {
// process next chunk
offset += part . byteLength ;
i ++ ;
sendPart ( _this . _buffer [ i ] , offset ) ;
return ;
}
// File processing complete -> retrieve completed file
getFile ( ) ;
}
function onFile ( file ) {
_this . _buffer = [ ] ;
_this . _fileCompleteCallback ( file ) ;
2024-02-09 03:34:07 +01:00
deleteFile ( ) ;
}
function onFileDeleted ( ) {
// File Digestion complete -> Tidy up
fileWorker . terminate ( ) ;
2024-02-07 04:11:56 +01:00
}
function onError ( error ) {
2024-02-09 03:34:07 +01:00
// an error occurred.
2024-02-07 04:11:56 +01:00
Logger . error ( error ) ;
Logger . warn ( 'Failed to process file via service-worker. Do not use Firefox private mode to prevent this.' )
2024-02-09 03:34:07 +01:00
// Use memory method instead and tidy up.
2024-02-07 04:11:56 +01:00
_this . processFileViaMemory ( ) ;
2024-02-09 03:34:07 +01:00
fileWorker . terminate ( ) ;
2024-02-07 04:11:56 +01:00
}
sendPart ( this . _buffer [ i ] , offset ) ;
fileWorker . onmessage = ( e ) => {
switch ( e . data . type ) {
case "part" :
onPart ( e . data . part ) ;
break ;
case "file" :
onFile ( e . data . file ) ;
break ;
2024-02-09 03:34:07 +01:00
case "file-deleted" :
onFileDeleted ( ) ;
break ;
2024-02-07 04:11:56 +01:00
case "error" :
onError ( e . data . error ) ;
break ;
}
}
2018-09-21 16:05:03 +02:00
}
}