@jsxc/jsxc
Version:
Real-time XMPP chat application with video calls, file transfer and encrypted communication
148 lines (121 loc) • 4.45 kB
text/typescript
import JID from '../../JID';
import * as Namespace from '../../connection/xmpp/namespace';
import { IPluginAPI } from '../../plugin/PluginAPI.interface';
import Attachment from '../../Attachment';
import { $iq } from '../../vendor/Strophe';
const ALLOWED_HEADERS = ['Authorization', 'Cookie', 'Expires'];
export default class HttpUploadService {
private namespace;
constructor(private pluginAPI: IPluginAPI, private jid: JID, private maxFileSize: number = 0) {
this.namespace = Namespace.get('HTTPUPLOAD');
this.maxFileSize = maxFileSize || 0;
}
public getJid(): JID {
return this.jid;
}
public getMaxFileSize(): number {
return this.maxFileSize;
}
public isSuitable(attachment: Attachment): boolean {
return this.maxFileSize === 0 || attachment.getSize() <= this.maxFileSize;
}
public async sendFile(file: File, progress?: (loaded, total) => void): Promise<string> {
let urls = await this.requestSlot(file);
await this.uploadFile(file, urls.put, urls.putHeaders, progress);
if ((<any>file).aesgcm) {
return urls.get.replace(/^https?:/, 'aesgcm:') + '#' + (<any>file).aesgcm;
}
return urls.get;
}
private requestSlot(file: File) {
let iq = $iq({
to: this.jid.full,
type: 'get',
}).c('request', {
xmlns: this.namespace,
filename: file.name,
size: file.size,
'content-type': file.type,
});
return this.pluginAPI.sendIQ(iq).then(this.parseSlotResponse).catch(this.parseSlotError);
}
private parseSlotResponse = stanza => {
let slot = $(stanza).find(`slot[xmlns="${this.namespace}"]`);
if (slot.length > 0) {
let put = slot.find('put').attr('url');
let get = slot.find('get').attr('url');
let putHeaders = {};
slot
.find('put')
.find('header')
.map((index, header) => {
return {
name: $(header).attr('name').replace(/\n/g, ''),
value: $(header).text().replace(/\n/g, ''),
};
})
.get()
.filter(header => ALLOWED_HEADERS.indexOf(header.name) > -1)
.forEach(header => (putHeaders[header.name] = header.value));
return Promise.resolve({
put,
get,
putHeaders,
});
}
return this.parseSlotError(stanza);
};
private parseSlotError = stanza => {
let error = {
type: $(stanza).find('error').attr('type') || 'unknown',
text: $(stanza).find('error text').text() || 'response does not contain a slot element',
reason: null,
};
if ($(stanza).find('error not-acceptable')) {
error.reason = 'not-acceptable';
} else if ($(stanza).find('error resource-constraint')) {
error.reason = 'resource-constraint';
} else if ($(stanza).find('error not-allowed')) {
error.reason = 'not-allowed';
} else if ($(stanza).find('error forbidden')) {
error.reason = 'forbidden';
}
return Promise.reject(error);
};
private uploadFile = (file: File, putUrl, headers, progress?: (loaded, total) => void) => {
return new Promise<void>((resolve, reject) => {
$.ajax({
url: putUrl,
type: 'PUT',
contentType: file.type,
data: file,
processData: false,
headers,
xhr() {
let xhr = (<any>$).ajaxSettings.xhr();
// track upload progress
xhr.upload.onprogress = function (ev) {
if (ev.lengthComputable && typeof progress === 'function') {
progress(ev.loaded, ev.total);
}
};
return xhr;
},
success: () => {
this.pluginAPI.Log.debug('file successful uploaded');
resolve();
},
error: jqXHR => {
this.pluginAPI.Log.warn('error while uploading file to ' + putUrl);
reject(
new Error(
jqXHR && jqXHR.readyState === 0
? 'Upload was probably blocked by your browser'
: 'Could not upload file'
)
);
},
});
});
};
}