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