mirror of
https://github.com/jech/galene.git
synced 2024-11-23 00:55:58 +01:00
Rework file transfer code.
More explicit data structures, better error handling.
This commit is contained in:
parent
0ef5d10744
commit
66e5d2951d
1 changed files with 247 additions and 237 deletions
474
static/galene.js
474
static/galene.js
|
@ -2153,74 +2153,93 @@ async function gotJoined(kind, group, perms, status, data, message) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** @type {Object<string,TransferredFile>} */
|
||||||
* @typedef {Object} transferredFile
|
|
||||||
* @property {string} id
|
|
||||||
* @property {string} username
|
|
||||||
* @property {string} name
|
|
||||||
* @property {File} [file]
|
|
||||||
* @property {RTCPeerConnection} [pc]
|
|
||||||
* @property {number} size
|
|
||||||
* @property {string} type
|
|
||||||
* @property {Array<RTCIceCandidateInit>} [candidates]
|
|
||||||
* @property {Array<Blob|ArrayBuffer>} [data]
|
|
||||||
* @property {number} [datalen]
|
|
||||||
* @property {boolean} [done]
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** @type {Object<string,transferredFile>} */
|
|
||||||
let transferredFiles = {};
|
let transferredFiles = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} up
|
* A file in the process of being transferred.
|
||||||
* @param {string} id
|
*
|
||||||
* @param {string} fileid
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function transferredFileId(up, id, fileid) {
|
function TransferredFile(id, userid, up, username, name, type, size) {
|
||||||
return id + (up ? '+' : '-') + fileid;
|
/** @type {string} */
|
||||||
|
this.id = id;
|
||||||
|
/** @type {string} */
|
||||||
|
this.userid = userid;
|
||||||
|
/** @type {boolean} */
|
||||||
|
this.up = up;
|
||||||
|
/** @type {string} */
|
||||||
|
this.username = username;
|
||||||
|
/** @type {string} */
|
||||||
|
this.name = name;
|
||||||
|
/** @type {string} */
|
||||||
|
this.type = type;
|
||||||
|
/** @type {number} */
|
||||||
|
this.size = size;
|
||||||
|
/** @type {File} */
|
||||||
|
this.file = null;
|
||||||
|
/** @type {RTCPeerConnection} */
|
||||||
|
this.pc = null;
|
||||||
|
/** @type {RTCDataChannel} */
|
||||||
|
this.dc = null;
|
||||||
|
/** @type {Array<RTCIceCandidateInit>} */
|
||||||
|
this.candidates = [];
|
||||||
|
/** @type {Array<Blob|ArrayBuffer>} */
|
||||||
|
this.data = [];
|
||||||
|
/** @type {number} */
|
||||||
|
this.datalen = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TransferredFile.prototype.fullid = function() {
|
||||||
|
return this.userid + (this.up ? '+' : '-') + this.id;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} up
|
* @param {boolean} up
|
||||||
* @param {string} id
|
* @param {string} userid
|
||||||
* @param {string} fileid
|
* @param {string} fileid
|
||||||
* @returns {transferredFile}
|
* @returns {TransferredFile}
|
||||||
*/
|
*/
|
||||||
function getTransferredFile(up, id, fileid) {
|
TransferredFile.get = function(up, userid, fileid) {
|
||||||
let f = transferredFiles[transferredFileId(up, id, fileid)];
|
return transferredFiles[userid + (up ? '+' : '-') + fileid];
|
||||||
if(!f) {
|
};
|
||||||
throw new Error("Couldn't find file being transferred");
|
|
||||||
|
TransferredFile.prototype.close = function() {
|
||||||
|
if(this.pc)
|
||||||
|
this.pc.close();
|
||||||
|
this.dc = null;
|
||||||
|
this.pc = null;
|
||||||
|
this.data = [];
|
||||||
|
this.datalen = 0;
|
||||||
|
delete(transferredFiles[this.fullid()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
TransferredFile.prototype.pushData = function(data) {
|
||||||
|
if(data instanceof Blob) {
|
||||||
|
this.datalen += data.size;
|
||||||
|
} else if(data instanceof ArrayBuffer) {
|
||||||
|
this.datalen += data.byteLength;
|
||||||
|
} else {
|
||||||
|
throw new Error('unexpected type for received data');
|
||||||
}
|
}
|
||||||
return f;
|
this.data.push(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
TransferredFile.prototype.getData = function() {
|
||||||
|
let blob = new Blob(this.data, {type: this.type});
|
||||||
|
if(blob.size != this.datalen)
|
||||||
|
throw new Error('Inconsistent data size');
|
||||||
|
this.data = [];
|
||||||
|
this.datalen = 0;
|
||||||
|
return blob;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} up
|
* @param {TransferredFile} f
|
||||||
* @param {string} id
|
|
||||||
* @param {string} fileid
|
|
||||||
*/
|
*/
|
||||||
function deleteTransferredFile(up, id, fileid) {
|
function fileTransferBox(f) {
|
||||||
let fullid = transferredFileId(up, id, fileid);
|
|
||||||
let f = transferredFiles[fullid];
|
|
||||||
if(!f)
|
|
||||||
return;
|
|
||||||
if(f.pc) {
|
|
||||||
f.pc.close();
|
|
||||||
delete(f.pc);
|
|
||||||
}
|
|
||||||
delete(transferredFiles[fullid]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {boolean} up
|
|
||||||
* @param {string} id
|
|
||||||
* @param {string} fileid
|
|
||||||
* @param {transferredFile} f
|
|
||||||
*/
|
|
||||||
function fileTransferBox(up, id, fileid, f) {
|
|
||||||
let fullid = transferredFileId(up, id, fileid);
|
|
||||||
let p = document.createElement('p');
|
let p = document.createElement('p');
|
||||||
if(up)
|
if(f.up)
|
||||||
p.textContent =
|
p.textContent =
|
||||||
`We have offered to send a file called "${f.name}" ` +
|
`We have offered to send a file called "${f.name}" ` +
|
||||||
`to user ${f.username}.`;
|
`to user ${f.username}.`;
|
||||||
|
@ -2229,35 +2248,35 @@ function fileTransferBox(up, id, fileid, f) {
|
||||||
`User ${f.username} offered to send us a file ` +
|
`User ${f.username} offered to send us a file ` +
|
||||||
`called "${f.name}" of size ${f.size}.`
|
`called "${f.name}" of size ${f.size}.`
|
||||||
let bno = null, byes = null;
|
let bno = null, byes = null;
|
||||||
if(up) {
|
if(f.up) {
|
||||||
bno = document.createElement('button');
|
bno = document.createElement('button');
|
||||||
bno.textContent = 'Cancel';
|
bno.textContent = 'Cancel';
|
||||||
bno.onclick = function(e) {
|
bno.onclick = function(e) {
|
||||||
cancelFile(id, fileid);
|
cancelFile(f);
|
||||||
};
|
};
|
||||||
bno.id = "bno-" + fullid;
|
bno.id = "bno-" + f.fullid();
|
||||||
} else {
|
} else {
|
||||||
byes = document.createElement('button');
|
byes = document.createElement('button');
|
||||||
byes.textContent = 'Accept';
|
byes.textContent = 'Accept';
|
||||||
byes.onclick = function(e) {
|
byes.onclick = function(e) {
|
||||||
getFile(id, fileid);
|
getFile(f);
|
||||||
};
|
};
|
||||||
byes.id = "byes-" + fullid;
|
byes.id = "byes-" + f.fullid();
|
||||||
bno = document.createElement('button');
|
bno = document.createElement('button');
|
||||||
bno.textContent = 'Decline';
|
bno.textContent = 'Decline';
|
||||||
bno.onclick = function(e) {
|
bno.onclick = function(e) {
|
||||||
rejectFile(id, fileid);
|
rejectFile(f);
|
||||||
};
|
};
|
||||||
bno.id = "bno-" + fullid;
|
bno.id = "bno-" + f.fullid();
|
||||||
}
|
}
|
||||||
let status = document.createElement('div');
|
let status = document.createElement('div');
|
||||||
status.id = 'status-' + fullid;
|
status.id = 'status-' + f.fullid();
|
||||||
if(!up) {
|
if(!f.up) {
|
||||||
status.textContent =
|
status.textContent =
|
||||||
'(Choosing "Accept" will disclose your IP address.)';
|
'(Choosing "Accept" will disclose your IP address.)';
|
||||||
}
|
}
|
||||||
let div = document.createElement('div');
|
let div = document.createElement('div');
|
||||||
div.id = 'file-' + fullid;
|
div.id = 'file-' + f.fullid();
|
||||||
div.appendChild(p);
|
div.appendChild(p);
|
||||||
if(byes)
|
if(byes)
|
||||||
div.appendChild(byes);
|
div.appendChild(byes);
|
||||||
|
@ -2266,36 +2285,34 @@ function fileTransferBox(up, id, fileid, f) {
|
||||||
div.appendChild(status);
|
div.appendChild(status);
|
||||||
div.classList.add('message');
|
div.classList.add('message');
|
||||||
div.classList.add('message-private');
|
div.classList.add('message-private');
|
||||||
|
div.classList.add('message-row');
|
||||||
let box = document.getElementById('box');
|
let box = document.getElementById('box');
|
||||||
box.appendChild(div);
|
box.appendChild(div);
|
||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} up
|
* @param {TransferredFile} f
|
||||||
* @param {string} id
|
|
||||||
* @param {string} fileid
|
|
||||||
* @param {string} status
|
* @param {string} status
|
||||||
* @param {boolean} [delyes]
|
* @param {boolean} [delyes]
|
||||||
* @param {boolean} [delno]
|
* @param {boolean} [delno]
|
||||||
*/
|
*/
|
||||||
function setFileStatus(up, id, fileid, status, delyes, delno) {
|
function setFileStatus(f, status, delyes, delno) {
|
||||||
let fullid = transferredFileId(up, id, fileid)
|
let statusdiv = document.getElementById('status-' + f.fullid());
|
||||||
let statusdiv = document.getElementById('status-' + fullid);
|
|
||||||
if(!statusdiv)
|
if(!statusdiv)
|
||||||
throw new Error("Couldn't find statusdiv");
|
throw new Error("Couldn't find statusdiv");
|
||||||
statusdiv.textContent = status;
|
statusdiv.textContent = status;
|
||||||
if(delyes || delno) {
|
if(delyes || delno) {
|
||||||
let div = document.getElementById('file-' + fullid);
|
let div = document.getElementById('file-' + f.fullid());
|
||||||
if(!div)
|
if(!div)
|
||||||
throw new Error("Couldn't find file div");
|
throw new Error("Couldn't find file div");
|
||||||
if(delyes) {
|
if(delyes) {
|
||||||
let byes = document.getElementById('byes-' + fullid)
|
let byes = document.getElementById('byes-' + f.fullid())
|
||||||
if(byes)
|
if(byes)
|
||||||
div.removeChild(byes);
|
div.removeChild(byes);
|
||||||
}
|
}
|
||||||
if(delno) {
|
if(delno) {
|
||||||
let bno = document.getElementById('bno-' + fullid)
|
let bno = document.getElementById('bno-' + f.fullid())
|
||||||
if(bno)
|
if(bno)
|
||||||
div.removeChild(bno);
|
div.removeChild(bno);
|
||||||
}
|
}
|
||||||
|
@ -2303,15 +2320,13 @@ function setFileStatus(up, id, fileid, status, delyes, delno) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} up
|
* @param {TransferredFile} f
|
||||||
* @param {string} id
|
|
||||||
* @param {string} fileid
|
|
||||||
* @param {any} message
|
* @param {any} message
|
||||||
*/
|
*/
|
||||||
function failFile(up, id, fileid, message) {
|
function failFile(f, message) {
|
||||||
console.error('File transfer failed:', message);
|
console.error('File transfer failed:', message);
|
||||||
setFileStatus(up, id, fileid, message ? `Failed: ${message}` : 'Failed.');
|
setFileStatus(f, message ? `Failed: ${message}` : 'Failed.');
|
||||||
deleteTransferredFile(true, id, fileid);
|
f.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2321,50 +2336,43 @@ function failFile(up, id, fileid, message) {
|
||||||
*/
|
*/
|
||||||
function offerFile(username, id, file) {
|
function offerFile(username, id, file) {
|
||||||
let fileid = newRandomId();
|
let fileid = newRandomId();
|
||||||
let fullid = transferredFileId(true, id, fileid);
|
let f = new TransferredFile(
|
||||||
if(transferredFiles[fullid])
|
fileid, id, true, username, file.name, file.type, file.size,
|
||||||
throw new Error('Id collision');
|
);
|
||||||
let f = {
|
f.file = file;
|
||||||
id: fileid,
|
transferredFiles[f.fullid()] = f;
|
||||||
username: username,
|
try {
|
||||||
file: file,
|
fileTransferBox(f);
|
||||||
name: file.name,
|
|
||||||
size: file.size,
|
|
||||||
type: file.type,
|
|
||||||
}
|
|
||||||
fileTransferBox(true, id, fileid, f);
|
|
||||||
serverConnection.userMessage('offerfile', id, {
|
serverConnection.userMessage('offerfile', id, {
|
||||||
id: fileid,
|
id: fileid,
|
||||||
name: f.name,
|
name: f.name,
|
||||||
size: f.size,
|
size: f.size,
|
||||||
type: f.type,
|
type: f.type,
|
||||||
});
|
});
|
||||||
transferredFiles[fullid] = f;
|
} catch(e) {
|
||||||
|
displayError(e);
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} id
|
* @param {TransferredFile} f
|
||||||
* @param {string} fileid
|
|
||||||
*/
|
*/
|
||||||
function cancelFile(id, fileid) {
|
function cancelFile(f) {
|
||||||
let f = getTransferredFile(true, id, fileid);
|
serverConnection.userMessage('cancelfile', f.userid, {
|
||||||
serverConnection.userMessage('cancelfile', id, {
|
|
||||||
id: f.id,
|
id: f.id,
|
||||||
});
|
});
|
||||||
deleteTransferredFile(true, id, fileid);
|
f.close();
|
||||||
setFileStatus(true, id, fileid, 'Cancelled.', true, true);
|
setFileStatus(f, 'Cancelled.', true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} id
|
* @param {TransferredFile} f
|
||||||
* @param {string} fileid
|
|
||||||
*/
|
*/
|
||||||
async function getFile(id, fileid) {
|
async function getFile(f) {
|
||||||
let f = getTransferredFile(false, id, fileid);
|
|
||||||
if(f.pc)
|
if(f.pc)
|
||||||
throw new Error('Download already in progress');
|
throw new Error("Download already in progress");
|
||||||
|
setFileStatus(f, 'Connecting...', true);
|
||||||
setFileStatus(false, id, fileid, 'Connecting...', true);
|
|
||||||
let pc = new RTCPeerConnection(serverConnection.rtcConfiguration);
|
let pc = new RTCPeerConnection(serverConnection.rtcConfiguration);
|
||||||
if(!pc)
|
if(!pc)
|
||||||
throw new Error("Couldn't create peer connection");
|
throw new Error("Couldn't create peer connection");
|
||||||
|
@ -2377,75 +2385,71 @@ async function getFile(id, fileid) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
pc.onicecandidate = function(e) {
|
pc.onicecandidate = function(e) {
|
||||||
serverConnection.userMessage('filedownice', id, {
|
serverConnection.userMessage('filedownice', f.userid, {
|
||||||
id: f.id,
|
id: f.id,
|
||||||
candidate: e.candidate,
|
candidate: e.candidate,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
let dc = pc.createDataChannel('file');
|
f.dc = pc.createDataChannel('file');
|
||||||
f.data = [];
|
f.data = [];
|
||||||
f.datalen = 0;
|
f.datalen = 0;
|
||||||
dc.onclose = function(e) {
|
f.dc.onclose = function(e) {
|
||||||
try {
|
try {
|
||||||
closeReceiveFileData(id, fileid, f);
|
closeReceiveFileData(f);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
failFile(false, id, fileid, e);
|
failFile(f, e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
dc.onmessage = function(e) {
|
f.dc.onmessage = function(e) {
|
||||||
try {
|
try {
|
||||||
receiveFileData(id, fileid, f, dc, e.data);
|
receiveFileData(f, e.data);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
failFile(false, id, fileid, e);
|
failFile(f, e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
dc.onerror = function(e) {
|
f.dc.onerror = function(e) {
|
||||||
/** @ts-ignore */
|
/** @ts-ignore */
|
||||||
let err = e.error;
|
let err = e.error;
|
||||||
failFile(false, id, fileid, err);
|
failFile(f, err);
|
||||||
};
|
};
|
||||||
let offer = await pc.createOffer();
|
let offer = await pc.createOffer();
|
||||||
if(!offer)
|
if(!offer)
|
||||||
throw new Error("Couldn't create offer");
|
throw new Error("Couldn't create offer");
|
||||||
await pc.setLocalDescription(offer);
|
await pc.setLocalDescription(offer);
|
||||||
serverConnection.userMessage('getfile', id, {
|
serverConnection.userMessage('getfile', f.userid, {
|
||||||
id: f.id,
|
id: f.id,
|
||||||
offer: pc.localDescription.sdp,
|
offer: pc.localDescription.sdp,
|
||||||
});
|
});
|
||||||
setFileStatus(false, id, fileid, 'Negotiating...', true);
|
setFileStatus(f, 'Negotiating...', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} id
|
* @param {TransferredFile} f
|
||||||
* @param {string} fileid
|
|
||||||
*/
|
*/
|
||||||
async function rejectFile(id, fileid) {
|
async function rejectFile(f) {
|
||||||
let f = getTransferredFile(false, id, fileid);
|
serverConnection.userMessage('rejectfile', f.userid, {
|
||||||
serverConnection.userMessage('rejectfile', id, {
|
|
||||||
id: f.id,
|
id: f.id,
|
||||||
});
|
});
|
||||||
deleteTransferredFile(false, id, fileid);
|
setFileStatus(f, 'Rejected.', true, true);
|
||||||
setFileStatus(false, id, fileid, 'Rejected.', true, true);
|
f.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} id
|
* @param {TransferredFile} f
|
||||||
* @param {string} fileid
|
|
||||||
* @param {string} sdp
|
* @param {string} sdp
|
||||||
*/
|
*/
|
||||||
async function sendFile(id, fileid, sdp) {
|
async function sendFile(f, sdp) {
|
||||||
let f = getTransferredFile(true, id, fileid);
|
|
||||||
if(f.pc)
|
if(f.pc)
|
||||||
throw new Error('Transfer already in progress');
|
throw new Error('Transfer already in progress');
|
||||||
|
|
||||||
setFileStatus(true, id, fileid, 'Negotiating...', true);
|
setFileStatus(f, 'Negotiating...', true);
|
||||||
let pc = new RTCPeerConnection(serverConnection.rtcConfiguration);
|
let pc = new RTCPeerConnection(serverConnection.rtcConfiguration);
|
||||||
if(!pc)
|
if(!pc)
|
||||||
throw new Error("Couldn't create peer connection");
|
throw new Error("Couldn't create peer connection");
|
||||||
f.pc = pc;
|
f.pc = pc;
|
||||||
f.candidates = [];
|
f.candidates = [];
|
||||||
pc.onicecandidate = function(e) {
|
pc.onicecandidate = function(e) {
|
||||||
serverConnection.userMessage('fileupice', id, {
|
serverConnection.userMessage('fileupice', f.userid, {
|
||||||
id: f.id,
|
id: f.id,
|
||||||
candidate: e.candidate,
|
candidate: e.candidate,
|
||||||
});
|
});
|
||||||
|
@ -2456,29 +2460,30 @@ async function sendFile(id, fileid, sdp) {
|
||||||
f.candidates = [];
|
f.candidates = [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let file = f.file;
|
|
||||||
pc.ondatachannel = function(e) {
|
pc.ondatachannel = function(e) {
|
||||||
let dc = /** @type{RTCDataChannel} */(e.channel);
|
if(f.dc)
|
||||||
dc.onclose = function(e) {
|
throw new Error('Duplicate datachannel');
|
||||||
|
f.dc = /** @type{RTCDataChannel} */(e.channel);
|
||||||
|
f.dc.onclose = function(e) {
|
||||||
try {
|
try {
|
||||||
closeSendFileData(id, fileid, f);
|
closeSendFileData(f);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
failFile(true, id, fileid, e);
|
failFile(f, e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
dc.onerror = function(e) {
|
f.dc.onerror = function(e) {
|
||||||
/** @ts-ignore */
|
/** @ts-ignore */
|
||||||
let err = e.error;
|
let err = e.error;
|
||||||
failFile(true, id, fileid, err);
|
failFile(f, err);
|
||||||
}
|
}
|
||||||
dc.onmessage = function(e) {
|
f.dc.onmessage = function(e) {
|
||||||
try {
|
try {
|
||||||
ackSendFileData(id, fileid, f, e.data);
|
ackSendFileData(f, e.data);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
failFile(true, id, fileid, e);
|
failFile(f, e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
sendFileData(id, fileid, f, dc, file);
|
sendFileData(f).catch(e => failFile(f, e));
|
||||||
};
|
};
|
||||||
|
|
||||||
await pc.setRemoteDescription({
|
await pc.setRemoteDescription({
|
||||||
|
@ -2490,62 +2495,60 @@ async function sendFile(id, fileid, sdp) {
|
||||||
if(!answer)
|
if(!answer)
|
||||||
throw new Error("Couldn't create answer");
|
throw new Error("Couldn't create answer");
|
||||||
await pc.setLocalDescription(answer);
|
await pc.setLocalDescription(answer);
|
||||||
serverConnection.userMessage('sendfile', id, {
|
serverConnection.userMessage('sendfile', f.userid, {
|
||||||
id: f.id,
|
id: f.id,
|
||||||
answer: pc.localDescription.sdp,
|
answer: pc.localDescription.sdp,
|
||||||
});
|
});
|
||||||
setFileStatus(true, id, fileid, 'Uploading...', true);
|
setFileStatus(f, 'Uploading...', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} id
|
* @param {TransferredFile} f
|
||||||
* @param {string} fileid
|
|
||||||
* @param {string} sdp
|
* @param {string} sdp
|
||||||
*/
|
*/
|
||||||
async function receiveFile(id, fileid, sdp) {
|
async function receiveFile(f, sdp) {
|
||||||
let f = getTransferredFile(false, id, fileid);
|
|
||||||
if(!f.pc)
|
|
||||||
throw new Error('Transfer is not in progress');
|
|
||||||
await f.pc.setRemoteDescription({
|
await f.pc.setRemoteDescription({
|
||||||
type: 'answer',
|
type: 'answer',
|
||||||
sdp: sdp,
|
sdp: sdp,
|
||||||
});
|
});
|
||||||
setFileStatus(false, id, fileid, 'Downloading...', true);
|
setFileStatus(f, 'Downloading...', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} id
|
* @param {TransferredFile} f
|
||||||
* @param {string} fileid
|
|
||||||
* @param {transferredFile} f
|
|
||||||
* @param {RTCDataChannel} dc
|
|
||||||
* @param {File} file
|
|
||||||
*/
|
*/
|
||||||
async function sendFileData(id, fileid, f, dc, file) {
|
async function sendFileData(f) {
|
||||||
let r = file.stream().getReader();
|
let r = f.file.stream().getReader();
|
||||||
f.datalen = 0;
|
|
||||||
|
|
||||||
dc.bufferedAmountLowThreshold = 65536;
|
f.dc.bufferedAmountLowThreshold = 65536;
|
||||||
|
|
||||||
async function write(a) {
|
async function write(a) {
|
||||||
while(dc.bufferedAmount > 65536) {
|
while(f.dc.bufferedAmount > f.dc.bufferedAmountLowThreshold) {
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
dc.onbufferedamountlow = function(e) {
|
if(f.dc == null) {
|
||||||
|
reject(new Error('File is closed.'));
|
||||||
|
}
|
||||||
|
f.dc.onbufferedamountlow = function(e) {
|
||||||
|
if(f.dc == null) {
|
||||||
|
reject(new Error('File is closed.'));
|
||||||
|
}
|
||||||
|
f.dc.onbufferedamountlow = null;
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
dc.send(a);
|
f.dc.send(a);
|
||||||
f.datalen += a.length;
|
f.datalen += a.length;
|
||||||
setFileStatus(
|
setFileStatus(f, `Uploading... ${f.datalen}/${f.size}`, true);
|
||||||
true, id, fileid, `Uploading... ${f.datalen}/${f.size}`, true,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while(true) {
|
while(true) {
|
||||||
let v = await r.read();
|
let v = await r.read();
|
||||||
if(v.done)
|
if(v.done)
|
||||||
break;
|
break;
|
||||||
if(v.value.length < 16384) {
|
if(!(v.value instanceof Uint8Array))
|
||||||
|
throw new Error('Unexpected type for chunk');
|
||||||
|
if(v.value.length <= 16384) {
|
||||||
await write(v.value);
|
await write(v.value);
|
||||||
} else {
|
} else {
|
||||||
for(let i = 0; i < v.value.length; i += 16384) {
|
for(let i = 0; i < v.value.length; i += 16384) {
|
||||||
|
@ -2559,70 +2562,71 @@ async function sendFileData(id, fileid, f, dc, file) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} id
|
* @param {TransferredFile} f
|
||||||
* @param {string} fileid
|
|
||||||
* @param {transferredFile} f
|
|
||||||
*/
|
*/
|
||||||
function ackSendFileData(id, fileid, f, data) {
|
function ackSendFileData(f, data) {
|
||||||
if(data === 'done' && f.datalen == f.size)
|
if(data === 'done' && f.datalen == f.size)
|
||||||
setFileStatus(true, id, fileid, 'Done.', true, true);
|
setFileStatus(f, 'Done.', true, true);
|
||||||
else
|
else
|
||||||
setFileStatus(true, id, fileid, 'Failed.', true, true);
|
setFileStatus(f, 'Failed.', true, true);
|
||||||
f.done = true;
|
f.dc.onclose = null;
|
||||||
deleteTransferredFile(true, id, fileid);
|
f.dc.onerror = null;
|
||||||
|
f.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} id
|
* @param {TransferredFile} f
|
||||||
* @param {string} fileid
|
|
||||||
* @param {transferredFile} f
|
|
||||||
*/
|
*/
|
||||||
function closeSendFileData(id, fileid, f) {
|
function closeSendFileData(f) {
|
||||||
if(!f.done)
|
setFileStatus(f, 'Failed.', true, true);
|
||||||
setFileStatus(true, id, fileid, 'Failed.', true, true);
|
f.close();
|
||||||
deleteTransferredFile(true, id, fileid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} id
|
* @param {TransferredFile} f
|
||||||
* @param {string} fileid
|
|
||||||
* @param {transferredFile} f
|
|
||||||
* @param {RTCDataChannel} dc
|
|
||||||
* @param {Blob|ArrayBuffer} data
|
* @param {Blob|ArrayBuffer} data
|
||||||
*/
|
*/
|
||||||
function receiveFileData(id, fileid, f, dc, data) {
|
function receiveFileData(f, data) {
|
||||||
f.data.push(data);
|
f.pushData(data);
|
||||||
if(data instanceof Blob) {
|
setFileStatus(f, `Downloading... ${f.datalen}/${f.size}`, true);
|
||||||
f.datalen += data.size;
|
|
||||||
} else if(data instanceof ArrayBuffer) {
|
|
||||||
f.datalen += data.byteLength;
|
|
||||||
} else {
|
|
||||||
console.error('Unexpeced type for received data', data);
|
|
||||||
throw new Error('unexpected type for received data');
|
|
||||||
}
|
|
||||||
|
|
||||||
setFileStatus(
|
|
||||||
false, id, fileid, `Downloading... ${f.datalen}/${f.size}`, true,
|
|
||||||
);
|
|
||||||
|
|
||||||
if(f.datalen < f.size)
|
if(f.datalen < f.size)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(f.datalen > f.size) {
|
if(f.datalen != f.size) {
|
||||||
setFileStatus(false, id, fileid, 'Failed.', true, true);
|
setFileStatus(f, 'Failed.', true, true);
|
||||||
deleteTransferredFile(false, id, fileid);
|
f.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dc.onmessage = null;
|
f.dc.onmessage = null;
|
||||||
dc.onerror = null;
|
doneReceiveFileData(f);
|
||||||
|
}
|
||||||
|
|
||||||
dc.send('done');
|
/**
|
||||||
|
* @param {TransferredFile} f
|
||||||
|
*/
|
||||||
|
async function doneReceiveFileData(f) {
|
||||||
|
setFileStatus(f, 'Done.', true, true);
|
||||||
|
let blob = f.getData();
|
||||||
|
|
||||||
setFileStatus(false, id, fileid, 'Done.', true, true);
|
await new Promise((resolve, reject) => {
|
||||||
|
let timer = setTimeout(function(e) { resolve(); }, 2000);
|
||||||
|
f.dc.onclose = function(e) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
f.dc.onerror = function(e) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
f.dc.send('done');
|
||||||
|
});
|
||||||
|
|
||||||
|
f.dc.onclose = null;
|
||||||
|
f.dc.onerror = null;
|
||||||
|
f.close();
|
||||||
|
|
||||||
let blob = new Blob(f.data, {type: f.type});
|
|
||||||
f.data = null;
|
|
||||||
let url = URL.createObjectURL(blob);
|
let url = URL.createObjectURL(blob);
|
||||||
let a = document.createElement('a');
|
let a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
|
@ -2634,14 +2638,12 @@ function receiveFileData(id, fileid, f, dc, data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} id
|
* @param {TransferredFile} f
|
||||||
* @param {string} fileid
|
|
||||||
* @param {transferredFile} f
|
|
||||||
*/
|
*/
|
||||||
function closeReceiveFileData(id, fileid, f) {
|
function closeReceiveFileData(f) {
|
||||||
if(f.datalen != f.size) {
|
if(f.datalen != f.size) {
|
||||||
setFileStatus(false, id, fileid, 'Failed.', true, true)
|
setFileStatus(f, 'Failed.', true, true)
|
||||||
deleteTransferredFile(false, id, fileid);
|
f.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2683,38 +2685,46 @@ function gotUserMessage(id, dest, username, time, privileged, kind, message) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'offerfile': {
|
case 'offerfile': {
|
||||||
let fullid = transferredFileId(false, id, message.id);
|
let f = new TransferredFile(
|
||||||
let f = {
|
message.id, id, false, username,
|
||||||
id: message.id,
|
message.name, message.type, message.size,
|
||||||
username: username,
|
);
|
||||||
name: message.name,
|
transferredFiles[f.fullid()] = f;
|
||||||
type: message.type,
|
fileTransferBox(f);
|
||||||
size: message.size,
|
|
||||||
};
|
|
||||||
transferredFiles[fullid] = f;
|
|
||||||
fileTransferBox(false, id, message.id, f);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'cancelfile': {
|
case 'cancelfile': {
|
||||||
setFileStatus(false, id, message.id, 'Cancelled.', true, true);
|
let f = TransferredFile.get(false, id, message.id);
|
||||||
deleteTransferredFile(false, id, message.id);
|
if(!f)
|
||||||
|
throw new Error('unexpected cancelfile');
|
||||||
|
setFileStatus(f, 'Cancelled.', true, true);
|
||||||
|
f.close();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'getfile': {
|
case 'getfile': {
|
||||||
sendFile(id, message.id, message.offer);
|
let f = TransferredFile.get(true, id, message.id);
|
||||||
|
if(!f)
|
||||||
|
throw new Error('unexpected getfile');
|
||||||
|
sendFile(f, message.offer);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'rejectfile': {
|
case 'rejectfile': {
|
||||||
setFileStatus(true, id, message.id, 'Rejected.', true, true);
|
let f = TransferredFile.get(true, id, message.id);
|
||||||
deleteTransferredFile(true, id, message.id);
|
if(!f)
|
||||||
|
throw new Error('unexpected rejectfile');
|
||||||
|
setFileStatus(f, 'Rejected.', true, true);
|
||||||
|
f.close();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'sendfile': {
|
case 'sendfile': {
|
||||||
receiveFile(id, message.id, message.answer);
|
let f = TransferredFile.get(false, id, message.id);
|
||||||
|
if(!f)
|
||||||
|
throw new Error('unexpected sendfile');
|
||||||
|
receiveFile(f, message.answer);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'filedownice': {
|
case 'filedownice': {
|
||||||
let f = getTransferredFile(true, id, message.id);
|
let f = TransferredFile.get(true, id, message.id);
|
||||||
if(!f.pc) {
|
if(!f.pc) {
|
||||||
console.warn('Unexpected filedownice');
|
console.warn('Unexpected filedownice');
|
||||||
return;
|
return;
|
||||||
|
@ -2726,7 +2736,7 @@ function gotUserMessage(id, dest, username, time, privileged, kind, message) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'fileupice': {
|
case 'fileupice': {
|
||||||
let f = getTransferredFile(false, id, message.id);
|
let f = TransferredFile.get(false, id, message.id);
|
||||||
if(!f.pc) {
|
if(!f.pc) {
|
||||||
console.warn('Unexpected fileupice');
|
console.warn('Unexpected fileupice');
|
||||||
return;
|
return;
|
||||||
|
@ -3279,7 +3289,7 @@ commands.sendfile = {
|
||||||
let files = input.files;
|
let files = input.files;
|
||||||
for(let i = 0; i < files.length; i++) {
|
for(let i = 0; i < files.length; i++) {
|
||||||
try {
|
try {
|
||||||
offerFile(p[0], id, files[i]);
|
offerFile(p[i], id, files[i]);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
displayError(e);
|
displayError(e);
|
||||||
|
|
Loading…
Reference in a new issue