mirror of
https://github.com/schlagmichdoch/PairDrop.git
synced 2025-04-22 07:46:17 -04:00
Decrease redundancy by changing the way the websocket fallback is included; Adding new env var SIGNALING_SERVER to host client files but use another server for signaling.
This commit is contained in:
parent
cb72edef20
commit
3439e7f6d4
62 changed files with 439 additions and 10101 deletions
|
@ -1,24 +1,3 @@
|
|||
window.URL = window.URL || window.webkitURL;
|
||||
window.isRtcSupported = !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection);
|
||||
|
||||
if (!window.isRtcSupported) alert("WebRTC must be enabled for PairDrop to work");
|
||||
|
||||
window.hiddenProperty = 'hidden' in document
|
||||
? 'hidden'
|
||||
: 'webkitHidden' in document
|
||||
? 'webkitHidden'
|
||||
: 'mozHidden' in document
|
||||
? 'mozHidden'
|
||||
: null;
|
||||
|
||||
window.visibilityChangeEvent = 'visibilitychange' in document
|
||||
? 'visibilitychange'
|
||||
: 'webkitvisibilitychange' in document
|
||||
? 'webkitvisibilitychange'
|
||||
: 'mozvisibilitychange' in document
|
||||
? 'mozvisibilitychange'
|
||||
: null;
|
||||
|
||||
class ServerConnection {
|
||||
|
||||
constructor() {
|
||||
|
@ -44,7 +23,33 @@ class ServerConnection {
|
|||
Events.on('offline', _ => clearTimeout(this._reconnectTimer));
|
||||
Events.on('online', _ => this._connect());
|
||||
|
||||
this._connect();
|
||||
this._getConfig().then(() => this._connect());
|
||||
}
|
||||
|
||||
_getConfig() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', 'config', true);
|
||||
xhr.addEventListener("load", () => {
|
||||
if (xhr.status === 200) {
|
||||
// Config received
|
||||
let config = JSON.parse(xhr.responseText);
|
||||
this._config = config;
|
||||
Events.fire('config', config);
|
||||
resolve()
|
||||
} else if (xhr.status !== 200) {
|
||||
// Handle errors
|
||||
console.error('Error:', xhr.status, xhr.statusText);
|
||||
reject();
|
||||
}
|
||||
});
|
||||
xhr.send();
|
||||
})
|
||||
}
|
||||
|
||||
_setWsConfig(wsConfig) {
|
||||
this._wsConfig = wsConfig;
|
||||
Events.fire('ws-config', wsConfig);
|
||||
}
|
||||
|
||||
_connect() {
|
||||
|
@ -111,16 +116,12 @@ class ServerConnection {
|
|||
this.send({ type: 'leave-public-room' });
|
||||
}
|
||||
|
||||
_setRtcConfig(config) {
|
||||
window.rtcConfig = config;
|
||||
}
|
||||
|
||||
_onMessage(msg) {
|
||||
msg = JSON.parse(msg);
|
||||
if (msg.type !== 'ping') console.log('WS receive:', msg);
|
||||
switch (msg.type) {
|
||||
case 'rtc-config':
|
||||
this._setRtcConfig(msg.config);
|
||||
case 'ws-config':
|
||||
this._setWsConfig(msg.wsConfig);
|
||||
break;
|
||||
case 'peers':
|
||||
this._onPeers(msg);
|
||||
|
@ -170,6 +171,25 @@ class ServerConnection {
|
|||
case 'public-room-left':
|
||||
Events.fire('public-room-left');
|
||||
break;
|
||||
case 'request':
|
||||
case 'header':
|
||||
case 'partition':
|
||||
case 'partition-received':
|
||||
case 'progress':
|
||||
case 'files-transfer-response':
|
||||
case 'file-transfer-complete':
|
||||
case 'message-transfer-complete':
|
||||
case 'text':
|
||||
case 'display-name-changed':
|
||||
case 'ws-chunk':
|
||||
// ws-fallback
|
||||
if (this._wsConfig.wsFallback) {
|
||||
Events.fire('ws-relay', JSON.stringify(msg));
|
||||
}
|
||||
else {
|
||||
console.log("WS receive: message type is for websocket fallback only but websocket fallback is not activated on this instance.")
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.error('WS receive: unknown message type', msg);
|
||||
}
|
||||
|
@ -209,17 +229,24 @@ class ServerConnection {
|
|||
}
|
||||
|
||||
_endpoint() {
|
||||
// hack to detect if deployment or development environment
|
||||
const protocol = location.protocol.startsWith('https') ? 'wss' : 'ws';
|
||||
const webrtc = window.isRtcSupported ? '/webrtc' : '/fallback';
|
||||
let ws_url = new URL(protocol + '://' + location.host + location.pathname + 'server' + webrtc);
|
||||
// Check whether the instance specifies another signaling server otherwise use the current instance for signaling
|
||||
let wsServerDomain = this._config.signalingServer
|
||||
? this._config.signalingServer
|
||||
: location.host + location.pathname;
|
||||
|
||||
let wsUrl = new URL(protocol + '://' + wsServerDomain + 'server');
|
||||
|
||||
wsUrl.searchParams.append('webrtc_supported', window.isRtcSupported ? 'true' : 'false');
|
||||
|
||||
const peerId = sessionStorage.getItem('peer_id');
|
||||
const peerIdHash = sessionStorage.getItem('peer_id_hash');
|
||||
if (peerId && peerIdHash) {
|
||||
ws_url.searchParams.append('peer_id', peerId);
|
||||
ws_url.searchParams.append('peer_id_hash', peerIdHash);
|
||||
wsUrl.searchParams.append('peer_id', peerId);
|
||||
wsUrl.searchParams.append('peer_id_hash', peerIdHash);
|
||||
}
|
||||
return ws_url.toString();
|
||||
|
||||
return wsUrl.toString();
|
||||
}
|
||||
|
||||
_disconnect() {
|
||||
|
@ -298,6 +325,9 @@ class Peer {
|
|||
this._send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
// Is overwritten in expanding classes
|
||||
_send(message) {}
|
||||
|
||||
sendDisplayName(displayName) {
|
||||
this.sendJSON({type: 'display-name-changed', displayName: displayName});
|
||||
}
|
||||
|
@ -314,14 +344,24 @@ class Peer {
|
|||
return this._roomIds['secret'];
|
||||
}
|
||||
|
||||
_regenerationOfPairSecretNeeded() {
|
||||
return this._getPairSecret() && this._getPairSecret().length !== 256
|
||||
}
|
||||
|
||||
_getRoomTypes() {
|
||||
return Object.keys(this._roomIds);
|
||||
}
|
||||
|
||||
_updateRoomIds(roomType, roomId) {
|
||||
const roomTypeIsSecret = roomType === "secret";
|
||||
const roomIdIsNotPairSecret = this._getPairSecret() !== roomId;
|
||||
|
||||
// if peer is another browser tab, peer is not identifiable with roomSecret as browser tabs share all roomSecrets
|
||||
// -> do not delete duplicates and do not regenerate room secrets
|
||||
if (!this._isSameBrowser() && roomType === "secret" && this._isPaired() && this._getPairSecret() !== roomId) {
|
||||
if (!this._isSameBrowser()
|
||||
&& roomTypeIsSecret
|
||||
&& this._isPaired()
|
||||
&& roomIdIsNotPairSecret) {
|
||||
// multiple roomSecrets with same peer -> delete old roomSecret
|
||||
PersistentStorage
|
||||
.deleteRoomSecret(this._getPairSecret())
|
||||
|
@ -332,8 +372,13 @@ class Peer {
|
|||
|
||||
this._roomIds[roomType] = roomId;
|
||||
|
||||
if (!this._isSameBrowser() && roomType === "secret" && this._isPaired() && this._getPairSecret().length !== 256 && this._isCaller) {
|
||||
// increase security by initiating the increase of the roomSecret length from 64 chars (<v1.7.0) to 256 chars (v1.7.0+)
|
||||
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+)
|
||||
console.log('RoomSecret is regenerated to increase security')
|
||||
Events.fire('regenerate-room-secret', this._getPairSecret());
|
||||
}
|
||||
|
@ -698,9 +743,12 @@ class Peer {
|
|||
|
||||
class RTCPeer extends Peer {
|
||||
|
||||
constructor(serverConnection, isCaller, peerId, roomType, roomId) {
|
||||
constructor(serverConnection, isCaller, peerId, roomType, roomId, rtcConfig) {
|
||||
super(serverConnection, isCaller, peerId, roomType, roomId);
|
||||
|
||||
this.rtcSupported = true;
|
||||
this.rtcConfig = rtcConfig
|
||||
|
||||
if (!this._isCaller) return; // we will listen for a caller
|
||||
this._connect();
|
||||
}
|
||||
|
@ -717,7 +765,7 @@ class RTCPeer extends Peer {
|
|||
}
|
||||
|
||||
_openConnection() {
|
||||
this._conn = new RTCPeerConnection(window.rtcConfig);
|
||||
this._conn = new RTCPeerConnection(this.rtcConfig);
|
||||
this._conn.onicecandidate = e => this._onIceCandidate(e);
|
||||
this._conn.onicecandidateerror = e => this._onError(e);
|
||||
this._conn.onconnectionstatechange = _ => this._onConnectionStateChange();
|
||||
|
@ -910,6 +958,48 @@ class RTCPeer extends Peer {
|
|||
}
|
||||
}
|
||||
|
||||
class WSPeer extends Peer {
|
||||
|
||||
constructor(serverConnection, isCaller, peerId, roomType, roomId) {
|
||||
super(serverConnection, isCaller, peerId, roomType, roomId);
|
||||
|
||||
this.rtcSupported = false;
|
||||
|
||||
if (!this._isCaller) return; // we will listen for a caller
|
||||
this._sendSignal();
|
||||
}
|
||||
|
||||
_send(chunk) {
|
||||
this.sendJSON({
|
||||
type: 'ws-chunk',
|
||||
chunk: arrayBufferToBase64(chunk)
|
||||
});
|
||||
}
|
||||
|
||||
sendJSON(message) {
|
||||
message.to = this._peerId;
|
||||
message.roomType = this._getRoomTypes()[0];
|
||||
message.roomId = this._roomIds[this._getRoomTypes()[0]];
|
||||
this._server.send(message);
|
||||
}
|
||||
|
||||
_sendSignal(connected = false) {
|
||||
this.sendJSON({type: 'signal', connected: connected});
|
||||
}
|
||||
|
||||
onServerMessage(message) {
|
||||
this._peerId = message.sender.id;
|
||||
Events.fire('peer-connected', {peerId: message.sender.id, connectionHash: this.getConnectionHash()})
|
||||
if (message.connected) return;
|
||||
this._sendSignal(true);
|
||||
}
|
||||
|
||||
getConnectionHash() {
|
||||
// Todo: implement SubtleCrypto asymmetric encryption and create connectionHash from public keys
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
class PeersManager {
|
||||
|
||||
constructor(serverConnection) {
|
||||
|
@ -937,6 +1027,13 @@ class PeersManager {
|
|||
Events.on('self-display-name-changed', e => this._notifyPeersDisplayNameChanged(e.detail));
|
||||
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));
|
||||
Events.on('ws-disconnected', _ => this._onWsDisconnected());
|
||||
Events.on('ws-relay', e => this._onWsRelay(e.detail));
|
||||
Events.on('ws-config', e => this._onWsConfig(e.detail));
|
||||
}
|
||||
|
||||
_onWsConfig(wsConfig) {
|
||||
this._wsConfig = wsConfig;
|
||||
}
|
||||
|
||||
_onMessage(message) {
|
||||
|
@ -944,9 +1041,10 @@ class PeersManager {
|
|||
this.peers[peerId].onServerMessage(message);
|
||||
}
|
||||
|
||||
_refreshPeer(peer, roomType, roomId) {
|
||||
if (!peer) return false;
|
||||
_refreshPeer(peerId, roomType, roomId) {
|
||||
if (!this._peerExists(peerId)) return false;
|
||||
|
||||
const peer = this.peers[peerId];
|
||||
const roomTypesDiffer = Object.keys(peer._roomIds)[0] !== roomType;
|
||||
const roomIdsDiffer = peer._roomIds[roomType] !== roomId;
|
||||
|
||||
|
@ -964,26 +1062,42 @@ class PeersManager {
|
|||
return true;
|
||||
}
|
||||
|
||||
_createOrRefreshPeer(isCaller, peerId, roomType, roomId) {
|
||||
const peer = this.peers[peerId];
|
||||
if (peer) {
|
||||
this._refreshPeer(peer, roomType, roomId);
|
||||
_createOrRefreshPeer(isCaller, peerId, roomType, roomId, rtcSupported) {
|
||||
if (this._peerExists(peerId)) {
|
||||
this._refreshPeer(peerId, roomType, roomId);
|
||||
return;
|
||||
}
|
||||
|
||||
this.peers[peerId] = new RTCPeer(this._server, isCaller, peerId, roomType, roomId);
|
||||
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 {
|
||||
console.warn("Websocket fallback is not activated on this instance.\n" +
|
||||
"Activate WebRTC in this browser or ask the admin of this instance to activate the websocket fallback.")
|
||||
}
|
||||
}
|
||||
|
||||
_onPeerJoined(message) {
|
||||
this._createOrRefreshPeer(false, message.peer.id, message.roomType, message.roomId);
|
||||
this._createOrRefreshPeer(false, message.peer.id, message.roomType, message.roomId, message.peer.rtcSupported);
|
||||
}
|
||||
|
||||
_onPeers(message) {
|
||||
message.peers.forEach(peer => {
|
||||
this._createOrRefreshPeer(true, peer.id, message.roomType, message.roomId);
|
||||
this._createOrRefreshPeer(true, peer.id, message.roomType, message.roomId, peer.rtcSupported);
|
||||
})
|
||||
}
|
||||
|
||||
_onWsRelay(message) {
|
||||
if (!this._wsConfig.wsFallback) return;
|
||||
|
||||
const messageJSON = JSON.parse(message);
|
||||
if (messageJSON.type === 'ws-chunk') message = base64ToArrayBuffer(messageJSON.chunk);
|
||||
this.peers[messageJSON.sender.id]._onMessage(message);
|
||||
}
|
||||
|
||||
_onRespondToFileTransferRequest(detail) {
|
||||
this.peers[detail.to]._respondToFileTransferRequest(detail.accepted);
|
||||
}
|
||||
|
@ -1009,6 +1123,9 @@ class PeersManager {
|
|||
}
|
||||
|
||||
_onPeerLeft(message) {
|
||||
if (this._peerExists(message.peerId) && this._webRtcSupported(message.peerId)) {
|
||||
console.log('WSPeer left:', message.peerId);
|
||||
}
|
||||
if (message.disconnect === true) {
|
||||
// if user actively disconnected from PairDrop server, disconnect all peer to peer connections immediately
|
||||
this._disconnectOrRemoveRoomTypeByPeerId(message.peerId, message.roomType);
|
||||
|
@ -1029,6 +1146,24 @@ class PeersManager {
|
|||
this._notifyPeerDisplayNameChanged(peerId);
|
||||
}
|
||||
|
||||
_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onPeerDisconnected(peerId) {
|
||||
const peer = this.peers[peerId];
|
||||
delete this.peers[peerId];
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
window.iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||||
window.android = /android/i.test(navigator.userAgent);
|
||||
window.isMobile = window.iOS || window.android;
|
||||
window.pasteMode = {};
|
||||
window.pasteMode.activated = false;
|
||||
|
||||
class PeersUI {
|
||||
|
||||
constructor() {
|
||||
|
@ -16,11 +10,16 @@ class PeersUI {
|
|||
this.$discoveryWrapper = $$('footer .discovery-wrapper');
|
||||
this.$displayName = $('display-name');
|
||||
this.$header = $$('header.opacity-0');
|
||||
this.$wsFallbackWarning = $('websocket-fallback');
|
||||
|
||||
this.evaluateHeader = ["notification", "edit-paired-devices"];
|
||||
this.fadedIn = false;
|
||||
this.peers = {};
|
||||
|
||||
this.pasteMode = {};
|
||||
this.pasteMode.activated = false;
|
||||
this.pasteMode.descriptor = "";
|
||||
|
||||
Events.on('display-name', e => this._onDisplayName(e.detail.displayName));
|
||||
Events.on('peer-joined', e => this._onPeerJoined(e.detail));
|
||||
Events.on('peer-added', _ => this._evaluateOverflowing());
|
||||
|
@ -61,6 +60,20 @@ class PeersUI {
|
|||
|
||||
// Load saved display name on page load
|
||||
Events.on('ws-connected', _ => this._loadSavedDisplayName());
|
||||
|
||||
Events.on('ws-config', e => this._evaluateRtcSupport(e.detail))
|
||||
}
|
||||
|
||||
_evaluateRtcSupport(wsConfig) {
|
||||
if (wsConfig.wsFallback) {
|
||||
this.$wsFallbackWarning.hidden = false;
|
||||
}
|
||||
else {
|
||||
this.$wsFallbackWarning.hidden = true;
|
||||
if (!window.isRtcSupported) {
|
||||
alert(Localization.getTranslation("instructions.webrtc-requirement"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_loadSavedDisplayName() {
|
||||
|
@ -198,7 +211,7 @@ class PeersUI {
|
|||
}
|
||||
|
||||
_onKeyDown(e) {
|
||||
if (this._noDialogShown() && window.pasteMode.activated && e.code === "Escape") {
|
||||
if (this._noDialogShown() && this.pasteMode.activated && e.code === "Escape") {
|
||||
Events.fire('deactivate-paste-mode');
|
||||
}
|
||||
|
||||
|
@ -318,7 +331,7 @@ class PeersUI {
|
|||
}
|
||||
|
||||
_activatePasteMode(files, text) {
|
||||
if (!window.pasteMode.activated && (files.length > 0 || text.length > 0)) {
|
||||
if (!this.pasteMode.activated && (files.length > 0 || text.length > 0)) {
|
||||
const openPairDrop = Localization.getTranslation("instructions.activate-paste-mode-base");
|
||||
const andOtherFiles = Localization.getTranslation("instructions.activate-paste-mode-and-other-files", null, {count: files.length-1});
|
||||
const sharedText = Localization.getTranslation("instructions.activate-paste-mode-shared-text");
|
||||
|
@ -344,17 +357,20 @@ class PeersUI {
|
|||
|
||||
this.$xNoPeers.querySelector('h2').innerHTML = `${openPairDrop}<br>${descriptor}`;
|
||||
|
||||
const _callback = (e) => this._sendClipboardData(e, files, text);
|
||||
this.pasteMode.descriptor = descriptor;
|
||||
|
||||
const _callback = e => this._sendClipboardData(e, files, text);
|
||||
Events.on('paste-pointerdown', _callback);
|
||||
Events.on('deactivate-paste-mode', _ => this._deactivatePasteMode(_callback), { once: true });
|
||||
|
||||
this.$cancelPasteModeBtn.removeAttribute('hidden');
|
||||
|
||||
window.pasteMode.descriptor = descriptor;
|
||||
window.pasteMode.activated = true;
|
||||
|
||||
console.log('Paste mode activated.');
|
||||
Events.fire('paste-mode-changed');
|
||||
Events.fire('paste-mode-changed', {
|
||||
active: true,
|
||||
descriptor: descriptor
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -363,9 +379,9 @@ class PeersUI {
|
|||
}
|
||||
|
||||
_deactivatePasteMode(_callback) {
|
||||
if (window.pasteMode.activated) {
|
||||
window.pasteMode.descriptor = undefined;
|
||||
window.pasteMode.activated = false;
|
||||
if (this.pasteMode.activated) {
|
||||
this.pasteMode.activated = false;
|
||||
this.pasteMode.descriptor = "";
|
||||
Events.off('paste-pointerdown', _callback);
|
||||
|
||||
this.$xInstructions.querySelector('p').innerText = '';
|
||||
|
@ -379,7 +395,10 @@ class PeersUI {
|
|||
this.$cancelPasteModeBtn.setAttribute('hidden', "");
|
||||
|
||||
console.log('Paste mode deactivated.')
|
||||
Events.fire('paste-mode-changed');
|
||||
Events.fire('paste-mode-changed', {
|
||||
active: false,
|
||||
descriptor: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -405,22 +424,29 @@ class PeersUI {
|
|||
class PeerUI {
|
||||
|
||||
constructor(peer, connectionHash) {
|
||||
this.$xInstructions = $$('x-instructions');
|
||||
this.$xPeers = $$('x-peers');
|
||||
|
||||
this._peer = peer;
|
||||
this._connectionHash =
|
||||
`${connectionHash.substring(0, 4)} ${connectionHash.substring(4, 8)} ${connectionHash.substring(8, 12)} ${connectionHash.substring(12, 16)}`;
|
||||
|
||||
this.pasteMode = {};
|
||||
this.pasteMode.activated = false;
|
||||
this.pasteMode.descriptor = "";
|
||||
|
||||
this._initDom();
|
||||
this._bindListeners();
|
||||
|
||||
$$('x-peers').appendChild(this.$el)
|
||||
this.$xPeers.appendChild(this.$el);
|
||||
Events.fire('peer-added');
|
||||
this.$xInstructions = $$('x-instructions');
|
||||
}
|
||||
|
||||
html() {
|
||||
let title;
|
||||
let input = '';
|
||||
if (window.pasteMode.activated) {
|
||||
title = Localization.getTranslation("peer-ui.click-to-send-paste-mode", null, {descriptor: window.pasteMode.descriptor});
|
||||
if (this.pasteMode.activated) {
|
||||
title = Localization.getTranslation("peer-ui.click-to-send-paste-mode", null, {descriptor: this.pasteMode.descriptor});
|
||||
}
|
||||
else {
|
||||
title = Localization.getTranslation("peer-ui.click-to-send");
|
||||
|
@ -463,6 +489,8 @@ class PeerUI {
|
|||
}
|
||||
|
||||
Object.keys(this._peer._roomIds).forEach(roomType => this.$el.classList.add(`type-${roomType}`));
|
||||
|
||||
if (!this._peer.rtcSupported || !window.isRtcSupported) this.$el.classList.add('ws-peer');
|
||||
}
|
||||
|
||||
_initDom() {
|
||||
|
@ -488,16 +516,18 @@ class PeerUI {
|
|||
this._callbackPointerDown = e => this._onPointerDown(e)
|
||||
|
||||
// PasteMode
|
||||
Events.on('paste-mode-changed', _ => this._onPasteModeChanged());
|
||||
Events.on('paste-mode-changed', e => this._onPasteModeChanged(e.detail.active, e.detail.descriptor));
|
||||
}
|
||||
|
||||
_onPasteModeChanged() {
|
||||
_onPasteModeChanged(active, descriptor) {
|
||||
this.pasteMode.active = active
|
||||
this.pasteMode.descriptor = descriptor
|
||||
this.html();
|
||||
this._bindListeners();
|
||||
}
|
||||
|
||||
_bindListeners() {
|
||||
if(!window.pasteMode.activated) {
|
||||
if(!this.pasteMode.activated) {
|
||||
// Remove Events Paste Mode
|
||||
this.$el.removeEventListener('pointerdown', this._callbackPointerDown);
|
||||
|
||||
|
@ -1827,7 +1857,7 @@ class SendTextDialog extends Dialog {
|
|||
this.$form = this.$el.querySelector('form');
|
||||
this.$submit = this.$el.querySelector('button[type="submit"]');
|
||||
this.$form.addEventListener('submit', e => this._onSubmit(e));
|
||||
this.$text.addEventListener('input', e => this._onChange(e));
|
||||
this.$text.addEventListener('input', _ => this._onChange());
|
||||
Events.on('keydown', e => this._onKeyDown(e));
|
||||
}
|
||||
|
||||
|
@ -1847,7 +1877,7 @@ class SendTextDialog extends Dialog {
|
|||
return !this.$text.innerText || this.$text.innerText === "\n";
|
||||
}
|
||||
|
||||
_onChange(e) {
|
||||
_onChange() {
|
||||
if (this._textInputEmpty()) {
|
||||
this.$submit.setAttribute('disabled', '');
|
||||
}
|
||||
|
|
|
@ -37,9 +37,34 @@ if (!navigator.clipboard) {
|
|||
}
|
||||
}
|
||||
|
||||
// Polyfills
|
||||
window.isRtcSupported = !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection);
|
||||
|
||||
window.hiddenProperty = 'hidden' in document
|
||||
? 'hidden'
|
||||
: 'webkitHidden' in document
|
||||
? 'webkitHidden'
|
||||
: 'mozHidden' in document
|
||||
? 'mozHidden'
|
||||
: null;
|
||||
|
||||
window.visibilityChangeEvent = 'visibilitychange' in document
|
||||
? 'visibilitychange'
|
||||
: 'webkitvisibilitychange' in document
|
||||
? 'webkitvisibilitychange'
|
||||
: 'mozvisibilitychange' in document
|
||||
? 'mozvisibilitychange'
|
||||
: null;
|
||||
|
||||
window.iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||||
window.android = /android/i.test(navigator.userAgent);
|
||||
window.isMobile = window.iOS || window.android;
|
||||
|
||||
// Selector shortcuts
|
||||
const $ = query => document.getElementById(query);
|
||||
const $$ = query => document.querySelector(query);
|
||||
|
||||
// Helper functions
|
||||
const zipper = (() => {
|
||||
|
||||
let zipWriter;
|
||||
|
@ -416,3 +441,23 @@ function changeFavicon(src) {
|
|||
document.querySelector('[rel="icon"]').href = src;
|
||||
document.querySelector('[rel="shortcut icon"]').href = src;
|
||||
}
|
||||
|
||||
function arrayBufferToBase64(buffer) {
|
||||
let binary = '';
|
||||
let bytes = new Uint8Array(buffer);
|
||||
let len = bytes.byteLength;
|
||||
for (let i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return window.btoa( binary );
|
||||
}
|
||||
|
||||
function base64ToArrayBuffer(base64) {
|
||||
let binary_string = window.atob(base64);
|
||||
let len = binary_string.length;
|
||||
let bytes = new Uint8Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
bytes[i] = binary_string.charCodeAt(i);
|
||||
}
|
||||
return bytes.buffer;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue