mirror of
https://github.com/schlagmichdoch/PairDrop.git
synced 2025-04-21 23:36:17 -04:00
Move service worker digestion into separate class and add static function to check if it is supported by the browser. Change ram-exceed-ios waring accordingly.
This commit is contained in:
parent
90f10910aa
commit
f4a947527d
3 changed files with 178 additions and 105 deletions
|
@ -484,7 +484,7 @@ class Peer {
|
|||
|
||||
_sendData(data) {}
|
||||
|
||||
_onMessage(message) {
|
||||
async _onMessage(message) {
|
||||
switch (message.type) {
|
||||
case 'display-name-changed':
|
||||
this._onDisplayNameChanged(message);
|
||||
|
@ -493,7 +493,7 @@ class Peer {
|
|||
this._onState(message.state);
|
||||
break;
|
||||
case 'transfer-request':
|
||||
this._onTransferRequest(message);
|
||||
await this._onTransferRequest(message);
|
||||
break;
|
||||
case 'transfer-request-response':
|
||||
this._onTransferRequestResponse(message);
|
||||
|
@ -740,44 +740,44 @@ class Peer {
|
|||
}
|
||||
|
||||
// File Receiver Only
|
||||
_onTransferRequest(request) {
|
||||
async _onTransferRequest(request) {
|
||||
// Only accept one request at a time per peer
|
||||
if (this._pendingRequest) {
|
||||
// Only accept one request at a time per peer
|
||||
this._sendTransferRequestResponse(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if each file must be loaded into RAM completely. This might lead to a page crash (Memory limit iOS Safari: ~380 MB)
|
||||
if (!(await FileDigesterWorker.isSupported())) {
|
||||
Logger.warn('Big file transfers might exceed the RAM of the receiver. Use a secure context (https) and do not use private tabs to prevent this.');
|
||||
|
||||
// Check if page will crash on iOS
|
||||
if (window.iOS && await this._filesTooBigForSwOnIOS(request.header)) {
|
||||
Events.fire('notify-user', Localization.getTranslation('notifications.ram-exceed-ios'));
|
||||
|
||||
// Would exceed RAM -> decline request
|
||||
this._sendTransferRequestResponse(false, 'ram-exceed-ios');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._pendingRequest = request;
|
||||
|
||||
if (!window.Worker || !window.isSecureContext) {
|
||||
// Each file must be loaded into RAM completely which might lead to a page crash (Memory limit iOS Safari: ~380 MB)
|
||||
Logger.warn('Big file transfers might exceed the RAM of the receiver. Use a secure context (https) to prevent this.');
|
||||
}
|
||||
|
||||
if (window.iOS && this._filesTooBigForIos(request.header)) {
|
||||
// Page will crash. Decline request
|
||||
Events.fire('notify-user', Localization.getTranslation('notifications.ram-exceed-ios'));
|
||||
this._sendTransferRequestResponse(false, 'ram-exceed-ios');
|
||||
return;
|
||||
}
|
||||
|
||||
// Automatically accept request if auto-accept is set to true via the Edit Paired Devices Dialog
|
||||
if (this._autoAccept) {
|
||||
// auto accept if set via Edit Paired Devices Dialog
|
||||
this._sendTransferRequestResponse(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// default behavior: show user transfer request
|
||||
// Default behavior: show transfer request to user
|
||||
Events.fire('files-transfer-request', {
|
||||
request: request,
|
||||
peerId: this._peerId
|
||||
});
|
||||
}
|
||||
|
||||
_filesTooBigForIos(files) {
|
||||
if (window.Worker && window.isSecureContext) {
|
||||
return false;
|
||||
}
|
||||
async _filesTooBigForSwOnIOS(files) {
|
||||
// Files over 250 MB crash safari if not handled via a service worker
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (files[i].size > 250000000) {
|
||||
return true;
|
||||
|
@ -1313,7 +1313,7 @@ class RTCPeer extends Peer {
|
|||
this._state = Peer.STATE_TRANSFER_PROCEEDING;
|
||||
}
|
||||
|
||||
_onMessage(message) {
|
||||
async _onMessage(message) {
|
||||
Logger.debug('RTC Receive:', JSON.parse(message));
|
||||
try {
|
||||
message = JSON.parse(message);
|
||||
|
@ -1321,7 +1321,7 @@ class RTCPeer extends Peer {
|
|||
Logger.warn("RTCPeer: Received JSON is malformed");
|
||||
return;
|
||||
}
|
||||
super._onMessage(message);
|
||||
await super._onMessage(message);
|
||||
}
|
||||
|
||||
getConnectionHash() {
|
||||
|
@ -1412,9 +1412,9 @@ class WSPeer extends Peer {
|
|||
this._sendSignal(true);
|
||||
}
|
||||
|
||||
_onMessage(message) {
|
||||
async _onMessage(message) {
|
||||
Logger.debug('WS Receive:', message);
|
||||
super._onMessage(message);
|
||||
await super._onMessage(message);
|
||||
}
|
||||
|
||||
_onWsRelay(message) {
|
||||
|
@ -1847,12 +1847,14 @@ class FileDigester {
|
|||
if (this._bytesReceived < this._size) return;
|
||||
|
||||
// We are done receiving. Preferably use a file worker to process the file to prevent exceeding of available RAM
|
||||
if (!window.Worker && !window.isSecureContext) {
|
||||
this.processFileViaMemory();
|
||||
return;
|
||||
}
|
||||
|
||||
this.processFileViaWorker();
|
||||
FileDigesterWorker.isSupported()
|
||||
.then(supported => {
|
||||
if (!supported) {
|
||||
this.processFileViaMemory();
|
||||
return;
|
||||
}
|
||||
this.processFileViaWorker();
|
||||
});
|
||||
}
|
||||
|
||||
processFileViaMemory() {
|
||||
|
@ -1865,88 +1867,146 @@ class FileDigester {
|
|||
}
|
||||
|
||||
processFileViaWorker() {
|
||||
// Use service worker to prevent loading the complete file into RAM
|
||||
const fileWorker = new Worker("scripts/sw-file-digester.js");
|
||||
|
||||
let i = 0;
|
||||
let offset = 0;
|
||||
|
||||
const _this = this;
|
||||
|
||||
function sendPart(buffer, offset) {
|
||||
fileWorker.postMessage({
|
||||
type: "part",
|
||||
name: _this._name,
|
||||
buffer: buffer,
|
||||
offset: offset
|
||||
});
|
||||
}
|
||||
|
||||
function getFile() {
|
||||
fileWorker.postMessage({
|
||||
type: "get-file",
|
||||
name: _this._name,
|
||||
});
|
||||
}
|
||||
|
||||
function deleteFile() {
|
||||
fileWorker.postMessage({
|
||||
type: "delete-file",
|
||||
name: _this._name
|
||||
const fileDigesterWorker = new FileDigesterWorker();
|
||||
fileDigesterWorker.digestFileBuffer(this._buffer, this._name)
|
||||
.then(file => {
|
||||
this._fileCompleteCallback(file);
|
||||
})
|
||||
}
|
||||
.catch(reason => {
|
||||
Logger.warn(reason);
|
||||
this.processFileViaWorker();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function onPart(part) {
|
||||
if (i < _this._buffer.length - 1) {
|
||||
// process next chunk
|
||||
offset += part.byteLength;
|
||||
i++;
|
||||
sendPart(_this._buffer[i], offset);
|
||||
return;
|
||||
}
|
||||
class FileDigesterWorker {
|
||||
|
||||
// File processing complete -> retrieve completed file
|
||||
getFile();
|
||||
}
|
||||
constructor() {
|
||||
// Use service worker to prevent loading the complete file into RAM
|
||||
this.fileWorker = new Worker("scripts/sw-file-digester.js");
|
||||
|
||||
function onFile(file) {
|
||||
_this._buffer = [];
|
||||
_this._fileCompleteCallback(file);
|
||||
deleteFile();
|
||||
}
|
||||
|
||||
function onFileDeleted() {
|
||||
// File Digestion complete -> Tidy up
|
||||
fileWorker.terminate();
|
||||
}
|
||||
|
||||
function onError(error) {
|
||||
// an error occurred.
|
||||
Logger.error(error);
|
||||
Logger.warn('Failed to process file via service-worker. Do not use Firefox private mode to prevent this.')
|
||||
|
||||
// Use memory method instead and terminate service worker.
|
||||
fileWorker.terminate();
|
||||
_this.processFileViaMemory();
|
||||
}
|
||||
|
||||
sendPart(this._buffer[i], offset);
|
||||
|
||||
fileWorker.onmessage = (e) => {
|
||||
this.fileWorker.onmessage = (e) => {
|
||||
switch (e.data.type) {
|
||||
case "support":
|
||||
this.onSupport(e.data.supported);
|
||||
break;
|
||||
case "part":
|
||||
onPart(e.data.part);
|
||||
this.onPart(e.data.part);
|
||||
break;
|
||||
case "file":
|
||||
onFile(e.data.file);
|
||||
this.onFile(e.data.file);
|
||||
break;
|
||||
case "file-deleted":
|
||||
onFileDeleted();
|
||||
this.onFileDeleted();
|
||||
break;
|
||||
case "error":
|
||||
onError(e.data.error);
|
||||
this.onError(e.data.error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static isSupported() {
|
||||
// Check if web worker is supported and supports specific functions
|
||||
return new Promise(async resolve => {
|
||||
if (!window.Worker || !window.isSecureContext) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const fileDigesterWorker = new FileDigesterWorker();
|
||||
|
||||
resolve(await fileDigesterWorker.checkSupport());
|
||||
|
||||
fileDigesterWorker.fileWorker.terminate();
|
||||
})
|
||||
}
|
||||
|
||||
checkSupport() {
|
||||
return new Promise(resolve => {
|
||||
this.resolveSupport = resolve;
|
||||
this.fileWorker.postMessage({
|
||||
type: "check-support"
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
onSupport(supported) {
|
||||
if (!this.resolveSupport) return;
|
||||
|
||||
this.resolveSupport(supported);
|
||||
this.resolveSupport = null;
|
||||
}
|
||||
|
||||
digestFileBuffer(buffer, fileName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.resolveFile = resolve;
|
||||
this.rejectFile = reject;
|
||||
|
||||
this.i = 0;
|
||||
this.offset = 0;
|
||||
|
||||
this.buffer = buffer;
|
||||
this.fileName = fileName;
|
||||
|
||||
this.sendPart(this.buffer[0], 0);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
sendPart(buffer, offset) {
|
||||
this.fileWorker.postMessage({
|
||||
type: "part",
|
||||
name: this.fileName,
|
||||
buffer: buffer,
|
||||
offset: offset
|
||||
});
|
||||
}
|
||||
|
||||
getFile() {
|
||||
this.fileWorker.postMessage({
|
||||
type: "get-file",
|
||||
name: this.fileName,
|
||||
});
|
||||
}
|
||||
|
||||
deleteFile() {
|
||||
this.fileWorker.postMessage({
|
||||
type: "delete-file",
|
||||
name: this.fileName
|
||||
})
|
||||
}
|
||||
|
||||
onPart(part) {
|
||||
if (this.i < this.buffer.length - 1) {
|
||||
// process next chunk
|
||||
this.offset += part.byteLength;
|
||||
this.i++;
|
||||
this.sendPart(this.buffer[this.i], this.offset);
|
||||
return;
|
||||
}
|
||||
|
||||
// File processing complete -> retrieve completed file
|
||||
this.getFile();
|
||||
}
|
||||
|
||||
onFile(file) {
|
||||
this.buffer = [];
|
||||
this.resolveFile(file);
|
||||
this.deleteFile();
|
||||
}
|
||||
|
||||
onFileDeleted() {
|
||||
// File Digestion complete -> Tidy up
|
||||
this.fileWorker.terminate();
|
||||
}
|
||||
|
||||
onError(error) {
|
||||
// an error occurred.
|
||||
Logger.error(error);
|
||||
|
||||
// Use memory method instead and terminate service worker.
|
||||
this.fileWorker.terminate();
|
||||
this.rejectFile("Failed to process file via service-worker. Do not use Firefox private mode to prevent this.");
|
||||
}
|
||||
}
|
|
@ -1,14 +1,17 @@
|
|||
self.addEventListener('message', async e => {
|
||||
try {
|
||||
switch (e.data.type) {
|
||||
case "check-support":
|
||||
await checkSupport();
|
||||
break;
|
||||
case "part":
|
||||
await this.onPart(e.data.name, e.data.buffer, e.data.offset);
|
||||
await onPart(e.data.name, e.data.buffer, e.data.offset);
|
||||
break;
|
||||
case "get-file":
|
||||
await this.onGetFile(e.data.name);
|
||||
await onGetFile(e.data.name);
|
||||
break;
|
||||
case "delete-file":
|
||||
await this.onDeleteFile(e.data.name);
|
||||
await onDeleteFile(e.data.name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +20,16 @@ self.addEventListener('message', async e => {
|
|||
}
|
||||
})
|
||||
|
||||
async function checkSupport() {
|
||||
try {
|
||||
await getAccessHandle("test.txt");
|
||||
self.postMessage({type: "support", supported: true});
|
||||
}
|
||||
catch (e) {
|
||||
self.postMessage({type: "support", supported: false});
|
||||
}
|
||||
}
|
||||
|
||||
async function getFileHandle(fileName) {
|
||||
const root = await navigator.storage.getDirectory();
|
||||
return await root.getFileHandle(fileName, {create: true});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue