2018-09-21 16:05:03 +02:00
const $ = query => document . getElementById ( query ) ;
const $$ = query => document . body . querySelector ( query ) ;
2018-09-21 23:19:54 +02:00
window . isProductionEnvironment = ! window . location . host . startsWith ( 'localhost' ) ;
2018-10-11 00:08:07 +02:00
window . iOS = /iPad|iPhone|iPod/ . test ( navigator . userAgent ) && ! window . MSStream ;
2023-01-17 10:41:50 +01:00
window . android = /android/i . test ( navigator . userAgent ) ;
2022-11-09 17:43:27 +01:00
window . pasteMode = { } ;
window . pasteMode . activated = false ;
2018-09-21 16:05:03 +02:00
2020-07-12 19:23:07 +02:00
// set display name
2020-12-16 04:16:53 +01:00
Events . on ( 'display-name' , e => {
2020-12-19 21:05:48 +01:00
const me = e . detail . message ;
2023-03-01 21:35:00 +01:00
const $displayName = $ ( 'display-name' ) ;
$displayName . setAttribute ( 'placeholder' , me . displayName ) ;
2020-07-12 19:23:07 +02:00
} ) ;
2018-09-21 16:05:03 +02:00
class PeersUI {
constructor ( ) {
Events . on ( 'peer-joined' , e => this . _onPeerJoined ( e . detail ) ) ;
2023-02-16 02:19:14 +01:00
Events . on ( 'peer-connected' , e => this . _onPeerConnected ( e . detail . peerId , e . detail . connectionHash ) ) ;
2022-12-22 22:41:26 +01:00
Events . on ( 'peer-disconnected' , e => this . _onPeerDisconnected ( e . detail ) ) ;
2018-09-21 16:05:03 +02:00
Events . on ( 'peers' , e => this . _onPeers ( e . detail ) ) ;
2023-01-17 10:41:50 +01:00
Events . on ( 'set-progress' , e => this . _onSetProgress ( e . detail ) ) ;
2020-03-09 22:40:57 +01:00
Events . on ( 'paste' , e => this . _onPaste ( e ) ) ;
2023-01-23 04:51:22 +01:00
Events . on ( 'secret-room-deleted' , e => this . _onSecretRoomDeleted ( e . detail ) ) ;
2023-01-18 15:28:57 +01:00
Events . on ( 'activate-paste-mode' , e => this . _activatePasteMode ( e . detail . files , e . detail . text ) ) ;
2022-12-22 22:41:26 +01:00
this . peers = { } ;
2023-01-17 10:41:50 +01:00
2023-03-03 12:01:43 +01:00
this . $cancelPasteModeBtn = $ ( 'cancel-paste-mode' ) ;
2023-01-18 15:28:57 +01:00
this . $cancelPasteModeBtn . addEventListener ( 'click' , _ => this . _cancelPasteMode ( ) ) ;
2023-01-17 10:41:50 +01:00
2023-01-22 17:33:19 +01:00
Events . on ( 'dragover' , e => this . _onDragOver ( e ) ) ;
2023-01-22 16:14:27 +01:00
Events . on ( 'dragleave' , _ => this . _onDragEnd ( ) ) ;
Events . on ( 'dragend' , _ => this . _onDragEnd ( ) ) ;
Events . on ( 'drop' , e => this . _onDrop ( e ) ) ;
2023-01-17 10:41:50 +01:00
Events . on ( 'keydown' , e => this . _onKeyDown ( e ) ) ;
2023-01-22 16:14:27 +01:00
2023-03-01 10:04:37 +01:00
this . $xPeers = $$ ( 'x-peers' ) ;
2023-01-22 16:14:27 +01:00
this . $xNoPeers = $$ ( 'x-no-peers' ) ;
this . $xInstructions = $$ ( 'x-instructions' ) ;
2023-03-01 10:04:37 +01:00
Events . on ( 'peer-added' , _ => this . evaluateOverflowing ( ) ) ;
Events . on ( 'bg-resize' , _ => this . evaluateOverflowing ( ) ) ;
2023-03-01 21:35:00 +01:00
this . $displayName = $ ( 'display-name' ) ;
this . $displayName . addEventListener ( 'keydown' , e => this . _onKeyDownDisplayName ( e ) ) ;
this . $displayName . addEventListener ( 'keyup' , e => this . _onKeyUpDisplayName ( e ) ) ;
this . $displayName . addEventListener ( 'blur' , e => this . _saveDisplayName ( e . target . innerText ) ) ;
Events . on ( 'self-display-name-changed' , e => this . _insertDisplayName ( e . detail ) ) ;
Events . on ( 'peer-display-name-changed' , e => this . _changePeerDisplayName ( e . detail . peerId , e . detail . displayName ) ) ;
2023-03-02 15:06:22 +01:00
// Load saved display name on page load
this . _getSavedDisplayName ( ) . then ( displayName => {
2023-03-01 21:35:00 +01:00
console . log ( "Retrieved edited display name:" , displayName )
if ( displayName ) Events . fire ( 'self-display-name-changed' , displayName ) ;
} ) ;
}
_insertDisplayName ( displayName ) {
this . $displayName . textContent = displayName ;
}
_onKeyDownDisplayName ( e ) {
if ( e . key === "Enter" || e . key === "Escape" ) {
e . preventDefault ( ) ;
e . target . blur ( ) ;
}
}
_onKeyUpDisplayName ( e ) {
2023-03-02 15:06:22 +01:00
// fix for Firefox inserting a linebreak into div on edit which prevents the placeholder from showing automatically when it is empty
if ( /^(\n|\r|\r\n)$/ . test ( e . target . innerText ) ) e . target . innerText = '' ;
2023-03-01 21:35:00 +01:00
}
async _saveDisplayName ( newDisplayName ) {
2023-03-02 15:06:22 +01:00
newDisplayName = newDisplayName . replace ( /(\n|\r|\r\n)/ , '' )
const savedDisplayName = await this . _getSavedDisplayName ( ) ;
2023-03-01 21:35:00 +01:00
if ( newDisplayName === savedDisplayName ) return ;
if ( newDisplayName ) {
PersistentStorage . set ( 'editedDisplayName' , newDisplayName ) . then ( _ => {
2023-03-04 22:59:49 +01:00
Events . fire ( 'notify-user' , 'Device name is changed permanently.' ) ;
2023-03-02 15:06:22 +01:00
} ) . catch ( _ => {
console . log ( "This browser does not support IndexedDB. Use localStorage instead." ) ;
localStorage . setItem ( 'editedDisplayName' , newDisplayName ) ;
2023-03-04 22:59:49 +01:00
Events . fire ( 'notify-user' , 'Device name is changed only for this session.' ) ;
2023-03-02 15:06:22 +01:00
} ) . finally ( _ => {
2023-03-01 21:35:00 +01:00
Events . fire ( 'self-display-name-changed' , newDisplayName ) ;
Events . fire ( 'broadcast-send' , { type : 'self-display-name-changed' , detail : newDisplayName } ) ;
} ) ;
} else {
2023-03-02 15:06:22 +01:00
PersistentStorage . delete ( 'editedDisplayName' ) . catch ( _ => {
console . log ( "This browser does not support IndexedDB. Use localStorage instead." )
localStorage . removeItem ( 'editedDisplayName' ) ;
Events . fire ( 'notify-user' , 'Random Display name is used again.' ) ;
} ) . finally ( _ => {
2023-03-04 22:59:49 +01:00
Events . fire ( 'notify-user' , 'Device name is randomly generated again.' ) ;
2023-03-01 21:35:00 +01:00
Events . fire ( 'self-display-name-changed' , '' ) ;
Events . fire ( 'broadcast-send' , { type : 'self-display-name-changed' , detail : '' } ) ;
} ) ;
}
}
2023-03-02 15:06:22 +01:00
_getSavedDisplayName ( ) {
return new Promise ( ( resolve ) => {
PersistentStorage . get ( 'editedDisplayName' )
. then ( displayName => resolve ( displayName ? ? "" ) )
. catch ( _ => resolve ( localStorage . getItem ( 'editedDisplayName' ) ? ? "" ) )
} ) ;
}
2023-03-01 21:35:00 +01:00
_changePeerDisplayName ( peerId , displayName ) {
this . peers [ peerId ] . name . displayName = displayName ;
const peerIdNode = $ ( peerId ) ;
if ( peerIdNode && displayName ) peerIdNode . querySelector ( '.name' ) . textContent = displayName ;
2023-01-17 10:41:50 +01:00
}
_onKeyDown ( e ) {
2023-01-18 15:28:57 +01:00
if ( document . querySelectorAll ( 'x-dialog[show]' ) . length === 0 && window . pasteMode . activated && e . code === "Escape" ) {
2023-01-17 10:41:50 +01:00
Events . fire ( 'deactivate-paste-mode' ) ;
}
2018-09-21 16:05:03 +02:00
}
2023-01-10 05:07:57 +01:00
_onPeerJoined ( msg ) {
2023-01-18 15:44:20 +01:00
this . _joinPeer ( msg . peer , msg . roomType , msg . roomSecret ) ;
2023-01-10 05:07:57 +01:00
}
_joinPeer ( peer , roomType , roomSecret ) {
2023-03-01 10:04:37 +01:00
peer . roomTypes = [ roomType ] ;
2023-01-10 05:07:57 +01:00
peer . roomSecret = roomSecret ;
if ( this . peers [ peer . id ] ) {
2023-03-01 10:04:37 +01:00
if ( ! this . peers [ peer . id ] . roomTypes . includes ( roomType ) ) this . peers [ peer . id ] . roomTypes . push ( roomType ) ;
this . _redrawPeer ( this . peers [ peer . id ] ) ;
2023-01-10 05:07:57 +01:00
return ; // peer already exists
}
2022-12-22 22:41:26 +01:00
this . peers [ peer . id ] = peer ;
}
2023-02-16 02:19:14 +01:00
_onPeerConnected ( peerId , connectionHash ) {
2023-01-10 05:07:57 +01:00
if ( this . peers [ peerId ] && ! $ ( peerId ) )
2023-02-16 02:19:14 +01:00
new PeerUI ( this . peers [ peerId ] , connectionHash ) ;
2018-09-21 16:05:03 +02:00
}
2023-01-10 05:07:57 +01:00
_redrawPeer ( peer ) {
const peerNode = $ ( peer . id ) ;
if ( ! peerNode ) return ;
peerNode . classList . remove ( 'type-ip' , 'type-secret' ) ;
2023-03-01 10:04:37 +01:00
peer . roomTypes . forEach ( roomType => peerNode . classList . add ( ` type- ${ roomType } ` ) ) ;
}
evaluateOverflowing ( ) {
if ( this . $xPeers . clientHeight < this . $xPeers . scrollHeight ) {
this . $xPeers . classList . add ( 'overflowing' ) ;
} else {
this . $xPeers . classList . remove ( 'overflowing' ) ;
}
2023-01-10 05:07:57 +01:00
}
_onPeers ( msg ) {
msg . peers . forEach ( peer => this . _joinPeer ( peer , msg . roomType , msg . roomSecret ) ) ;
2018-09-21 16:05:03 +02:00
}
2022-12-22 22:41:26 +01:00
_onPeerDisconnected ( peerId ) {
2018-10-09 15:45:07 +02:00
const $peer = $ ( peerId ) ;
if ( ! $peer ) return ;
$peer . remove ( ) ;
2023-03-01 10:04:37 +01:00
this . evaluateOverflowing ( ) ;
2023-01-20 01:58:49 +01:00
if ( $$ ( 'x-peers:empty' ) ) setTimeout ( _ => window . animateBackground ( true ) , 1750 ) ; // Start animation again
2018-09-21 16:05:03 +02:00
}
2023-01-10 05:07:57 +01:00
_onSecretRoomDeleted ( roomSecret ) {
for ( const peerId in this . peers ) {
const peer = this . peers [ peerId ] ;
if ( peer . roomSecret === roomSecret ) {
2023-01-23 04:51:22 +01:00
this . _onPeerDisconnected ( peerId ) ;
2023-01-10 05:07:57 +01:00
}
}
}
2023-01-17 10:41:50 +01:00
_onSetProgress ( progress ) {
const $peer = $ ( progress . peerId ) ;
2018-10-09 15:45:07 +02:00
if ( ! $peer ) return ;
2023-01-17 10:41:50 +01:00
$peer . ui . setProgress ( progress . progress , progress . status )
2018-09-21 16:05:03 +02:00
}
2023-01-22 16:14:27 +01:00
_onDrop ( e ) {
e . preventDefault ( ) ;
2023-01-22 17:33:19 +01:00
if ( ! $$ ( 'x-peer' ) || ! $$ ( 'x-peer' ) . contains ( e . target ) ) {
2023-01-22 16:14:27 +01:00
this . _activatePasteMode ( e . dataTransfer . files , '' )
}
this . _onDragEnd ( ) ;
}
2023-01-22 17:33:19 +01:00
_onDragOver ( e ) {
e . preventDefault ( ) ;
2023-01-22 16:14:27 +01:00
this . $xInstructions . setAttribute ( 'drop-bg' , 1 ) ;
2023-01-22 17:33:19 +01:00
this . $xNoPeers . setAttribute ( 'drop-bg' , 1 ) ;
2023-01-22 16:14:27 +01:00
}
_onDragEnd ( ) {
this . $xInstructions . removeAttribute ( 'drop-bg' , 1 ) ;
2023-01-22 17:33:19 +01:00
this . $xNoPeers . removeAttribute ( 'drop-bg' ) ;
2023-01-22 16:14:27 +01:00
}
2020-12-22 21:23:10 +01:00
_onPaste ( e ) {
2023-01-07 03:04:48 +01:00
if ( document . querySelectorAll ( 'x-dialog[show]' ) . length === 0 ) {
2022-11-09 17:43:27 +01:00
// prevent send on paste when dialog is open
e . preventDefault ( )
const files = e . clipboardData . files ;
const text = e . clipboardData . getData ( "Text" ) ;
2023-01-18 15:28:57 +01:00
if ( files . length === 0 && text . length === 0 ) return ;
2022-11-09 17:43:27 +01:00
this . _activatePasteMode ( files , text ) ;
}
}
_activatePasteMode ( files , text ) {
2023-01-18 15:28:57 +01:00
if ( ! window . pasteMode . activated && ( files . length > 0 || text . length > 0 ) ) {
2022-11-09 17:43:27 +01:00
let descriptor ;
let noPeersMessage ;
if ( files . length === 1 ) {
descriptor = files [ 0 ] . name ;
2023-01-18 15:28:57 +01:00
noPeersMessage = ` Open PairDrop on other devices to send<br><i> ${ descriptor } </i> ` ;
2022-11-09 17:43:27 +01:00
} else if ( files . length > 1 ) {
2023-01-18 15:28:57 +01:00
descriptor = ` ${ files [ 0 ] . name } and ${ files . length - 1 } other files ` ;
noPeersMessage = ` Open PairDrop on other devices to send<br> ${ descriptor } ` ;
} else {
2023-02-20 17:42:02 +01:00
descriptor = "shared text" ;
2023-01-18 15:28:57 +01:00
noPeersMessage = ` Open PairDrop on other devices to send<br> ${ descriptor } ` ;
2022-11-09 17:43:27 +01:00
}
2023-01-22 16:14:27 +01:00
this . $xInstructions . querySelector ( 'p' ) . innerHTML = ` <i> ${ descriptor } </i> ` ;
this . $xInstructions . querySelector ( 'p' ) . style . display = 'block' ;
this . $xInstructions . setAttribute ( 'desktop' , ` Click to send ` ) ;
this . $xInstructions . setAttribute ( 'mobile' , ` Tap to send ` ) ;
2022-11-09 17:43:27 +01:00
2023-01-22 16:14:27 +01:00
this . $xNoPeers . querySelector ( 'h2' ) . innerHTML = noPeersMessage ;
2022-11-09 17:43:27 +01:00
const _callback = ( e ) => this . _sendClipboardData ( e , files , text ) ;
Events . on ( 'paste-pointerdown' , _callback ) ;
2023-03-06 11:59:56 +01:00
Events . on ( 'deactivate-paste-mode' , _ => this . _deactivatePasteMode ( _callback ) , { once : true } ) ;
2022-11-09 17:43:27 +01:00
2023-01-17 10:41:50 +01:00
this . $cancelPasteModeBtn . removeAttribute ( 'hidden' ) ;
2022-11-09 17:43:27 +01:00
window . pasteMode . descriptor = descriptor ;
window . pasteMode . activated = true ;
2023-01-18 15:28:57 +01:00
console . log ( 'Paste mode activated.' ) ;
Events . fire ( 'paste-mode-changed' ) ;
2022-11-09 17:43:27 +01:00
}
}
_cancelPasteMode ( ) {
2023-01-17 10:41:50 +01:00
Events . fire ( 'deactivate-paste-mode' ) ;
2022-11-09 17:43:27 +01:00
}
2023-01-17 10:41:50 +01:00
_deactivatePasteMode ( _callback ) {
if ( window . pasteMode . activated ) {
2022-11-09 17:43:27 +01:00
window . pasteMode . descriptor = undefined ;
window . pasteMode . activated = false ;
Events . off ( 'paste-pointerdown' , _callback ) ;
2023-01-22 16:14:27 +01:00
this . $xInstructions . querySelector ( 'p' ) . innerText = '' ;
this . $xInstructions . querySelector ( 'p' ) . style . display = 'none' ;
2023-01-18 15:28:57 +01:00
2023-01-22 16:14:27 +01:00
this . $xInstructions . setAttribute ( 'desktop' , 'Click to send files or right click to send a message' ) ;
this . $xInstructions . setAttribute ( 'mobile' , 'Tap to send files or long tap to send a message' ) ;
2023-01-18 15:28:57 +01:00
2023-01-22 16:14:27 +01:00
this . $xNoPeers . querySelector ( 'h2' ) . innerHTML = 'Open PairDrop on other devices to send files' ;
2023-01-18 15:28:57 +01:00
2023-01-19 06:43:24 +01:00
this . $cancelPasteModeBtn . setAttribute ( 'hidden' , "" ) ;
2022-11-09 17:43:27 +01:00
2023-01-18 15:28:57 +01:00
console . log ( 'Paste mode deactivated.' )
Events . fire ( 'paste-mode-changed' ) ;
2022-11-09 17:43:27 +01:00
}
}
_sendClipboardData ( e , files , text ) {
// send the pasted file/text content
const peerId = e . detail . peerId ;
if ( files . length > 0 ) {
2020-03-03 15:15:32 +01:00
Events . fire ( 'files-selected' , {
files : files ,
2022-11-09 17:43:27 +01:00
to : peerId
} ) ;
} else if ( text . length > 0 ) {
Events . fire ( 'send-text' , {
text : text ,
to : peerId
2020-03-03 15:15:32 +01:00
} ) ;
}
2018-09-21 16:05:03 +02:00
}
}
class PeerUI {
2023-03-01 10:04:37 +01:00
constructor ( peer , connectionHash ) {
this . _peer = peer ;
this . _connectionHash = connectionHash ;
this . _initDom ( ) ;
this . _bindListeners ( ) ;
$$ ( 'x-peers' ) . appendChild ( this . $el )
Events . fire ( 'peer-added' ) ;
this . $xInstructions = $$ ( 'x-instructions' ) ;
setTimeout ( _ => window . animateBackground ( false ) , 1750 ) ; // Stop animation
}
2018-09-21 16:05:03 +02:00
html ( ) {
2022-11-09 17:43:27 +01:00
let title ;
2023-01-18 15:28:57 +01:00
let input = '' ;
2022-11-09 17:43:27 +01:00
if ( window . pasteMode . activated ) {
2023-01-18 15:28:57 +01:00
title = ` Click to send ${ window . pasteMode . descriptor } ` ;
2022-11-09 17:43:27 +01:00
} else {
title = 'Click to send files or right click to send a message' ;
2023-01-14 01:43:44 +01:00
input = '<input type="file" multiple>' ;
2022-11-09 17:43:27 +01:00
}
2023-01-18 15:28:57 +01:00
this . $el . innerHTML = `
2022-11-09 17:43:27 +01:00
< label class = "column center" title = "${title}" >
2023-01-14 01:43:44 +01:00
$ { input }
2023-03-01 10:04:37 +01:00
< x - icon >
< div class = "icon-wrapper" shadow = "1" >
< svg class = "icon" > < use xlink : href = "#" / > < / s v g >
< / d i v >
< div class = "highlight-wrapper center" >
< div class = "highlight" shadow = "1" > < / d i v >
< / d i v >
2018-09-21 16:05:03 +02:00
< / x - i c o n >
< div class = "progress" >
< div class = "circle" > < / d i v >
< div class = "circle right" > < / d i v >
< / d i v >
2023-03-01 10:04:37 +01:00
< div class = "device-descriptor" >
< div class = "name font-subheading" > < / d i v >
< div class = "device-name font-body2" > < / d i v >
< div class = "status font-body2" > < / d i v >
< span class = "connection-hash font-body2" title = "To verify the security of the end-to-end encryption, compare this security number on both devices" > < / s p a n >
< / d i v >
2022-11-09 17:43:27 +01:00
< / l a b e l > ` ;
2023-01-18 15:28:57 +01:00
this . $el . querySelector ( 'svg use' ) . setAttribute ( 'xlink:href' , this . _icon ( ) ) ;
this . $el . querySelector ( '.name' ) . textContent = this . _displayName ( ) ;
this . $el . querySelector ( '.device-name' ) . textContent = this . _deviceName ( ) ;
2023-02-16 02:19:14 +01:00
this . $el . querySelector ( '.connection-hash' ) . textContent =
this . _connectionHash . substring ( 0 , 4 ) + " " + this . _connectionHash . substring ( 4 , 8 ) + " " + this . _connectionHash . substring ( 8 , 12 ) + " " + this . _connectionHash . substring ( 12 , 16 ) ;
2018-09-21 16:05:03 +02:00
}
_initDom ( ) {
2023-01-18 15:28:57 +01:00
this . $el = document . createElement ( 'x-peer' ) ;
this . $el . id = this . _peer . id ;
this . $el . ui = this ;
2023-03-01 10:04:37 +01:00
this . _peer . roomTypes . forEach ( roomType => this . $el . classList . add ( ` type- ${ roomType } ` ) ) ;
this . $el . classList . add ( 'center' ) ;
2023-01-18 15:28:57 +01:00
this . html ( ) ;
this . _callbackInput = e => this . _onFilesSelected ( e )
this . _callbackClickSleep = _ => NoSleepUI . enable ( )
this . _callbackTouchStartSleep = _ => NoSleepUI . enable ( )
this . _callbackDrop = e => this . _onDrop ( e )
this . _callbackDragEnd = e => this . _onDragEnd ( e )
this . _callbackDragLeave = e => this . _onDragEnd ( e )
this . _callbackDragOver = e => this . _onDragOver ( e )
this . _callbackContextMenu = e => this . _onRightClick ( e )
2023-03-01 10:04:37 +01:00
this . _callbackTouchStart = e => this . _onTouchStart ( e )
2023-01-18 15:28:57 +01:00
this . _callbackTouchEnd = e => this . _onTouchEnd ( e )
this . _callbackPointerDown = e => this . _onPointerDown ( e )
2023-01-22 17:33:19 +01:00
// PasteMode
2023-01-18 15:28:57 +01:00
Events . on ( 'paste-mode-changed' , _ => this . _onPasteModeChanged ( ) ) ;
}
_onPasteModeChanged ( ) {
this . html ( ) ;
this . _bindListeners ( ) ;
}
_bindListeners ( ) {
2022-11-09 17:43:27 +01:00
if ( ! window . pasteMode . activated ) {
2023-01-18 15:28:57 +01:00
// Remove Events Paste Mode
this . $el . removeEventListener ( 'pointerdown' , this . _callbackPointerDown ) ;
// Add Events Normal Mode
this . $el . querySelector ( 'input' ) . addEventListener ( 'change' , this . _callbackInput ) ;
this . $el . addEventListener ( 'click' , this . _callbackClickSleep ) ;
this . $el . addEventListener ( 'touchstart' , this . _callbackTouchStartSleep ) ;
this . $el . addEventListener ( 'drop' , this . _callbackDrop ) ;
this . $el . addEventListener ( 'dragend' , this . _callbackDragEnd ) ;
this . $el . addEventListener ( 'dragleave' , this . _callbackDragLeave ) ;
this . $el . addEventListener ( 'dragover' , this . _callbackDragOver ) ;
this . $el . addEventListener ( 'contextmenu' , this . _callbackContextMenu ) ;
this . $el . addEventListener ( 'touchstart' , this . _callbackTouchStart ) ;
this . $el . addEventListener ( 'touchend' , this . _callbackTouchEnd ) ;
2022-11-09 17:43:27 +01:00
} else {
2023-01-18 15:28:57 +01:00
// Remove Events Normal Mode
this . $el . removeEventListener ( 'click' , this . _callbackClickSleep ) ;
this . $el . removeEventListener ( 'touchstart' , this . _callbackTouchStartSleep ) ;
this . $el . removeEventListener ( 'drop' , this . _callbackDrop ) ;
this . $el . removeEventListener ( 'dragend' , this . _callbackDragEnd ) ;
this . $el . removeEventListener ( 'dragleave' , this . _callbackDragLeave ) ;
this . $el . removeEventListener ( 'dragover' , this . _callbackDragOver ) ;
this . $el . removeEventListener ( 'contextmenu' , this . _callbackContextMenu ) ;
this . $el . removeEventListener ( 'touchstart' , this . _callbackTouchStart ) ;
this . $el . removeEventListener ( 'touchend' , this . _callbackTouchEnd ) ;
// Add Events Paste Mode
this . $el . addEventListener ( 'pointerdown' , this . _callbackPointerDown ) ;
2022-11-09 17:43:27 +01:00
}
}
_onPointerDown ( e ) {
// Prevents triggering of event twice on touch devices
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
Events . fire ( 'paste-pointerdown' , {
peerId : this . _peer . id
} ) ;
2018-09-21 16:05:03 +02:00
}
2020-12-19 21:05:48 +01:00
_displayName ( ) {
2019-08-28 16:46:04 +02:00
return this . _peer . name . displayName ;
2018-09-21 16:05:03 +02:00
}
2020-12-19 21:05:48 +01:00
_deviceName ( ) {
return this . _peer . name . deviceName ;
}
2018-09-21 16:05:03 +02:00
_icon ( ) {
const device = this . _peer . name . device || this . _peer . name ;
if ( device . type === 'mobile' ) {
return '#phone-iphone' ;
}
if ( device . type === 'tablet' ) {
return '#tablet-mac' ;
}
return '#desktop-mac' ;
}
_onFilesSelected ( e ) {
const $input = e . target ;
const files = $input . files ;
Events . fire ( 'files-selected' , {
files : files ,
to : this . _peer . id
} ) ;
2023-01-17 10:41:50 +01:00
$input . files = null ; // reset input
2018-09-21 16:05:03 +02:00
}
2023-01-17 10:41:50 +01:00
setProgress ( progress , status ) {
2023-01-18 15:28:57 +01:00
const $progress = this . $el . querySelector ( '.progress' ) ;
2023-01-17 10:41:50 +01:00
if ( 0.5 < progress && progress < 1 ) {
2023-01-18 15:28:57 +01:00
$progress . classList . add ( 'over50' ) ;
2018-09-21 16:05:03 +02:00
} else {
2023-01-18 15:28:57 +01:00
$progress . classList . remove ( 'over50' ) ;
2018-09-21 16:05:03 +02:00
}
2023-01-17 10:41:50 +01:00
if ( progress < 1 ) {
this . $el . setAttribute ( 'status' , status ) ;
} else {
this . $el . removeAttribute ( 'status' ) ;
progress = 0 ;
}
2018-09-21 16:05:03 +02:00
const degrees = ` rotate( ${ 360 * progress } deg) ` ;
2023-01-18 15:28:57 +01:00
$progress . style . setProperty ( '--progress' , degrees ) ;
2018-09-21 16:05:03 +02:00
}
_onDrop ( e ) {
e . preventDefault ( ) ;
Events . fire ( 'files-selected' , {
2023-01-22 16:14:27 +01:00
files : e . dataTransfer . files ,
2018-09-21 16:05:03 +02:00
to : this . _peer . id
} ) ;
this . _onDragEnd ( ) ;
}
_onDragOver ( ) {
this . $el . setAttribute ( 'drop' , 1 ) ;
2023-01-22 16:14:27 +01:00
this . $xInstructions . setAttribute ( 'drop-peer' , 1 ) ;
2018-09-21 16:05:03 +02:00
}
_onDragEnd ( ) {
this . $el . removeAttribute ( 'drop' ) ;
2023-01-22 16:14:27 +01:00
this . $xInstructions . removeAttribute ( 'drop-peer' , 1 ) ;
2018-09-21 16:05:03 +02:00
}
_onRightClick ( e ) {
e . preventDefault ( ) ;
2023-03-01 10:04:37 +01:00
Events . fire ( 'text-recipient' , {
peerId : this . _peer . id ,
deviceName : e . target . closest ( 'x-peer' ) . querySelector ( '.name' ) . innerText
} ) ;
2018-09-21 16:05:03 +02:00
}
2023-03-01 10:04:37 +01:00
_onTouchStart ( e ) {
2018-09-21 16:05:03 +02:00
this . _touchStart = Date . now ( ) ;
2023-03-01 10:04:37 +01:00
this . _touchTimer = setTimeout ( _ => this . _onTouchEnd ( e ) , 610 ) ;
2018-09-21 16:05:03 +02:00
}
_onTouchEnd ( e ) {
if ( Date . now ( ) - this . _touchStart < 500 ) {
clearTimeout ( this . _touchTimer ) ;
2023-03-01 10:04:37 +01:00
} else if ( this . _touchTimer ) { // this was a long tap
e . preventDefault ( ) ;
Events . fire ( 'text-recipient' , {
peerId : this . _peer . id ,
deviceName : e . target . closest ( 'x-peer' ) . querySelector ( '.name' ) . innerText
} ) ;
2018-09-21 16:05:03 +02:00
}
2023-03-01 10:04:37 +01:00
this . _touchTimer = null ;
2018-09-21 16:05:03 +02:00
}
}
class Dialog {
2023-02-08 12:55:28 +01:00
constructor ( id ) {
2018-09-21 16:05:03 +02:00
this . $el = $ ( id ) ;
2023-02-10 03:26:08 +01:00
this . $el . querySelectorAll ( '[close]' ) . forEach ( el => el . addEventListener ( 'click' , _ => this . hide ( ) ) ) ;
2018-09-21 16:05:03 +02:00
this . $autoFocus = this . $el . querySelector ( '[autofocus]' ) ;
2023-02-08 12:55:28 +01:00
Events . on ( 'peer-disconnected' , e => this . _onPeerDisconnected ( e . detail ) ) ;
2018-09-21 16:05:03 +02:00
}
show ( ) {
this . $el . setAttribute ( 'show' , 1 ) ;
if ( this . $autoFocus ) this . $autoFocus . focus ( ) ;
}
hide ( ) {
this . $el . removeAttribute ( 'show' ) ;
2023-01-10 05:07:57 +01:00
if ( this . $autoFocus ) {
document . activeElement . blur ( ) ;
window . blur ( ) ;
}
2023-01-20 01:36:15 +01:00
document . title = 'PairDrop' ;
2023-01-20 15:56:20 +01:00
document . changeFavicon ( "images/favicon-96x96.png" ) ;
2023-01-23 04:51:22 +01:00
}
_onPeerDisconnected ( peerId ) {
2023-02-08 12:55:28 +01:00
if ( this . correspondingPeerId === peerId ) {
2023-01-23 04:51:22 +01:00
this . hide ( ) ;
Events . fire ( 'notify-user' , 'Selected peer left.' )
}
2018-09-21 16:05:03 +02:00
}
}
class ReceiveDialog extends Dialog {
2023-02-08 12:55:28 +01:00
constructor ( id ) {
super ( id ) ;
2023-03-03 12:01:43 +01:00
this . $fileDescription = this . $el . querySelector ( '.file-description' ) ;
this . $displayName = this . $el . querySelector ( '.display-name' ) ;
this . $fileStem = this . $el . querySelector ( '.file-stem' ) ;
this . $fileExtension = this . $el . querySelector ( '.file-extension' ) ;
this . $fileOther = this . $el . querySelector ( '.file-other' ) ;
this . $fileSize = this . $el . querySelector ( '.file-size' ) ;
this . $previewBox = this . $el . querySelector ( '.file-preview' ) ;
this . $receiveTitle = this . $el . querySelector ( 'h2:first-of-type' ) ;
2023-01-17 10:41:50 +01:00
}
_formatFileSize ( bytes ) {
2023-01-23 20:09:35 +01:00
// 1 GB = 1024 MB = 1024^2 KB = 1024^3 B
// 1024^2 = 104876; 1024^3 = 1073741824
if ( bytes >= 1073741824 ) {
return Math . round ( 10 * bytes / 1073741824 ) / 10 + ' GB' ;
} else if ( bytes >= 1048576 ) {
return Math . round ( bytes / 1048576 ) + ' MB' ;
} else if ( bytes > 1024 ) {
2023-01-25 09:59:38 +01:00
return Math . round ( bytes / 1024 ) + ' KB' ;
2023-01-17 10:41:50 +01:00
} else {
return bytes + ' Bytes' ;
}
}
2023-03-03 12:01:43 +01:00
_parseFileData ( displayName , files , imagesOnly , totalSize ) {
if ( files . length > 1 ) {
let fileOtherText = ` and ${ files . length - 1 } other ` ;
if ( files . length === 2 ) {
fileOtherText += imagesOnly ? 'image' : 'file' ;
} else {
fileOtherText += imagesOnly ? 'images' : 'files' ;
}
this . $fileOther . innerText = fileOtherText ;
}
const fileName = files [ 0 ] . name ;
const fileNameSplit = fileName . split ( '.' ) ;
const fileExtension = '.' + fileNameSplit [ fileNameSplit . length - 1 ] ;
this . $fileStem . innerText = fileName . substring ( 0 , fileName . length - fileExtension . length ) ;
this . $fileExtension . innerText = fileExtension ;
this . $displayName . innerText = displayName ;
this . $fileSize . innerText = this . _formatFileSize ( totalSize ) ;
}
2023-01-17 10:41:50 +01:00
}
2023-01-17 14:19:51 +01:00
2023-01-17 10:41:50 +01:00
class ReceiveFileDialog extends ReceiveDialog {
2018-09-21 16:05:03 +02:00
constructor ( ) {
2023-03-01 10:44:57 +01:00
super ( 'receive-file-dialog' ) ;
2023-01-17 10:41:50 +01:00
2023-03-03 12:01:43 +01:00
this . $downloadBtn = this . $el . querySelector ( '#download-btn' ) ;
this . $shareBtn = this . $el . querySelector ( '#share-btn' ) ;
2023-01-17 10:41:50 +01:00
2023-03-03 12:01:43 +01:00
Events . on ( 'files-received' , e => this . _onFilesReceived ( e . detail . sender , e . detail . files , e . detail . imagesOnly , e . detail . totalSize ) ) ;
2018-09-21 16:05:03 +02:00
this . _filesQueue = [ ] ;
}
2023-03-03 12:01:43 +01:00
_onFilesReceived ( sender , files , imagesOnly , totalSize ) {
const displayName = $ ( sender ) . ui . _displayName ( )
this . _filesQueue . push ( { peer : sender , displayName : displayName , files : files , imagesOnly : imagesOnly , totalSize : totalSize } ) ;
this . _nextFiles ( ) ;
2023-01-17 10:41:50 +01:00
window . blop . play ( ) ;
}
2023-03-03 12:01:43 +01:00
_nextFiles ( ) {
2018-09-21 16:05:03 +02:00
if ( this . _busy ) return ;
this . _busy = true ;
2023-03-03 12:01:43 +01:00
const { peer , displayName , files , imagesOnly , totalSize } = this . _filesQueue . shift ( ) ;
this . _displayFiles ( peer , displayName , files , imagesOnly , totalSize ) ;
2018-09-21 16:05:03 +02:00
}
_dequeueFile ( ) {
2023-03-01 21:35:00 +01:00
// Todo: change count in document.title and move '- PairDrop' to back
2018-09-21 16:05:03 +02:00
if ( ! this . _filesQueue . length ) { // nothing to do
this . _busy = false ;
return ;
}
// dequeue next file
setTimeout ( _ => {
this . _busy = false ;
2023-01-17 10:41:50 +01:00
this . _nextFiles ( ) ;
2018-09-21 16:05:03 +02:00
} , 300 ) ;
}
2023-01-17 10:41:50 +01:00
createPreviewElement ( file ) {
2023-01-21 18:21:58 +01:00
return new Promise ( ( resolve , reject ) => {
2023-01-17 10:41:50 +01:00
let mime = file . type . split ( '/' ) [ 0 ]
let previewElement = {
image : 'img' ,
audio : 'audio' ,
video : 'video'
}
if ( Object . keys ( previewElement ) . indexOf ( mime ) === - 1 ) {
resolve ( false ) ;
} else {
console . log ( 'the file is able to preview' ) ;
let element = document . createElement ( previewElement [ mime ] ) ;
element . src = URL . createObjectURL ( file ) ;
element . controls = true ;
2023-02-08 14:10:34 +01:00
element . onload = _ => {
this . $previewBox . appendChild ( element ) ;
resolve ( true )
} ;
2023-01-21 18:20:42 +01:00
element . addEventListener ( 'loadeddata' , _ => resolve ( true ) ) ;
2023-02-08 14:10:34 +01:00
element . onerror = _ => reject ( ` ${ mime } preview could not be loaded from type ${ file . type } ` ) ;
2023-01-17 10:41:50 +01:00
}
} ) ;
}
2023-03-03 12:01:43 +01:00
async _displayFiles ( peerId , displayName , files , imagesOnly , totalSize ) {
this . _parseFileData ( displayName , files , imagesOnly , totalSize ) ;
2023-01-27 01:27:22 +01:00
2023-03-03 12:01:43 +01:00
let descriptor , url , filenameDownload ;
2023-01-17 10:41:50 +01:00
if ( files . length === 1 ) {
2023-03-03 12:01:43 +01:00
descriptor = imagesOnly ? 'Image' : 'File' ;
2023-01-17 10:41:50 +01:00
} else {
2023-03-03 12:01:43 +01:00
descriptor = imagesOnly ? 'Images' : 'Files' ;
}
this . $receiveTitle . innerText = ` ${ descriptor } Received ` ;
2023-01-17 10:41:50 +01:00
2023-03-03 12:01:43 +01:00
const canShare = ( window . iOS || window . android ) && ! ! navigator . share && navigator . canShare ( { files } ) ;
if ( canShare ) {
this . $shareBtn . removeAttribute ( 'hidden' ) ;
this . $shareBtn . onclick = _ => {
navigator . share ( { files : files } )
. catch ( err => {
console . error ( err ) ;
} ) ;
}
}
2023-01-17 10:41:50 +01:00
2023-03-03 12:01:43 +01:00
let downloadZipped = false ;
if ( files . length > 1 ) {
downloadZipped = true ;
try {
2023-01-27 01:27:22 +01:00
let bytesCompleted = 0 ;
zipper . createNewZipWriter ( ) ;
for ( let i = 0 ; i < files . length ; i ++ ) {
await zipper . addFile ( files [ i ] , {
onprogress : ( progress ) => {
Events . fire ( 'set-progress' , {
peerId : peerId ,
2023-03-03 12:01:43 +01:00
progress : ( bytesCompleted + progress ) / totalSize ,
2023-01-27 01:27:22 +01:00
status : 'process'
} )
}
} ) ;
bytesCompleted += files [ i ] . size ;
}
url = await zipper . getBlobURL ( ) ;
let now = new Date ( Date . now ( ) ) ;
let year = now . getFullYear ( ) . toString ( ) ;
let month = ( now . getMonth ( ) + 1 ) . toString ( ) ;
month = month . length < 2 ? "0" + month : month ;
let date = now . getDate ( ) . toString ( ) ;
date = date . length < 2 ? "0" + date : date ;
let hours = now . getHours ( ) . toString ( ) ;
hours = hours . length < 2 ? "0" + hours : hours ;
let minutes = now . getMinutes ( ) . toString ( ) ;
minutes = minutes . length < 2 ? "0" + minutes : minutes ;
filenameDownload = ` PairDrop_files_ ${ year + month + date } _ ${ hours + minutes } .zip ` ;
2023-03-03 12:01:43 +01:00
} catch ( e ) {
console . error ( e ) ;
downloadZipped = false ;
2023-01-17 10:41:50 +01:00
}
}
2023-03-03 12:01:43 +01:00
this . $downloadBtn . innerText = "Download" ;
this . $downloadBtn . onclick = _ => {
if ( downloadZipped ) {
let tmpZipBtn = document . createElement ( "a" ) ;
tmpZipBtn . download = filenameDownload ;
tmpZipBtn . href = url ;
tmpZipBtn . click ( ) ;
} else {
this . _downloadFilesIndividually ( files ) ;
}
2023-01-17 10:41:50 +01:00
2023-03-03 12:01:43 +01:00
if ( ! canShare ) {
this . $downloadBtn . innerText = "Download again" ;
2023-01-17 10:41:50 +01:00
}
2023-03-03 12:01:43 +01:00
Events . fire ( 'notify-user' , ` ${ descriptor } downloaded successfully ` ) ;
this . $downloadBtn . style . pointerEvents = "none" ;
setTimeout ( _ => this . $downloadBtn . style . pointerEvents = "unset" , 2000 ) ;
} ;
2023-01-17 10:41:50 +01:00
2023-01-21 18:20:42 +01:00
this . createPreviewElement ( files [ 0 ] ) . finally ( _ => {
2023-03-02 15:30:25 +01:00
document . title = files . length === 1
? 'File received - PairDrop'
2023-03-03 17:03:10 +01:00
: ` ${ files . length } Files received - PairDrop ` ;
2023-01-20 15:56:20 +01:00
document . changeFavicon ( "images/favicon-96x96-notification.png" ) ;
2023-01-27 01:27:22 +01:00
Events . fire ( 'set-progress' , { peerId : peerId , progress : 1 , status : 'process' } )
2023-03-03 12:01:43 +01:00
this . show ( ) ;
if ( canShare ) {
this . $shareBtn . click ( ) ;
} else {
this . $downloadBtn . click ( ) ;
}
2023-01-21 18:21:58 +01:00
} ) . catch ( r => console . error ( r ) ) ;
2023-01-17 10:41:50 +01:00
}
2023-03-03 12:01:43 +01:00
_downloadFilesIndividually ( files ) {
let tmpBtn = document . createElement ( "a" ) ;
for ( let i = 0 ; i < files . length ; i ++ ) {
tmpBtn . download = files [ i ] . name ;
tmpBtn . href = URL . createObjectURL ( files [ i ] ) ;
tmpBtn . click ( ) ;
}
}
2023-01-17 10:41:50 +01:00
hide ( ) {
2023-03-03 12:01:43 +01:00
this . $shareBtn . setAttribute ( 'hidden' , '' ) ;
2023-01-17 10:41:50 +01:00
this . $previewBox . innerHTML = '' ;
super . hide ( ) ;
this . _dequeueFile ( ) ;
}
}
class ReceiveRequestDialog extends ReceiveDialog {
2018-09-21 16:05:03 +02:00
2023-01-17 10:41:50 +01:00
constructor ( ) {
2023-03-01 10:44:57 +01:00
super ( 'receive-request-dialog' ) ;
2023-01-17 10:41:50 +01:00
2023-03-01 10:44:57 +01:00
this . $acceptRequestBtn = this . $el . querySelector ( '#accept-request' ) ;
this . $declineRequestBtn = this . $el . querySelector ( '#decline-request' ) ;
2023-01-17 10:41:50 +01:00
this . $acceptRequestBtn . addEventListener ( 'click' , _ => this . _respondToFileTransferRequest ( true ) ) ;
this . $declineRequestBtn . addEventListener ( 'click' , _ => this . _respondToFileTransferRequest ( false ) ) ;
Events . on ( 'files-transfer-request' , e => this . _onRequestFileTransfer ( e . detail . request , e . detail . peerId ) )
Events . on ( 'keydown' , e => this . _onKeyDown ( e ) ) ;
2023-02-08 12:55:28 +01:00
this . _filesTransferRequestQueue = [ ] ;
2023-01-17 10:41:50 +01:00
}
_onKeyDown ( e ) {
if ( this . $el . attributes [ "show" ] && e . code === "Escape" ) {
2023-02-10 03:26:08 +01:00
this . _respondToFileTransferRequest ( false ) ;
2020-12-20 21:30:28 +01:00
}
2023-01-17 10:41:50 +01:00
}
2023-01-10 16:03:52 +01:00
2023-01-17 10:41:50 +01:00
_onRequestFileTransfer ( request , peerId ) {
2023-02-08 12:55:28 +01:00
this . _filesTransferRequestQueue . push ( { request : request , peerId : peerId } ) ;
if ( this . $el . attributes [ "show" ] ) return ;
this . _dequeueRequests ( ) ;
}
_dequeueRequests ( ) {
if ( ! this . _filesTransferRequestQueue . length ) return ;
let { request , peerId } = this . _filesTransferRequestQueue . shift ( ) ;
this . _showRequestDialog ( request , peerId )
}
_showRequestDialog ( request , peerId ) {
2023-01-23 04:51:22 +01:00
this . correspondingPeerId = peerId ;
2023-01-10 16:03:52 +01:00
2023-03-03 12:01:43 +01:00
const displayName = $ ( peerId ) . ui . _displayName ( ) ;
this . _parseFileData ( displayName , request . header , request . imagesOnly , request . totalSize ) ;
2023-01-17 10:41:50 +01:00
2023-01-27 01:27:22 +01:00
if ( request . thumbnailDataUrl ? . substring ( 0 , 22 ) === "data:image/jpeg;base64" ) {
2023-01-17 10:41:50 +01:00
let element = document . createElement ( 'img' ) ;
element . src = request . thumbnailDataUrl ;
2023-01-10 16:03:52 +01:00
this . $previewBox . appendChild ( element )
2021-06-02 18:56:08 +02:00
}
2020-12-20 21:30:28 +01:00
2023-03-03 12:01:43 +01:00
this . $receiveTitle . innerText = ` ${ request . imagesOnly ? 'Image' : 'File' } Transfer Request `
document . title = ` ${ request . imagesOnly ? 'Image' : 'File' } Transfer Requested - PairDrop ` ;
2023-01-20 15:56:20 +01:00
document . changeFavicon ( "images/favicon-96x96-notification.png" ) ;
2023-02-10 03:26:08 +01:00
this . show ( ) ;
2018-09-21 16:05:03 +02:00
}
2023-01-17 10:41:50 +01:00
_respondToFileTransferRequest ( accepted ) {
Events . fire ( 'respond-to-files-transfer-request' , {
2023-01-23 04:51:22 +01:00
to : this . correspondingPeerId ,
2023-01-17 10:41:50 +01:00
accepted : accepted
} )
if ( accepted ) {
2023-01-23 04:51:22 +01:00
Events . fire ( 'set-progress' , { peerId : this . correspondingPeerId , progress : 0 , status : 'wait' } ) ;
2023-01-17 14:19:51 +01:00
NoSleepUI . enable ( ) ;
2018-09-21 16:05:03 +02:00
}
2023-02-08 12:55:28 +01:00
this . hide ( ) ;
2018-09-21 16:05:03 +02:00
}
hide ( ) {
2023-01-10 16:03:52 +01:00
this . $previewBox . innerHTML = '' ;
2018-09-21 16:05:03 +02:00
super . hide ( ) ;
2023-02-10 03:26:08 +01:00
setTimeout ( _ => this . _dequeueRequests ( ) , 500 ) ;
2020-12-20 21:30:28 +01:00
}
2018-09-21 16:05:03 +02:00
}
2023-01-10 05:07:57 +01:00
class PairDeviceDialog extends Dialog {
constructor ( ) {
2023-03-01 10:44:57 +01:00
super ( 'pair-device-dialog' ) ;
this . $inputRoomKeyChars = this . $el . querySelectorAll ( '#key-input-container>input' ) ;
2023-01-10 05:07:57 +01:00
this . $submitBtn = this . $el . querySelector ( 'button[type="submit"]' ) ;
2023-03-01 10:44:57 +01:00
this . $roomKey = this . $el . querySelector ( '#room-key' ) ;
this . $qrCode = this . $el . querySelector ( '#room-key-qr-code' ) ;
2023-03-04 22:59:49 +01:00
this . $pairDeviceBtn = $ ( 'pair-device' ) ;
2023-01-10 05:07:57 +01:00
this . $clearSecretsBtn = $ ( 'clear-pair-devices' ) ;
2023-02-08 04:06:15 +01:00
this . $footerInstructionsPairedDevices = $ ( 'and-by-paired-devices' ) ;
2023-03-04 22:59:49 +01:00
this . $createJoinForm = this . $el . querySelector ( 'form' ) ;
this . $createJoinForm . addEventListener ( 'submit' , e => this . _onSubmit ( e ) ) ;
this . $pairDeviceBtn . addEventListener ( 'click' , _ => this . _pairDeviceInitiate ( ) ) ;
2023-01-10 05:07:57 +01:00
this . $el . querySelector ( '[close]' ) . addEventListener ( 'click' , _ => this . _pairDeviceCancel ( ) )
this . $inputRoomKeyChars . forEach ( el => el . addEventListener ( 'input' , e => this . _onCharsInput ( e ) ) ) ;
this . $inputRoomKeyChars . forEach ( el => el . addEventListener ( 'keydown' , e => this . _onCharsKeyDown ( e ) ) ) ;
2023-01-17 10:11:17 +01:00
this . $inputRoomKeyChars . forEach ( el => el . addEventListener ( 'focus' , e => e . target . select ( ) ) ) ;
this . $inputRoomKeyChars . forEach ( el => el . addEventListener ( 'click' , e => e . target . select ( ) ) ) ;
2023-01-10 05:07:57 +01:00
Events . on ( 'keydown' , e => this . _onKeyDown ( e ) ) ;
Events . on ( 'ws-connected' , _ => this . _onWsConnected ( ) ) ;
2023-01-23 04:51:22 +01:00
Events . on ( 'ws-disconnected' , _ => this . hide ( ) ) ;
2023-01-10 05:07:57 +01:00
Events . on ( 'pair-device-initiated' , e => this . _pairDeviceInitiated ( e . detail ) ) ;
2023-02-11 00:52:37 +01:00
Events . on ( 'pair-device-joined' , e => this . _pairDeviceJoined ( e . detail . peerId , e . detail . roomSecret ) ) ;
2023-01-10 05:07:57 +01:00
Events . on ( 'pair-device-join-key-invalid' , _ => this . _pairDeviceJoinKeyInvalid ( ) ) ;
Events . on ( 'pair-device-canceled' , e => this . _pairDeviceCanceled ( e . detail ) ) ;
Events . on ( 'clear-room-secrets' , e => this . _onClearRoomSecrets ( e . detail ) )
Events . on ( 'secret-room-deleted' , e => this . _onSecretRoomDeleted ( e . detail ) ) ;
this . $el . addEventListener ( 'paste' , e => this . _onPaste ( e ) ) ;
this . evaluateRoomKeyChars ( ) ;
this . evaluateUrlAttributes ( ) ;
}
_onCharsInput ( e ) {
e . target . value = e . target . value . replace ( /\D/g , '' ) ;
if ( ! e . target . value ) return ;
2023-01-17 10:11:17 +01:00
this . evaluateRoomKeyChars ( ) ;
2023-01-10 05:07:57 +01:00
let nextSibling = e . target . nextElementSibling ;
if ( nextSibling ) {
e . preventDefault ( ) ;
nextSibling . focus ( ) ;
}
}
_onKeyDown ( e ) {
2023-01-18 15:44:20 +01:00
if ( this . $el . attributes [ "show" ] && e . code === "Escape" ) {
// Timeout to prevent paste mode from getting cancelled simultaneously
setTimeout ( _ => this . _pairDeviceCancel ( ) , 50 ) ;
2023-01-10 05:07:57 +01:00
}
}
_onCharsKeyDown ( e ) {
let previousSibling = e . target . previousElementSibling ;
let nextSibling = e . target . nextElementSibling ;
if ( e . key === "Backspace" && previousSibling && ! e . target . value ) {
previousSibling . value = '' ;
previousSibling . focus ( ) ;
} else if ( e . key === "ArrowRight" && nextSibling ) {
e . preventDefault ( ) ;
nextSibling . focus ( ) ;
} else if ( e . key === "ArrowLeft" && previousSibling ) {
e . preventDefault ( ) ;
previousSibling . focus ( ) ;
}
}
_onPaste ( e ) {
e . preventDefault ( ) ;
let num = e . clipboardData . getData ( "Text" ) . replace ( /\D/g , '' ) . substring ( 0 , 6 ) ;
for ( let i = 0 ; i < num . length ; i ++ ) {
document . activeElement . value = num . charAt ( i ) ;
let nextSibling = document . activeElement . nextElementSibling ;
if ( ! nextSibling ) break ;
nextSibling . focus ( ) ;
}
2023-01-19 06:43:24 +01:00
this . evaluateRoomKeyChars ( ) ;
2023-01-10 05:07:57 +01:00
}
evaluateRoomKeyChars ( ) {
2023-03-01 10:44:57 +01:00
if ( this . $el . querySelectorAll ( '#key-input-container>input:placeholder-shown' ) . length > 0 ) {
2023-01-10 05:07:57 +01:00
this . $submitBtn . setAttribute ( "disabled" , "" ) ;
} else {
this . inputRoomKey = "" ;
this . $inputRoomKeyChars . forEach ( el => {
this . inputRoomKey += el . value ;
} )
this . $submitBtn . removeAttribute ( "disabled" ) ;
2023-01-17 10:11:17 +01:00
if ( document . activeElement === this . $inputRoomKeyChars [ 5 ] ) {
2023-03-02 16:30:47 +01:00
this . _pairDeviceJoin ( this . inputRoomKey ) ;
2023-01-17 10:11:17 +01:00
}
2023-01-10 05:07:57 +01:00
}
}
evaluateUrlAttributes ( ) {
const urlParams = new URLSearchParams ( window . location . search ) ;
if ( urlParams . has ( 'room_key' ) ) {
this . _pairDeviceJoin ( urlParams . get ( 'room_key' ) ) ;
window . history . replaceState ( { } , "title**" , '/' ) ; //remove room_key from url
}
}
_onWsConnected ( ) {
2023-03-04 22:59:49 +01:00
this . $pairDeviceBtn . removeAttribute ( 'hidden' ) ;
2023-01-10 05:07:57 +01:00
PersistentStorage . getAllRoomSecrets ( ) . then ( roomSecrets => {
Events . fire ( 'room-secrets' , roomSecrets ) ;
this . _evaluateNumberRoomSecrets ( ) ;
2023-01-22 17:34:33 +01:00
} ) . catch ( _ => PersistentStorage . logBrowserNotCapable ( ) ) ;
2023-01-10 05:07:57 +01:00
}
_pairDeviceInitiate ( ) {
Events . fire ( 'pair-device-initiate' ) ;
}
_pairDeviceInitiated ( msg ) {
this . roomKey = msg . roomKey ;
this . roomSecret = msg . roomSecret ;
this . $roomKey . innerText = ` ${ this . roomKey . substring ( 0 , 3 ) } ${ this . roomKey . substring ( 3 , 6 ) } `
// Display the QR code for the url
const qr = new QRCode ( {
content : this . _getShareRoomURL ( ) ,
2023-02-24 16:08:36 +01:00
width : 150 ,
height : 150 ,
2023-01-10 05:07:57 +01:00
padding : 0 ,
background : "transparent" ,
2023-03-01 10:04:37 +01:00
color : ` rgb(var(--text-color)) ` ,
2023-01-10 05:07:57 +01:00
ecl : "L" ,
join : true
} ) ;
this . $qrCode . innerHTML = qr . svg ( ) ;
2023-01-17 10:11:17 +01:00
this . $inputRoomKeyChars . forEach ( el => el . removeAttribute ( "disabled" ) ) ;
2023-01-10 05:07:57 +01:00
this . show ( ) ;
}
_getShareRoomURL ( ) {
let url = new URL ( location . href ) ;
url . searchParams . append ( 'room_key' , this . roomKey )
return url . href ;
}
2023-03-02 16:30:47 +01:00
_onSubmit ( e ) {
e . preventDefault ( ) ;
2023-01-10 05:07:57 +01:00
this . _pairDeviceJoin ( this . inputRoomKey ) ;
}
_pairDeviceJoin ( roomKey ) {
if ( /^\d{6}$/g . test ( roomKey ) ) {
roomKey = roomKey . substring ( 0 , 6 ) ;
Events . fire ( 'pair-device-join' , roomKey ) ;
let lastChar = this . $inputRoomKeyChars [ 5 ] ;
lastChar . focus ( ) ;
}
}
2023-02-11 00:52:37 +01:00
_pairDeviceJoined ( peerId , roomSecret ) {
2023-01-10 05:07:57 +01:00
this . hide ( ) ;
PersistentStorage . addRoomSecret ( roomSecret ) . then ( _ => {
2023-02-11 00:52:37 +01:00
Events . fire ( 'notify-user' , 'Devices paired successfully.' ) ;
const oldRoomSecret = $ ( peerId ) . ui . roomSecret ;
if ( oldRoomSecret ) PersistentStorage . deleteRoomSecret ( oldRoomSecret ) ;
$ ( peerId ) . ui . roomSecret = roomSecret ;
2023-02-08 04:06:42 +01:00
this . _evaluateNumberRoomSecrets ( ) ;
2023-01-10 05:07:57 +01:00
} ) . finally ( _ => {
2023-02-08 04:06:42 +01:00
this . _cleanUp ( ) ;
2023-01-10 05:07:57 +01:00
} )
2023-01-22 17:43:03 +01:00
. catch ( _ => {
2023-02-11 00:52:37 +01:00
Events . fire ( 'notify-user' , 'Paired devices are not persistent.' ) ;
PersistentStorage . logBrowserNotCapable ( ) ;
2023-01-22 17:43:03 +01:00
} ) ;
2023-01-10 05:07:57 +01:00
}
_pairDeviceJoinKeyInvalid ( ) {
2023-02-11 00:52:37 +01:00
Events . fire ( 'notify-user' , 'Key not valid' ) ;
2023-01-10 05:07:57 +01:00
}
_pairDeviceCancel ( ) {
this . hide ( ) ;
this . _cleanUp ( ) ;
Events . fire ( 'pair-device-cancel' ) ;
}
_pairDeviceCanceled ( roomKey ) {
2023-02-11 00:52:37 +01:00
Events . fire ( 'notify-user' , ` Key ${ roomKey } invalidated. ` ) ;
2023-01-10 05:07:57 +01:00
}
_cleanUp ( ) {
this . roomSecret = null ;
this . roomKey = null ;
this . inputRoomKey = '' ;
this . $inputRoomKeyChars . forEach ( el => el . value = '' ) ;
2023-01-17 10:11:17 +01:00
this . $inputRoomKeyChars . forEach ( el => el . setAttribute ( "disabled" , "" ) ) ;
2023-01-10 05:07:57 +01:00
}
_onClearRoomSecrets ( ) {
PersistentStorage . getAllRoomSecrets ( ) . then ( roomSecrets => {
Events . fire ( 'room-secrets-cleared' , roomSecrets ) ;
PersistentStorage . clearRoomSecrets ( ) . finally ( _ => {
Events . fire ( 'notify-user' , 'All Devices unpaired.' )
this . _evaluateNumberRoomSecrets ( ) ;
} )
2023-01-22 17:34:33 +01:00
} ) . catch ( _ => PersistentStorage . logBrowserNotCapable ( ) ) ;
2023-01-10 05:07:57 +01:00
}
_onSecretRoomDeleted ( roomSecret ) {
PersistentStorage . deleteRoomSecret ( roomSecret ) . then ( _ => {
this . _evaluateNumberRoomSecrets ( ) ;
} ) . catch ( e => console . error ( e ) ) ;
}
_evaluateNumberRoomSecrets ( ) {
PersistentStorage . getAllRoomSecrets ( ) . then ( roomSecrets => {
if ( roomSecrets . length > 0 ) {
this . $clearSecretsBtn . removeAttribute ( 'hidden' ) ;
2023-02-08 04:06:15 +01:00
this . $footerInstructionsPairedDevices . removeAttribute ( 'hidden' ) ;
2023-01-10 05:07:57 +01:00
} else {
this . $clearSecretsBtn . setAttribute ( 'hidden' , '' ) ;
2023-02-08 04:06:15 +01:00
this . $footerInstructionsPairedDevices . setAttribute ( 'hidden' , '' ) ;
2023-01-10 05:07:57 +01:00
}
2023-03-01 10:04:37 +01:00
Events . fire ( 'bg-resize' ) ;
2023-01-22 17:34:33 +01:00
} ) . catch ( _ => PersistentStorage . logBrowserNotCapable ( ) ) ;
2023-01-10 05:07:57 +01:00
}
}
class ClearDevicesDialog extends Dialog {
constructor ( ) {
2023-03-01 10:44:57 +01:00
super ( 'clear-devices-dialog' ) ;
2023-01-10 05:07:57 +01:00
$ ( 'clear-pair-devices' ) . addEventListener ( 'click' , _ => this . _onClearPairDevices ( ) ) ;
let clearDevicesForm = this . $el . querySelector ( 'form' ) ;
2023-03-02 16:30:47 +01:00
clearDevicesForm . addEventListener ( 'submit' , e => this . _onSubmit ( e ) ) ;
2023-01-10 05:07:57 +01:00
}
_onClearPairDevices ( ) {
this . show ( ) ;
}
2023-03-02 16:30:47 +01:00
_onSubmit ( e ) {
e . preventDefault ( ) ;
this . _clearRoomSecrets ( ) ;
}
_clearRoomSecrets ( ) {
2023-01-10 05:07:57 +01:00
Events . fire ( 'clear-room-secrets' ) ;
this . hide ( ) ;
}
}
2018-09-21 16:05:03 +02:00
class SendTextDialog extends Dialog {
constructor ( ) {
2023-03-01 10:44:57 +01:00
super ( 'send-text-dialog' ) ;
2023-03-01 10:04:37 +01:00
Events . on ( 'text-recipient' , e => this . _onRecipient ( e . detail . peerId , e . detail . deviceName ) ) ;
2023-03-01 10:44:57 +01:00
this . $text = this . $el . querySelector ( '#text-input' ) ;
2023-03-03 12:01:43 +01:00
this . $peerDisplayName = this . $el . querySelector ( '.display-name' ) ;
2023-02-10 03:26:08 +01:00
this . $form = this . $el . querySelector ( 'form' ) ;
this . $submit = this . $el . querySelector ( 'button[type="submit"]' ) ;
2023-03-02 16:30:47 +01:00
this . $form . addEventListener ( 'submit' , e => this . _onSubmit ( e ) ) ;
2023-02-10 03:26:08 +01:00
this . $text . addEventListener ( 'input' , e => this . _onChange ( e ) ) ;
2023-01-18 15:44:20 +01:00
Events . on ( "keydown" , e => this . _onKeyDown ( e ) ) ;
2022-12-30 20:34:54 +01:00
}
async _onKeyDown ( e ) {
2023-01-14 01:43:44 +01:00
if ( this . $el . attributes [ "show" ] ) {
if ( e . code === "Escape" ) {
this . hide ( ) ;
2023-01-25 10:01:45 +01:00
} else if ( e . code === "Enter" && ( e . ctrlKey || e . metaKey ) ) {
2023-02-10 03:26:08 +01:00
if ( this . _textInputEmpty ( ) ) return ;
2023-01-17 10:47:44 +01:00
this . _send ( ) ;
2023-01-14 01:43:44 +01:00
}
2022-12-30 20:34:54 +01:00
}
2018-09-21 16:05:03 +02:00
}
2023-02-10 03:26:08 +01:00
_textInputEmpty ( ) {
return this . $text . innerText === "\n" ;
}
_onChange ( e ) {
if ( this . _textInputEmpty ( ) ) {
this . $submit . setAttribute ( 'disabled' , '' ) ;
} else {
this . $submit . removeAttribute ( 'disabled' ) ;
}
}
2023-03-01 10:04:37 +01:00
_onRecipient ( peerId , deviceName ) {
2023-01-23 04:51:22 +01:00
this . correspondingPeerId = peerId ;
2023-03-01 10:04:37 +01:00
this . $peerDisplayName . innerText = deviceName ;
2018-09-21 16:05:03 +02:00
this . show ( ) ;
2020-12-22 21:23:10 +01:00
const range = document . createRange ( ) ;
const sel = window . getSelection ( ) ;
2023-01-14 01:43:44 +01:00
this . $text . focus ( ) ;
2023-01-17 10:47:44 +01:00
range . selectNodeContents ( this . $text ) ;
sel . removeAllRanges ( ) ;
sel . addRange ( range ) ;
2018-09-21 16:05:03 +02:00
}
2023-03-02 16:30:47 +01:00
_onSubmit ( e ) {
e . preventDefault ( ) ;
this . _send ( ) ;
}
2023-01-14 01:43:44 +01:00
_send ( ) {
2018-09-21 16:05:03 +02:00
Events . fire ( 'send-text' , {
2023-01-23 04:51:22 +01:00
to : this . correspondingPeerId ,
2023-01-17 10:47:44 +01:00
text : this . $text . innerText
2018-09-21 16:05:03 +02:00
} ) ;
2023-01-14 01:43:44 +01:00
this . $text . value = "" ;
2023-02-10 03:26:08 +01:00
this . hide ( ) ;
2018-09-21 16:05:03 +02:00
}
}
class ReceiveTextDialog extends Dialog {
constructor ( ) {
2023-03-01 10:44:57 +01:00
super ( 'receive-text-dialog' ) ;
2023-02-10 03:26:08 +01:00
Events . on ( 'text-received' , e => this . _onText ( e . detail . text , e . detail . peerId ) ) ;
2018-09-21 16:05:03 +02:00
this . $text = this . $el . querySelector ( '#text' ) ;
2023-02-10 03:26:08 +01:00
this . $copy = this . $el . querySelector ( '#copy' ) ;
this . $close = this . $el . querySelector ( '#close' ) ;
this . $copy . addEventListener ( 'click' , _ => this . _onCopy ( ) ) ;
this . $close . addEventListener ( 'click' , _ => this . hide ( ) ) ;
Events . on ( "keydown" , e => this . _onKeyDown ( e ) ) ;
2023-03-03 12:01:43 +01:00
this . $displayNameNode = this . $el . querySelector ( '.display-name' ) ;
2023-02-10 03:26:08 +01:00
this . _receiveTextQueue = [ ] ;
2022-12-30 20:34:54 +01:00
}
async _onKeyDown ( e ) {
2023-01-25 10:01:45 +01:00
if ( this . $el . attributes [ "show" ] ) {
if ( e . code === "KeyC" && ( e . ctrlKey || e . metaKey ) ) {
await this . _onCopy ( )
this . hide ( ) ;
} else if ( e . code === "Escape" ) {
this . hide ( ) ;
}
2022-12-30 20:34:54 +01:00
}
2018-09-21 16:05:03 +02:00
}
2023-02-10 03:26:08 +01:00
_onText ( text , peerId ) {
window . blop . play ( ) ;
this . _receiveTextQueue . push ( { text : text , peerId : peerId } ) ;
2023-03-02 15:30:25 +01:00
this . _setDocumentTitleMessages ( ) ;
2023-02-10 03:26:08 +01:00
if ( this . $el . attributes [ "show" ] ) return ;
this . _dequeueRequests ( ) ;
}
_dequeueRequests ( ) {
if ( ! this . _receiveTextQueue . length ) return ;
let { text , peerId } = this . _receiveTextQueue . shift ( ) ;
this . _showReceiveTextDialog ( text , peerId ) ;
}
_showReceiveTextDialog ( text , peerId ) {
2023-03-03 12:01:43 +01:00
this . $displayNameNode . innerText = $ ( peerId ) . ui . _displayName ( ) ;
2023-02-10 03:26:08 +01:00
2023-03-03 12:28:50 +01:00
this . $text . innerText = text ;
this . $text . classList . remove ( 'text-center' ) ;
// Beautify text if text is short
if ( text . length < 2000 ) {
// replace urls with actual links
this . $text . innerHTML = this . $text . innerHTML . replace ( /((https?:\/\/|www)[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\-._~:\/?#\[\]@!$&'()*+,;=]+)/g , url => {
return ` <a href=" ${ url } " target="_blank"> ${ url } </a> ` ;
} ) ;
if ( ! /\s/ . test ( text ) ) {
this . $text . classList . add ( 'text-center' ) ;
}
2018-09-21 16:05:03 +02:00
}
2023-03-03 12:28:50 +01:00
2023-03-02 15:30:25 +01:00
this . _setDocumentTitleMessages ( ) ;
2023-01-20 15:56:20 +01:00
document . changeFavicon ( "images/favicon-96x96-notification.png" ) ;
2018-09-21 16:05:03 +02:00
this . show ( ) ;
}
2023-03-02 15:30:25 +01:00
_setDocumentTitleMessages ( ) {
document . title = ! this . _receiveTextQueue . length
? 'Message Received - PairDrop'
2023-03-03 17:03:10 +01:00
: ` ${ this . _receiveTextQueue . length + 1 } Messages Received - PairDrop ` ;
2023-03-02 15:30:25 +01:00
}
2020-12-20 04:41:16 +01:00
async _onCopy ( ) {
await navigator . clipboard . writeText ( this . $text . textContent ) ;
2018-09-21 16:05:03 +02:00
Events . fire ( 'notify-user' , 'Copied to clipboard' ) ;
2023-02-10 03:26:08 +01:00
this . hide ( ) ;
}
hide ( ) {
super . hide ( ) ;
setTimeout ( _ => this . _dequeueRequests ( ) , 500 ) ;
2018-09-21 16:05:03 +02:00
}
}
2023-01-19 04:40:28 +01:00
class Base64ZipDialog extends Dialog {
constructor ( ) {
2023-03-01 10:44:57 +01:00
super ( 'base64-paste-dialog' ) ;
2023-01-19 04:40:28 +01:00
const urlParams = new URL ( window . location ) . searchParams ;
2023-01-22 16:12:00 +01:00
const base64Text = urlParams . get ( 'base64text' ) ;
2023-02-20 17:42:02 +01:00
const base64Zip = urlParams . get ( 'base64zip' ) ;
const base64Hash = window . location . hash . substring ( 1 ) ;
2023-03-01 10:44:57 +01:00
this . $pasteBtn = this . $el . querySelector ( '#base64-paste-btn' ) ;
2023-03-06 02:18:07 +01:00
this . $fallbackTextarea = this . $el . querySelector ( '.textarea' ) ;
2023-01-25 09:43:32 +01:00
2023-01-22 16:12:00 +01:00
if ( base64Text ) {
2023-02-20 17:42:02 +01:00
this . show ( ) ;
if ( base64Text === "paste" ) {
// ?base64text=paste
// base64 encoded string is ready to be pasted from clipboard
2023-03-06 02:18:07 +01:00
this . preparePasting ( "text" ) ;
2023-02-20 17:42:02 +01:00
} else if ( base64Text === "hash" ) {
// ?base64text=hash#BASE64ENCODED
// base64 encoded string is url hash which is never sent to server and faster (recommended)
this . processBase64Text ( base64Hash )
. catch ( _ => {
Events . fire ( 'notify-user' , 'Text content is incorrect.' ) ;
2023-03-06 02:18:07 +01:00
console . log ( "Text content incorrect." ) ;
2023-02-20 17:42:02 +01:00
} ) . finally ( _ => {
this . hide ( ) ;
} ) ;
} else {
// ?base64text=BASE64ENCODED
// base64 encoded string was part of url param (not recommended)
this . processBase64Text ( base64Text )
. catch ( _ => {
Events . fire ( 'notify-user' , 'Text content is incorrect.' ) ;
2023-03-06 02:18:07 +01:00
console . log ( "Text content incorrect." ) ;
2023-02-20 17:42:02 +01:00
} ) . finally ( _ => {
this . hide ( ) ;
} ) ;
2023-01-25 09:43:32 +01:00
}
2023-02-20 17:42:02 +01:00
} else if ( base64Zip ) {
2023-01-19 14:48:43 +01:00
this . show ( ) ;
2023-02-20 17:42:02 +01:00
if ( base64Zip === "hash" ) {
// ?base64zip=hash#BASE64ENCODED
// base64 encoded zip file is url hash which is never sent to the server
this . processBase64Zip ( base64Hash )
. catch ( _ => {
Events . fire ( 'notify-user' , 'File content is incorrect.' ) ;
2023-03-06 02:18:07 +01:00
console . log ( "File content incorrect." ) ;
2023-02-20 17:42:02 +01:00
} ) . finally ( _ => {
this . hide ( ) ;
} ) ;
} else {
// ?base64zip=paste || ?base64zip=true
2023-03-06 02:18:07 +01:00
this . preparePasting ( 'files' ) ;
2023-02-20 17:42:02 +01:00
}
}
}
_setPasteBtnToProcessing ( ) {
2023-03-03 12:01:43 +01:00
this . $pasteBtn . style . pointerEvents = "none" ;
2023-02-20 17:42:02 +01:00
this . $pasteBtn . innerText = "Processing..." ;
}
2023-03-06 02:18:07 +01:00
preparePasting ( type ) {
if ( navigator . clipboard . readText ) {
this . $pasteBtn . innerText = ` Tap here to paste ${ type } ` ;
2023-03-06 12:20:30 +01:00
this . _clickCallback = _ => this . processClipboard ( type ) ;
this . $pasteBtn . addEventListener ( 'click' , _ => this . _clickCallback ( ) ) ;
2023-03-06 02:18:07 +01:00
} else {
console . log ( "`navigator.clipboard.readText()` is not available on your browser.\nOn Firefox you can set `dom.events.asyncClipboard.readText` to true under `about:config` for convenience." )
this . $pasteBtn . setAttribute ( 'hidden' , '' ) ;
this . $fallbackTextarea . setAttribute ( 'placeholder' , ` Paste here to send ${ type } ` ) ;
this . $fallbackTextarea . removeAttribute ( 'hidden' ) ;
2023-03-06 12:20:30 +01:00
this . _inputCallback = _ => this . processInput ( type ) ;
this . $fallbackTextarea . addEventListener ( 'input' , _ => this . _inputCallback ( ) ) ;
2023-03-06 02:18:07 +01:00
this . $fallbackTextarea . focus ( ) ;
2023-02-20 17:42:02 +01:00
}
2023-03-06 02:18:07 +01:00
}
2023-02-20 17:42:02 +01:00
2023-03-06 02:18:07 +01:00
async processInput ( type ) {
const base64 = this . $fallbackTextarea . textContent ;
this . $fallbackTextarea . textContent = '' ;
2023-03-06 12:20:30 +01:00
await this . processBase64 ( type , base64 ) ;
2023-03-06 02:18:07 +01:00
}
2023-02-20 17:42:02 +01:00
2023-03-06 02:18:07 +01:00
async processClipboard ( type ) {
2023-02-20 17:42:02 +01:00
const base64 = await navigator . clipboard . readText ( ) ;
2023-03-06 02:18:07 +01:00
await this . processBase64 ( type , base64 ) ;
}
2023-02-20 17:42:02 +01:00
2023-03-06 12:20:30 +01:00
isValidBase64 ( base64 ) {
try {
// check if input is base64 encoded
window . atob ( base64 ) ;
return true ;
} catch ( e ) {
// input is not base64 string.
return false ;
}
}
2023-03-06 02:18:07 +01:00
async processBase64 ( type , base64 ) {
2023-03-06 12:20:30 +01:00
if ( ! base64 || ! this . isValidBase64 ( base64 ) ) return ;
this . _setPasteBtnToProcessing ( ) ;
2023-03-06 02:18:07 +01:00
try {
if ( type === "text" ) {
await this . processBase64Text ( base64 ) ;
} else {
await this . processBase64Zip ( base64 ) ;
}
} catch ( _ ) {
Events . fire ( 'notify-user' , 'Clipboard content is incorrect.' ) ;
console . log ( "Clipboard content is incorrect." )
2023-01-19 14:48:43 +01:00
}
2023-03-06 02:18:07 +01:00
this . hide ( ) ;
2023-01-19 04:40:28 +01:00
}
2023-01-22 16:12:00 +01:00
processBase64Text ( base64Text ) {
2023-02-20 17:42:02 +01:00
return new Promise ( ( resolve ) => {
this . _setPasteBtnToProcessing ( ) ;
2023-01-25 09:43:32 +01:00
let decodedText = decodeURIComponent ( escape ( window . atob ( base64Text ) ) ) ;
Events . fire ( 'activate-paste-mode' , { files : [ ] , text : decodedText } ) ;
2023-02-20 17:42:02 +01:00
resolve ( ) ;
} ) ;
2023-01-22 16:12:00 +01:00
}
2023-02-20 17:42:02 +01:00
async processBase64Zip ( base64zip ) {
this . _setPasteBtnToProcessing ( ) ;
let bstr = atob ( base64zip ) , n = bstr . length , u8arr = new Uint8Array ( n ) ;
while ( n -- ) {
u8arr [ n ] = bstr . charCodeAt ( n ) ;
}
2023-01-19 04:40:28 +01:00
2023-02-20 17:42:02 +01:00
const zipBlob = new File ( [ u8arr ] , 'archive.zip' ) ;
2023-01-19 04:40:28 +01:00
2023-02-20 17:42:02 +01:00
let files = [ ] ;
const zipEntries = await zipper . getEntries ( zipBlob ) ;
for ( let i = 0 ; i < zipEntries . length ; i ++ ) {
let fileBlob = await zipper . getData ( zipEntries [ i ] ) ;
files . push ( new File ( [ fileBlob ] , zipEntries [ i ] . filename ) ) ;
2023-01-19 04:40:28 +01:00
}
2023-02-20 17:42:02 +01:00
Events . fire ( 'activate-paste-mode' , { files : files , text : "" } ) ;
2023-01-19 04:40:28 +01:00
}
2023-01-25 09:43:32 +01:00
clearBrowserHistory ( ) {
window . history . replaceState ( { } , "Rewrite URL" , '/' ) ;
}
2023-02-20 17:42:02 +01:00
hide ( ) {
this . clearBrowserHistory ( ) ;
2023-03-06 12:20:30 +01:00
this . $pasteBtn . removeEventListener ( 'click' , _ => this . _clickCallback ( ) ) ;
this . $fallbackTextarea . removeEventListener ( 'input' , _ => this . _inputCallback ( ) ) ;
2023-02-20 17:42:02 +01:00
super . hide ( ) ;
}
2023-01-19 04:40:28 +01:00
}
2018-09-21 16:05:03 +02:00
class Toast extends Dialog {
constructor ( ) {
super ( 'toast' ) ;
2023-01-23 04:51:22 +01:00
Events . on ( 'notify-user' , e => this . _onNotify ( e . detail ) ) ;
2018-09-21 16:05:03 +02:00
}
2023-01-23 04:51:22 +01:00
_onNotify ( message ) {
if ( this . hideTimeout ) clearTimeout ( this . hideTimeout ) ;
2018-09-21 16:05:03 +02:00
this . $el . textContent = message ;
this . show ( ) ;
2023-01-27 01:27:22 +01:00
this . hideTimeout = setTimeout ( _ => this . hide ( ) , 5000 ) ;
2018-09-21 16:05:03 +02:00
}
}
class Notifications {
constructor ( ) {
// Check if the browser supports notifications
if ( ! ( 'Notification' in window ) ) return ;
2018-09-21 22:31:46 +02:00
2018-09-21 16:05:03 +02:00
// Check whether notification permissions have already been granted
if ( Notification . permission !== 'granted' ) {
this . $button = $ ( 'notification' ) ;
this . $button . removeAttribute ( 'hidden' ) ;
2023-01-10 05:07:57 +01:00
this . $button . addEventListener ( 'click' , _ => this . _requestPermission ( ) ) ;
2018-09-21 16:05:03 +02:00
}
2023-03-01 10:04:37 +01:00
// Todo: fix Notifications
2023-02-10 03:26:08 +01:00
Events . on ( 'text-received' , e => this . _messageNotification ( e . detail . text , e . detail . peerId ) ) ;
2023-01-21 18:21:58 +01:00
Events . on ( 'files-received' , e => this . _downloadNotification ( e . detail . files ) ) ;
2018-09-21 16:05:03 +02:00
}
_requestPermission ( ) {
Notification . requestPermission ( permission => {
if ( permission !== 'granted' ) {
Events . fire ( 'notify-user' , Notifications . PERMISSION _ERROR || 'Error' ) ;
return ;
}
2023-02-10 03:26:08 +01:00
Events . fire ( 'notify-user' , 'Notifications enabled.' ) ;
2018-09-21 16:05:03 +02:00
this . $button . setAttribute ( 'hidden' , 1 ) ;
} ) ;
}
2023-02-10 03:26:08 +01:00
_notify ( title , body ) {
2018-09-21 18:54:52 +02:00
const config = {
2018-09-21 16:05:03 +02:00
body : body ,
2018-09-21 18:54:52 +02:00
icon : '/images/logo_transparent_128x128.png' ,
}
2019-03-13 00:08:37 +01:00
let notification ;
2018-09-21 23:19:54 +02:00
try {
2023-02-10 03:26:08 +01:00
notification = new Notification ( title , config ) ;
2018-09-21 23:19:54 +02:00
} catch ( e ) {
2019-03-13 00:08:37 +01:00
// Android doesn't support "new Notification" if service worker is installed
2018-09-21 23:19:54 +02:00
if ( ! serviceWorker || ! serviceWorker . showNotification ) return ;
2023-02-10 03:26:08 +01:00
notification = serviceWorker . showNotification ( title , config ) ;
2018-09-21 18:54:52 +02:00
}
2018-09-21 23:19:54 +02:00
2019-03-13 00:08:37 +01:00
// Notification is persistent on Android. We have to close it manually
2022-12-22 22:41:26 +01:00
const visibilitychangeHandler = ( ) => {
if ( document . visibilityState === 'visible' ) {
2022-09-13 07:35:05 +02:00
notification . close ( ) ;
2022-09-13 14:34:44 +02:00
Events . off ( 'visibilitychange' , visibilitychangeHandler ) ;
2022-12-22 22:41:26 +01:00
}
} ;
2022-09-13 14:34:44 +02:00
Events . on ( 'visibilitychange' , visibilitychangeHandler ) ;
2019-03-13 00:08:37 +01:00
return notification ;
2018-09-21 16:05:03 +02:00
}
2023-02-10 03:26:08 +01:00
_messageNotification ( message , peerId ) {
2022-09-13 07:35:05 +02:00
if ( document . visibilityState !== 'visible' ) {
2023-02-10 03:26:08 +01:00
const peerDisplayName = $ ( peerId ) . ui . _displayName ( ) ;
2023-03-03 12:28:50 +01:00
if ( /^((https?:\/\/|www)[abcdefghijklmnopqrstuvwxyz0123456789\-._~:\/?#\[\]@!$&'()*+,;=]+)$/ . test ( message . toLowerCase ( ) ) ) {
2023-02-10 03:26:08 +01:00
const notification = this . _notify ( ` Link received by ${ peerDisplayName } - Click to open ` , message ) ;
2023-01-10 05:07:57 +01:00
this . _bind ( notification , _ => window . open ( message , '_blank' , null , true ) ) ;
2022-09-13 07:35:05 +02:00
} else {
2023-02-10 03:26:08 +01:00
const notification = this . _notify ( ` Message received by ${ peerDisplayName } - Click to copy ` , message ) ;
2023-01-10 05:07:57 +01:00
this . _bind ( notification , _ => this . _copyText ( message , notification ) ) ;
2022-09-13 07:35:05 +02:00
}
2018-09-21 16:05:03 +02:00
}
}
2023-01-21 18:21:58 +01:00
_downloadNotification ( files ) {
2022-09-13 07:35:05 +02:00
if ( document . visibilityState !== 'visible' ) {
2023-01-21 18:21:58 +01:00
let imagesOnly = true ;
for ( let i = 0 ; i < files . length ; i ++ ) {
2023-01-23 20:09:35 +01:00
if ( files [ i ] . type . split ( '/' ) [ 0 ] !== 'image' ) {
2023-01-21 18:21:58 +01:00
imagesOnly = false ;
break ;
}
}
let title = files [ 0 ] . name ;
if ( files . length >= 2 ) {
title += ` and ${ files . length - 1 } other ` ;
title += imagesOnly ? 'image' : 'file' ;
if ( files . length > 2 ) title += "s" ;
}
const notification = this . _notify ( title , 'Click to download' ) ;
2023-01-10 05:07:57 +01:00
this . _bind ( notification , _ => this . _download ( notification ) ) ;
2022-09-13 07:35:05 +02:00
}
2018-09-21 22:31:46 +02:00
}
2018-09-21 23:19:54 +02:00
_download ( notification ) {
2023-03-01 10:44:57 +01:00
$ ( 'share-or-download' ) . click ( ) ;
2018-09-21 23:19:54 +02:00
notification . close ( ) ;
2018-09-21 16:05:03 +02:00
}
2018-09-21 23:19:54 +02:00
_copyText ( message , notification ) {
2023-01-10 05:07:57 +01:00
if ( navigator . clipboard . writeText ( message ) ) {
notification . close ( ) ;
this . _notify ( 'Copied text to clipboard' ) ;
} else {
this . _notify ( 'Writing to clipboard failed. Copy manually!' ) ;
}
2018-09-21 23:19:54 +02:00
}
_bind ( notification , handler ) {
if ( notification . then ) {
2023-01-23 20:09:35 +01:00
notification . then ( _ => serviceWorker . getNotifications ( ) . then ( _ => {
2018-09-21 23:19:54 +02:00
serviceWorker . addEventListener ( 'notificationclick' , handler ) ;
} ) ) ;
} else {
notification . onclick = handler ;
}
2018-09-21 22:31:46 +02:00
}
2018-09-21 16:05:03 +02:00
}
2019-03-13 01:21:44 +01:00
class NetworkStatusUI {
2019-03-12 23:37:50 +01:00
constructor ( ) {
2023-01-07 01:45:52 +01:00
Events . on ( 'offline' , _ => this . _showOfflineMessage ( ) ) ;
Events . on ( 'online' , _ => this . _showOnlineMessage ( ) ) ;
2023-03-03 17:03:10 +01:00
Events . on ( 'ws-connected' , _ => this . _onWsConnected ( ) ) ;
2023-01-23 20:40:08 +01:00
Events . on ( 'ws-disconnected' , _ => this . _onWsDisconnected ( ) ) ;
2019-03-13 01:21:44 +01:00
if ( ! navigator . onLine ) this . _showOfflineMessage ( ) ;
2019-03-12 23:37:50 +01:00
}
2019-03-13 01:21:44 +01:00
_showOfflineMessage ( ) {
Events . fire ( 'notify-user' , 'You are offline' ) ;
2022-12-31 18:03:37 +01:00
window . animateBackground ( false ) ;
2019-03-12 23:37:50 +01:00
}
2019-03-13 01:21:44 +01:00
_showOnlineMessage ( ) {
Events . fire ( 'notify-user' , 'You are back online' ) ;
2023-03-03 17:03:10 +01:00
window . animateBackground ( true ) ;
}
_onWsConnected ( ) {
window . animateBackground ( true ) ;
2023-01-23 20:40:08 +01:00
}
_onWsDisconnected ( ) {
window . animateBackground ( false ) ;
2019-03-12 23:37:50 +01:00
}
}
2019-03-13 01:48:53 +01:00
class WebShareTargetUI {
constructor ( ) {
2023-01-18 21:01:29 +01:00
const urlParams = new URL ( window . location ) . searchParams ;
const share _target _type = urlParams . get ( "share-target" )
if ( share _target _type ) {
if ( share _target _type === "text" ) {
const title = urlParams . get ( 'title' ) || '' ;
const text = urlParams . get ( 'text' ) || '' ;
const url = urlParams . get ( 'url' ) || '' ;
let shareTargetText ;
if ( url ) {
shareTargetText = url ; // We share only the Link - no text. Because link-only text becomes clickable.
} else if ( title && text ) {
shareTargetText = title + '\r\n' + text ;
} else {
shareTargetText = title + text ;
}
console . log ( 'Shared Target Text:' , '"' + shareTargetText + '"' ) ;
Events . fire ( 'activate-paste-mode' , { files : [ ] , text : shareTargetText } )
} else if ( share _target _type === "files" ) {
2023-01-19 01:28:35 +01:00
const openRequest = window . indexedDB . open ( 'pairdrop_store' )
openRequest . onsuccess ( db => {
const tx = db . transaction ( 'share_target_files' , 'readwrite' ) ;
const store = tx . objectStore ( 'share_target_files' ) ;
const request = store . getAll ( ) ;
request . onsuccess = _ => {
Events . fire ( 'activate-paste-mode' , { files : request . result , text : "" } )
const clearRequest = store . clear ( )
clearRequest . onsuccess = _ => db . close ( ) ;
}
} )
2023-01-18 21:01:29 +01:00
}
2023-01-19 04:40:28 +01:00
window . history . replaceState ( { } , "Rewrite URL" , '/' ) ;
2023-01-18 21:01:29 +01:00
}
2019-03-13 01:48:53 +01:00
}
}
2019-03-12 23:37:50 +01:00
2023-01-18 15:45:53 +01:00
class WebFileHandlersUI {
constructor ( ) {
2023-02-02 15:19:28 +01:00
const urlParams = new URL ( window . location ) . searchParams ;
if ( urlParams . has ( "file_handler" ) && "launchQueue" in window ) {
2023-01-18 15:45:53 +01:00
launchQueue . setConsumer ( async launchParams => {
console . log ( "Launched with: " , launchParams ) ;
if ( ! launchParams . files . length )
return ;
let files = [ ] ;
for ( let i = 0 ; i < launchParams . files . length ; i ++ ) {
if ( i !== 0 && await launchParams . files [ i ] . isSameEntry ( launchParams . files [ i - 1 ] ) ) continue ;
const fileHandle = launchParams . files [ i ] ;
const file = await fileHandle . getFile ( ) ;
files . push ( file ) ;
}
Events . fire ( 'activate-paste-mode' , { files : files , text : "" } )
launchParams = null ;
} ) ;
2023-02-02 15:19:28 +01:00
window . history . replaceState ( { } , "Rewrite URL" , '/' ) ;
2023-01-18 15:45:53 +01:00
}
}
}
2023-01-17 14:19:51 +01:00
class NoSleepUI {
constructor ( ) {
NoSleepUI . _nosleep = new NoSleep ( ) ;
}
static enable ( ) {
if ( ! this . _interval ) {
NoSleepUI . _nosleep . enable ( ) ;
NoSleepUI . _interval = setInterval ( _ => NoSleepUI . disable ( ) , 10000 ) ;
}
}
static disable ( ) {
if ( $$ ( 'x-peer[status]' ) === null ) {
clearInterval ( NoSleepUI . _interval ) ;
NoSleepUI . _nosleep . disable ( ) ;
}
}
}
2023-01-10 05:07:57 +01:00
class PersistentStorage {
constructor ( ) {
if ( ! ( 'indexedDB' in window ) ) {
2023-01-22 17:34:33 +01:00
PersistentStorage . logBrowserNotCapable ( ) ;
2023-01-10 05:07:57 +01:00
return ;
}
2023-01-19 01:28:35 +01:00
const DBOpenRequest = window . indexedDB . open ( 'pairdrop_store' , 2 ) ;
2023-01-10 05:07:57 +01:00
DBOpenRequest . onerror = ( e ) => {
2023-01-22 17:34:33 +01:00
PersistentStorage . logBrowserNotCapable ( ) ;
2023-01-10 05:07:57 +01:00
console . log ( 'Error initializing database: ' ) ;
2023-01-22 17:34:33 +01:00
console . log ( e )
2023-01-10 05:07:57 +01:00
} ;
DBOpenRequest . onsuccess = ( ) => {
console . log ( 'Database initialised.' ) ;
} ;
DBOpenRequest . onupgradeneeded = ( e ) => {
const db = e . target . result ;
db . onerror = e => console . log ( 'Error loading database: ' + e ) ;
2023-01-19 01:28:35 +01:00
try {
db . createObjectStore ( 'keyval' ) ;
} catch ( error ) {
console . log ( "Object store named 'keyval' already exists" )
}
try {
const roomSecretsObjectStore = db . createObjectStore ( 'room_secrets' , { autoIncrement : true } ) ;
roomSecretsObjectStore . createIndex ( 'secret' , 'secret' , { unique : true } ) ;
} catch ( error ) {
console . log ( "Object store named 'room_secrets' already exists" )
}
try {
db . createObjectStore ( 'share_target_files' ) ;
} catch ( error ) {
console . log ( "Object store named 'share_target_files' already exists" )
}
2023-01-10 05:07:57 +01:00
}
}
2023-01-22 17:34:33 +01:00
static logBrowserNotCapable ( ) {
2023-01-18 22:42:47 +01:00
console . log ( "This browser does not support IndexedDB. Paired devices will be gone after the browser is closed." ) ;
2023-01-10 05:07:57 +01:00
}
static set ( key , value ) {
return new Promise ( ( resolve , reject ) => {
2023-01-10 17:22:36 +01:00
const DBOpenRequest = window . indexedDB . open ( 'pairdrop_store' ) ;
2023-01-10 05:07:57 +01:00
DBOpenRequest . onsuccess = ( e ) => {
const db = e . target . result ;
const transaction = db . transaction ( 'keyval' , 'readwrite' ) ;
const objectStore = transaction . objectStore ( 'keyval' ) ;
const objectStoreRequest = objectStore . put ( value , key ) ;
objectStoreRequest . onsuccess = _ => {
console . log ( ` Request successful. Added key-pair: ${ key } - ${ value } ` ) ;
2023-01-23 00:03:26 +01:00
resolve ( value ) ;
2023-01-10 05:07:57 +01:00
} ;
}
DBOpenRequest . onerror = ( e ) => {
reject ( e ) ;
}
} )
}
static get ( key ) {
return new Promise ( ( resolve , reject ) => {
2023-01-10 17:22:36 +01:00
const DBOpenRequest = window . indexedDB . open ( 'pairdrop_store' ) ;
2023-01-10 05:07:57 +01:00
DBOpenRequest . onsuccess = ( e ) => {
const db = e . target . result ;
const transaction = db . transaction ( 'keyval' , 'readwrite' ) ;
const objectStore = transaction . objectStore ( 'keyval' ) ;
const objectStoreRequest = objectStore . get ( key ) ;
objectStoreRequest . onsuccess = _ => {
console . log ( ` Request successful. Retrieved key-pair: ${ key } - ${ objectStoreRequest . result } ` ) ;
resolve ( objectStoreRequest . result ) ;
}
}
DBOpenRequest . onerror = ( e ) => {
reject ( e ) ;
}
} ) ;
}
2023-01-19 01:28:35 +01:00
2023-01-10 05:07:57 +01:00
static delete ( key ) {
return new Promise ( ( resolve , reject ) => {
2023-01-10 17:22:36 +01:00
const DBOpenRequest = window . indexedDB . open ( 'pairdrop_store' ) ;
2023-01-10 05:07:57 +01:00
DBOpenRequest . onsuccess = ( e ) => {
const db = e . target . result ;
const transaction = db . transaction ( 'keyval' , 'readwrite' ) ;
const objectStore = transaction . objectStore ( 'keyval' ) ;
const objectStoreRequest = objectStore . delete ( key ) ;
objectStoreRequest . onsuccess = _ => {
console . log ( ` Request successful. Deleted key: ${ key } ` ) ;
resolve ( ) ;
} ;
}
DBOpenRequest . onerror = ( e ) => {
reject ( e ) ;
}
} )
}
static addRoomSecret ( roomSecret ) {
return new Promise ( ( resolve , reject ) => {
2023-01-10 17:22:36 +01:00
const DBOpenRequest = window . indexedDB . open ( 'pairdrop_store' ) ;
2023-01-10 05:07:57 +01:00
DBOpenRequest . onsuccess = ( e ) => {
const db = e . target . result ;
const transaction = db . transaction ( 'room_secrets' , 'readwrite' ) ;
const objectStore = transaction . objectStore ( 'room_secrets' ) ;
const objectStoreRequest = objectStore . add ( { 'secret' : roomSecret } ) ;
objectStoreRequest . onsuccess = _ => {
console . log ( ` Request successful. RoomSecret added: ${ roomSecret } ` ) ;
resolve ( ) ;
}
}
DBOpenRequest . onerror = ( e ) => {
reject ( e ) ;
}
} )
}
static getAllRoomSecrets ( ) {
return new Promise ( ( resolve , reject ) => {
2023-01-10 17:22:36 +01:00
const DBOpenRequest = window . indexedDB . open ( 'pairdrop_store' ) ;
2023-01-10 05:07:57 +01:00
DBOpenRequest . onsuccess = ( e ) => {
const db = e . target . result ;
const transaction = db . transaction ( 'room_secrets' , 'readwrite' ) ;
const objectStore = transaction . objectStore ( 'room_secrets' ) ;
const objectStoreRequest = objectStore . getAll ( ) ;
objectStoreRequest . onsuccess = e => {
let secrets = [ ] ;
for ( let i = 0 ; i < e . target . result . length ; i ++ ) {
secrets . push ( e . target . result [ i ] . secret ) ;
}
console . log ( ` Request successful. Retrieved ${ secrets . length } room_secrets ` ) ;
resolve ( secrets ) ;
}
}
DBOpenRequest . onerror = ( e ) => {
reject ( e ) ;
}
} ) ;
}
static deleteRoomSecret ( room _secret ) {
return new Promise ( ( resolve , reject ) => {
2023-01-10 17:22:36 +01:00
const DBOpenRequest = window . indexedDB . open ( 'pairdrop_store' ) ;
2023-01-10 05:07:57 +01:00
DBOpenRequest . onsuccess = ( e ) => {
const db = e . target . result ;
const transaction = db . transaction ( 'room_secrets' , 'readwrite' ) ;
const objectStore = transaction . objectStore ( 'room_secrets' ) ;
const objectStoreRequestKey = objectStore . index ( "secret" ) . getKey ( room _secret ) ;
objectStoreRequestKey . onsuccess = e => {
if ( ! e . target . result ) {
console . log ( ` Nothing to delete. room_secret not existing: ${ room _secret } ` ) ;
resolve ( ) ;
return ;
}
const objectStoreRequestDeletion = objectStore . delete ( e . target . result ) ;
objectStoreRequestDeletion . onsuccess = _ => {
console . log ( ` Request successful. Deleted room_secret: ${ room _secret } ` ) ;
resolve ( ) ;
}
objectStoreRequestDeletion . onerror = ( e ) => {
reject ( e ) ;
}
} ;
}
DBOpenRequest . onerror = ( e ) => {
reject ( e ) ;
}
} )
}
static clearRoomSecrets ( ) {
return new Promise ( ( resolve , reject ) => {
2023-01-10 17:22:36 +01:00
const DBOpenRequest = window . indexedDB . open ( 'pairdrop_store' ) ;
2023-01-10 05:07:57 +01:00
DBOpenRequest . onsuccess = ( e ) => {
const db = e . target . result ;
const transaction = db . transaction ( 'room_secrets' , 'readwrite' ) ;
const objectStore = transaction . objectStore ( 'room_secrets' ) ;
const objectStoreRequest = objectStore . clear ( ) ;
objectStoreRequest . onsuccess = _ => {
console . log ( 'Request successful. All room_secrets cleared' ) ;
resolve ( ) ;
} ;
}
DBOpenRequest . onerror = ( e ) => {
reject ( e ) ;
}
} )
}
}
2019-03-13 01:21:44 +01:00
2023-03-01 21:35:00 +01:00
class Broadcast {
constructor ( ) {
this . bc = new BroadcastChannel ( 'pairdrop' ) ;
this . bc . addEventListener ( 'message' , e => this . _onMessage ( e ) ) ;
Events . on ( 'broadcast-send' , e => this . _broadcastMessage ( e . detail ) ) ;
}
_broadcastMessage ( message ) {
this . bc . postMessage ( message ) ;
}
_onMessage ( e ) {
console . log ( 'Broadcast message received:' , e . data )
Events . fire ( e . data . type , e . data . detail ) ;
}
}
2023-01-17 10:50:28 +01:00
class PairDrop {
2018-09-21 16:05:03 +02:00
constructor ( ) {
2023-01-10 05:07:57 +01:00
Events . on ( 'load' , _ => {
const server = new ServerConnection ( ) ;
const peers = new PeersManager ( server ) ;
const peersUI = new PeersUI ( ) ;
2023-01-17 10:41:50 +01:00
const receiveFileDialog = new ReceiveFileDialog ( ) ;
const receiveRequestDialog = new ReceiveRequestDialog ( ) ;
2018-09-21 16:05:03 +02:00
const sendTextDialog = new SendTextDialog ( ) ;
const receiveTextDialog = new ReceiveTextDialog ( ) ;
2023-01-10 05:07:57 +01:00
const pairDeviceDialog = new PairDeviceDialog ( ) ;
const clearDevicesDialog = new ClearDevicesDialog ( ) ;
2023-01-19 04:40:28 +01:00
const base64ZipDialog = new Base64ZipDialog ( ) ;
2018-09-21 16:05:03 +02:00
const toast = new Toast ( ) ;
const notifications = new Notifications ( ) ;
2019-03-12 23:37:50 +01:00
const networkStatusUI = new NetworkStatusUI ( ) ;
2019-03-13 01:21:44 +01:00
const webShareTargetUI = new WebShareTargetUI ( ) ;
2023-01-18 15:45:53 +01:00
const webFileHandlersUI = new WebFileHandlersUI ( ) ;
2023-01-17 14:19:51 +01:00
const noSleepUI = new NoSleepUI ( ) ;
2023-03-01 21:35:00 +01:00
const broadCast = new Broadcast ( ) ;
2019-08-28 17:14:51 +02:00
} ) ;
2018-09-21 16:05:03 +02:00
}
}
2023-01-10 05:07:57 +01:00
const persistentStorage = new PersistentStorage ( ) ;
2023-01-17 10:50:28 +01:00
const pairDrop = new PairDrop ( ) ;
2018-09-21 16:05:03 +02:00
2018-10-24 17:43:50 +02:00
if ( 'serviceWorker' in navigator ) {
2019-03-14 21:37:44 +01:00
navigator . serviceWorker . register ( '/service-worker.js' )
2018-09-21 18:54:52 +02:00
. then ( serviceWorker => {
console . log ( 'Service Worker registered' ) ;
window . serviceWorker = serviceWorker
} ) ;
2018-09-21 16:05:03 +02:00
}
2019-03-14 21:37:44 +01:00
window . addEventListener ( 'beforeinstallprompt' , e => {
2023-01-23 00:03:26 +01:00
if ( ! window . matchMedia ( '(display-mode: minimal-ui)' ) . matches ) {
// only display install btn when installed
2019-03-14 21:37:44 +01:00
const btn = document . querySelector ( '#install' )
btn . hidden = false ;
btn . onclick = _ => e . prompt ( ) ;
}
2023-01-23 00:03:26 +01:00
return e . preventDefault ( ) ;
2019-03-14 21:37:44 +01:00
} ) ;
2018-09-21 16:05:03 +02:00
// Background Animation
Events . on ( 'load' , ( ) => {
2020-12-20 22:08:16 +01:00
let c = document . createElement ( 'canvas' ) ;
2018-09-21 16:05:03 +02:00
document . body . appendChild ( c ) ;
2020-12-20 22:08:16 +01:00
let style = c . style ;
2018-09-21 16:05:03 +02:00
style . width = '100%' ;
style . position = 'absolute' ;
style . zIndex = - 1 ;
2020-05-19 22:14:10 +02:00
style . top = 0 ;
style . left = 0 ;
2020-12-20 22:08:16 +01:00
let ctx = c . getContext ( '2d' ) ;
2023-02-08 05:14:34 +01:00
let x0 , y0 , w , h , dw , offset ;
2018-09-21 16:05:03 +02:00
function init ( ) {
2023-03-03 12:03:20 +01:00
w = document . documentElement . clientWidth ;
h = document . documentElement . clientHeight ;
2018-09-21 16:05:03 +02:00
c . width = w ;
c . height = h ;
2023-03-01 10:04:37 +01:00
offset = $$ ( 'footer' ) . offsetHeight - 32 ;
if ( h > 800 ) offset += 16 ;
2018-09-21 16:05:03 +02:00
x0 = w / 2 ;
y0 = h - offset ;
dw = Math . max ( w , h , 1000 ) / 13 ;
drawCircles ( ) ;
}
2023-03-01 10:04:37 +01:00
Events . on ( 'bg-resize' , _ => init ( ) ) ;
window . onresize = _ => Events . fire ( 'bg-resize' ) ;
2018-09-21 16:05:03 +02:00
2021-01-11 06:40:51 +01:00
function drawCircle ( radius ) {
2018-09-21 16:05:03 +02:00
ctx . beginPath ( ) ;
2020-12-20 22:08:16 +01:00
let color = Math . round ( 255 * ( 1 - radius / Math . max ( w , h ) ) ) ;
2018-09-21 16:05:03 +02:00
ctx . strokeStyle = 'rgba(' + color + ',' + color + ',' + color + ',0.1)' ;
ctx . arc ( x0 , y0 , radius , 0 , 2 * Math . PI ) ;
ctx . stroke ( ) ;
ctx . lineWidth = 2 ;
}
2020-12-20 22:08:16 +01:00
let step = 0 ;
2018-09-21 16:05:03 +02:00
function drawCircles ( ) {
ctx . clearRect ( 0 , 0 , w , h ) ;
2020-12-20 22:08:16 +01:00
for ( let i = 0 ; i < 8 ; i ++ ) {
2021-01-11 06:40:51 +01:00
drawCircle ( dw * i + step % dw ) ;
2018-09-21 16:05:03 +02:00
}
step += 1 ;
}
2020-12-20 22:08:16 +01:00
let loading = true ;
2018-09-21 16:05:03 +02:00
function animate ( ) {
2023-01-10 14:52:03 +01:00
if ( loading || ! finished ( ) ) {
2020-12-20 22:08:16 +01:00
requestAnimationFrame ( function ( ) {
2018-09-21 16:05:03 +02:00
drawCircles ( ) ;
animate ( ) ;
} ) ;
}
}
2023-01-10 14:52:03 +01:00
function finished ( ) {
return step % dw >= dw - 5 ;
}
2018-09-21 16:05:03 +02:00
window . animateBackground = function ( l ) {
2022-12-31 12:14:56 +01:00
if ( ! l ) {
loading = false ;
} else if ( ! loading ) {
loading = true ;
2023-01-10 14:52:03 +01:00
if ( finished ( ) ) animate ( ) ;
2022-12-31 12:14:56 +01:00
}
2018-09-21 16:05:03 +02:00
} ;
init ( ) ;
animate ( ) ;
} ) ;
2023-01-25 09:43:32 +01:00
document . changeFavicon = function ( src ) {
document . querySelector ( '[rel="icon"]' ) . href = src ;
document . querySelector ( '[rel="shortcut icon"]' ) . href = src ;
}
2023-01-17 10:50:28 +01:00
// close About PairDrop page on Escape
2022-12-30 17:09:15 +01:00
window . addEventListener ( "keydown" , ( e ) => {
if ( e . key === "Escape" ) {
window . location . hash = '#' ;
}
} ) ;
2018-09-21 16:05:03 +02:00
Notifications . PERMISSION _ERROR = `
2020-12-22 21:23:10 +01:00
Notifications permission has been blocked
as the user has dismissed the permission prompt several times .
This can be reset in Page Info
2018-09-21 16:05:03 +02:00
which can be accessed by clicking the lock icon next to the URL . ` ;