780 lines
27 KiB
JavaScript
780 lines
27 KiB
JavaScript
(function (window) {
|
||
var factory = function () {
|
||
var Promise = window.Promise;
|
||
var cache;
|
||
var cypherChunkLength = 131088;
|
||
|
||
// Save a blob on the file system
|
||
var saveFile = function (blob, url, fileName) {
|
||
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
|
||
window.navigator.msSaveOrOpenBlob(blob, fileName);
|
||
} else {
|
||
// We want to be able to download the file with a name, so we need an "a" tag with
|
||
// a download attribute
|
||
var a = document.createElement("a");
|
||
a.href = url;
|
||
a.download = fileName;
|
||
// It's not in the DOM, so we can't use a.click();
|
||
var event = new MouseEvent("click");
|
||
a.dispatchEvent(event);
|
||
}
|
||
};
|
||
|
||
var fixHTML = function (str) {
|
||
if (!str) { return ''; }
|
||
return str.replace(/[<>&"']/g, function (x) {
|
||
return ({ "<": "<", ">": ">", "&": "&", '"': """, "'": "'" })[x];
|
||
});
|
||
};
|
||
|
||
|
||
var isplainTextFile = function (metadata) {
|
||
// does its type begins with "text/"
|
||
if (metadata.type.indexOf("text/") === 0) { return true; }
|
||
// no type and no file extension -> let's guess it's plain text
|
||
var parsedName = /^(\.?.+?)(\.[^.]+)?$/.exec(metadata.name) || [];
|
||
if (!metadata.type && !parsedName[2]) { return true; }
|
||
// other exceptions
|
||
if (metadata.type === 'application/x-javascript') { return true; }
|
||
if (metadata.type === 'application/xml') { return true; }
|
||
return false;
|
||
};
|
||
|
||
// Default config, can be overriden per media-tag call
|
||
var config = {
|
||
allowed: [
|
||
'text/plain',
|
||
'image/png',
|
||
'image/jpeg',
|
||
'image/jpg',
|
||
'image/gif',
|
||
'audio/mpeg',
|
||
'audio/mp3',
|
||
'audio/ogg',
|
||
'audio/wav',
|
||
'audio/webm',
|
||
'video/mp4',
|
||
'video/ogg',
|
||
'video/webm',
|
||
'application/pdf',
|
||
//'application/dash+xml', // FIXME?
|
||
'download'
|
||
],
|
||
pdf: {},
|
||
download: {
|
||
text: "Save",
|
||
textDl: "Load attachment"
|
||
},
|
||
Plugins: {
|
||
/**
|
||
* @param {object} metadataObject {name, metadatatype, owners} containing metadata of the file
|
||
* @param {strint} url Url of the blob object
|
||
* @param {Blob} content Blob object containing the data of the file
|
||
* @param {object} cfg Object {Plugins, allowed, download, pdf} containing infos about plugins
|
||
* @param {function} cb Callback function: (err, pluginElement) => {}
|
||
*/
|
||
text: function (metadata, url, content, cfg, cb) {
|
||
var plainText = document.createElement('div');
|
||
plainText.className = "plain-text-reader";
|
||
plainText.setAttribute('style', 'white-space: pre-wrap;');
|
||
var reader = new FileReader();
|
||
reader.addEventListener('loadend', function (e) {
|
||
plainText.innerText = e.srcElement.result;
|
||
cb(void 0, plainText);
|
||
});
|
||
reader.readAsText(content);
|
||
},
|
||
image: function (metadata, url, content, cfg, cb) {
|
||
var img = document.createElement('img');
|
||
img.setAttribute('src', url);
|
||
img.blob = content;
|
||
cb(void 0, img);
|
||
},
|
||
video: function (metadata, url, content, cfg, cb) {
|
||
var video = document.createElement('video');
|
||
video.setAttribute('src', url);
|
||
video.setAttribute('controls', true);
|
||
cb(void 0, video);
|
||
},
|
||
audio: function (metadata, url, content, cfg, cb) {
|
||
var audio = document.createElement('audio');
|
||
audio.setAttribute('src', url);
|
||
audio.setAttribute('controls', true);
|
||
cb(void 0, audio);
|
||
},
|
||
pdf: function (metadata, url, content, cfg, cb) {
|
||
var iframe = document.createElement('iframe');
|
||
if (cfg.pdf.viewer) { // PDFJS
|
||
var viewerUrl = cfg.pdf.viewer + '?file=' + url;
|
||
iframe.src = viewerUrl + '#' + window.encodeURIComponent(metadata.name);
|
||
return void cb (void 0, iframe);
|
||
}
|
||
iframe.src = url + '#' + window.encodeURIComponent(metadata.name);
|
||
return void cb (void 0, iframe);
|
||
},
|
||
download: function (metadata, url, content, cfg, cb) {
|
||
var btn = document.createElement('button');
|
||
btn.setAttribute('class', 'btn btn-default');
|
||
btn.innerHTML = '<i class="fa fa-save"></i>' + cfg.download.text + '<br>' +
|
||
(metadata.name ? '<b>' + fixHTML(metadata.name) + '</b>' : '');
|
||
btn.addEventListener('click', function () {
|
||
saveFile(content, url, metadata.name);
|
||
});
|
||
cb(void 0, btn);
|
||
}
|
||
}
|
||
};
|
||
|
||
var makeProgressBar = function (cfg, mediaObject) {
|
||
if (mediaObject.bar) { return; }
|
||
mediaObject.bar = true;
|
||
var style = (function(){/*
|
||
.mediatag-progress-container {
|
||
position: relative;
|
||
border: 1px solid #0087FF;
|
||
background: white;
|
||
height: 25px;
|
||
display: inline-flex;
|
||
width: 200px;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-sizing: border-box;
|
||
vertical-align: top;
|
||
}
|
||
.mediatag-progress-bar {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
background: #0087FF;
|
||
width: 0%;
|
||
}
|
||
.mediatag-progress-text {
|
||
font-size: 14px;
|
||
height: 25px;
|
||
width: 50px;
|
||
margin-left: 5px;
|
||
line-height: 25px;
|
||
vertical-align: top;
|
||
display: inline-block;
|
||
color: #3F4141;
|
||
font-weight: bold;
|
||
}
|
||
*/}).toString().slice(14, -3);
|
||
var container = document.createElement('div');
|
||
container.classList.add('mediatag-progress-container');
|
||
var bar = document.createElement('div');
|
||
bar.classList.add('mediatag-progress-bar');
|
||
container.appendChild(bar);
|
||
|
||
var text = document.createElement('span');
|
||
text.classList.add('mediatag-progress-text');
|
||
text.innerText = '0%';
|
||
|
||
mediaObject.on('progress', function (obj) {
|
||
var percent = obj.progress;
|
||
text.innerText = (Math.round(percent*10))/10+'%';
|
||
bar.setAttribute('style', 'width:'+percent+'%;');
|
||
});
|
||
|
||
mediaObject.tag.innerHTML = '<style>'+style+'</style>';
|
||
mediaObject.tag.appendChild(container);
|
||
mediaObject.tag.appendChild(text);
|
||
};
|
||
var makeDownloadButton = function (cfg, mediaObject, size, cb) {
|
||
var metadata = cfg.metadata || {};
|
||
var i = '<i class="fa fa-paperclip"></i>';
|
||
var name = metadata.name ? '<span class="mediatag-download-name">'+ i +'<b>'+
|
||
fixHTML(metadata.name)+'</b></span>' : '';
|
||
var btn = document.createElement('button');
|
||
btn.setAttribute('class', 'btn btn-default mediatag-download-btn');
|
||
btn.innerHTML = name + '<span>' + (name ? '' : i) +
|
||
cfg.download.textDl + ' <b>(' + size + 'MB)</b></span>';
|
||
btn.addEventListener('click', function () {
|
||
makeProgressBar(cfg, mediaObject);
|
||
var a = (cfg.body || document).querySelectorAll('media-tag[src="'+mediaObject.tag.getAttribute('src')+'"] button.mediatag-download-btn');
|
||
for(var i = 0; i < a.length; i++) {
|
||
if (a[i] !== btn) { a[i].click(); }
|
||
}
|
||
cb();
|
||
});
|
||
mediaObject.tag.innerHTML = '';
|
||
mediaObject.tag.appendChild(btn);
|
||
};
|
||
|
||
var getCacheKey = function (src) {
|
||
var _src = src.replace(/(\/)*$/, ''); // Remove trailing slashes
|
||
var idx = _src.lastIndexOf('/');
|
||
var cacheKey = _src.slice(idx+1);
|
||
if (!/^[a-f0-9]{48}$/.test(cacheKey)) { cacheKey = undefined; }
|
||
return cacheKey;
|
||
};
|
||
|
||
var getBlobCache = function (id, cb) {
|
||
if (!config.Cache || typeof(config.Cache.getBlobCache) !== "function") {
|
||
return void cb('EINVAL');
|
||
}
|
||
config.Cache.getBlobCache(id, cb);
|
||
};
|
||
var setBlobCache = function (id, u8, cb) {
|
||
if (!config.Cache || typeof(config.Cache.setBlobCache) !== "function") {
|
||
return void cb('EINVAL');
|
||
}
|
||
config.Cache.setBlobCache(id, u8, cb);
|
||
};
|
||
|
||
var getFileSize = function (src, _cb) {
|
||
var cb = function (e, res) {
|
||
_cb(e, res);
|
||
cb = function () {};
|
||
};
|
||
|
||
var cacheKey = getCacheKey(src);
|
||
|
||
var check = function () {
|
||
var xhr = new XMLHttpRequest();
|
||
xhr.open("HEAD", src);
|
||
xhr.onerror = function () { return void cb("XHR_ERROR"); };
|
||
xhr.onreadystatechange = function() {
|
||
if (this.readyState === this.DONE) {
|
||
cb(null, Number(xhr.getResponseHeader("Content-Length")));
|
||
}
|
||
};
|
||
xhr.onload = function () {
|
||
if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); }
|
||
};
|
||
xhr.send();
|
||
};
|
||
|
||
if (!cacheKey) { return void check(); }
|
||
|
||
getBlobCache(cacheKey, function (err, u8) {
|
||
if (err || !u8) { return void check(); }
|
||
cb(null, 0);
|
||
});
|
||
};
|
||
|
||
// Download a blob from href
|
||
var download = function (src, _cb, progressCb) {
|
||
var cb = function (e, res) {
|
||
_cb(e, res);
|
||
cb = function () {};
|
||
};
|
||
|
||
var cacheKey = getCacheKey(src);
|
||
|
||
var fetch = function () {
|
||
var xhr = new XMLHttpRequest();
|
||
xhr.open('GET', src, true);
|
||
xhr.responseType = 'arraybuffer';
|
||
|
||
var progress = function (offset) {
|
||
progressCb(offset * 100);
|
||
};
|
||
xhr.addEventListener("progress", function (evt) {
|
||
if (evt.lengthComputable) {
|
||
var percentComplete = evt.loaded / evt.total;
|
||
progress(percentComplete);
|
||
}
|
||
}, false);
|
||
|
||
xhr.onerror = function () { return void cb("XHR_ERROR"); };
|
||
xhr.onload = function () {
|
||
// Error?
|
||
if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); }
|
||
|
||
var arrayBuffer = xhr.response;
|
||
if (arrayBuffer) {
|
||
var u8 = new Uint8Array(arrayBuffer);
|
||
if (cacheKey) {
|
||
return void setBlobCache(cacheKey, u8, function () {
|
||
cb(null, u8);
|
||
});
|
||
}
|
||
cb(null, u8);
|
||
}
|
||
};
|
||
|
||
xhr.send(null);
|
||
};
|
||
|
||
if (!cacheKey) { return void fetch(); }
|
||
|
||
getBlobCache(cacheKey, function (err, u8) {
|
||
if (err || !u8) { return void fetch(); }
|
||
cb(null, u8);
|
||
});
|
||
|
||
};
|
||
|
||
// Decryption tools
|
||
var Decrypt = {
|
||
// Create a nonce
|
||
createNonce: function () {
|
||
var n = new Uint8Array(24);
|
||
for (var i = 0; i < 24; i++) { n[i] = 0; }
|
||
return n;
|
||
},
|
||
|
||
// Increment a nonce
|
||
increment: function (N) {
|
||
var l = N.length;
|
||
while (l-- > 1) {
|
||
/* .jshint probably suspects this is unsafe because we lack types
|
||
but as long as this is only used on nonces, it should be safe */
|
||
if (N[l] !== 255) { return void N[l]++; } // jshint ignore:line
|
||
|
||
// you don't need to worry about this running out.
|
||
// you'd need a REAAAALLY big file
|
||
if (l === 0) { throw new Error('E_NONCE_TOO_LARGE'); }
|
||
|
||
N[l] = 0;
|
||
}
|
||
},
|
||
|
||
decodePrefix: function (A) {
|
||
return (A[0] << 8) | A[1];
|
||
},
|
||
joinChunks: function (chunks) {
|
||
return new Blob(chunks);
|
||
},
|
||
|
||
// Convert a Uint8Array into Array.
|
||
slice: function (u8) {
|
||
return Array.prototype.slice.call(u8);
|
||
},
|
||
|
||
// Gets the key from the key string.
|
||
getKeyFromStr: function (str) {
|
||
return window.nacl.util.decodeBase64(str);
|
||
}
|
||
};
|
||
|
||
// The metadata size can go up to 65535 (16 bits - 2 bytes)
|
||
// The first 8 bits are stored in A[0]
|
||
// The last 8 bits are stored in A[0]
|
||
var uint8ArrayJoin = function (AA) {
|
||
var l = 0;
|
||
var i = 0;
|
||
for (; i < AA.length; i++) { l += AA[i].length; }
|
||
var C = new Uint8Array(l);
|
||
|
||
i = 0;
|
||
for (var offset = 0; i < AA.length; i++) {
|
||
C.set(AA[i], offset);
|
||
offset += AA[i].length;
|
||
}
|
||
return C;
|
||
};
|
||
var fetchMetadata = function (src, _cb) {
|
||
var cb = function (e, res) {
|
||
_cb(e, res);
|
||
cb = function () {};
|
||
};
|
||
|
||
var cacheKey = getCacheKey(src);
|
||
|
||
var fetch = function () {
|
||
var xhr = new XMLHttpRequest();
|
||
xhr.open('GET', src, true);
|
||
xhr.setRequestHeader('Range', 'bytes=0-1');
|
||
xhr.responseType = 'arraybuffer';
|
||
|
||
xhr.onerror = function () { return void cb("XHR_ERROR"); };
|
||
xhr.onload = function () {
|
||
// Error?
|
||
if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); }
|
||
var res = new Uint8Array(xhr.response);
|
||
var size = Decrypt.decodePrefix(res);
|
||
var xhr2 = new XMLHttpRequest();
|
||
|
||
xhr2.open("GET", src, true);
|
||
xhr2.setRequestHeader('Range', 'bytes=2-' + (size + 2));
|
||
xhr2.responseType = 'arraybuffer';
|
||
xhr2.onload = function () {
|
||
if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); }
|
||
var res2 = new Uint8Array(xhr2.response);
|
||
var all = uint8ArrayJoin([res, res2]);
|
||
cb(void 0, all);
|
||
};
|
||
xhr2.send(null);
|
||
};
|
||
|
||
xhr.send(null);
|
||
};
|
||
|
||
if (!cacheKey) { return void fetch(); }
|
||
|
||
getBlobCache(cacheKey, function (err, u8) {
|
||
if (err || !u8) { return void fetch(); }
|
||
var size = Decrypt.decodePrefix(u8.subarray(0,2));
|
||
cb(null, u8.subarray(0, size+2));
|
||
});
|
||
};
|
||
var decryptMetadata = function (u8, key) {
|
||
var prefix = u8.subarray(0, 2);
|
||
var metadataLength = Decrypt.decodePrefix(prefix);
|
||
|
||
var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength));
|
||
var metaChunk = window.nacl.secretbox.open(metaBox, Decrypt.createNonce(), key);
|
||
|
||
try {
|
||
return JSON.parse(window.nacl.util.encodeUTF8(metaChunk));
|
||
}
|
||
catch (e) { return null; }
|
||
};
|
||
var fetchDecryptedMetadata = function (src, key, cb) {
|
||
if (typeof(src) !== 'string') {
|
||
return window.setTimeout(function () {
|
||
cb('NO_SOURCE');
|
||
});
|
||
}
|
||
fetchMetadata(src, function (e, buffer) {
|
||
if (e) { return cb(e); }
|
||
if (typeof(key) === "string") { key = Decrypt.getKeyFromStr(key); }
|
||
cb(void 0, decryptMetadata(buffer, key));
|
||
});
|
||
};
|
||
|
||
// Decrypts a Uint8Array with the given key.
|
||
var decrypt = function (u8, strKey, done, progressCb) {
|
||
var Nacl = window.nacl;
|
||
|
||
var progress = function (offset) {
|
||
progressCb((offset / u8.length) * 100);
|
||
};
|
||
|
||
var key = Decrypt.getKeyFromStr(strKey);
|
||
var nonce = Decrypt.createNonce();
|
||
var i = 0;
|
||
var prefix = u8.subarray(0, 2);
|
||
var metadataLength = Decrypt.decodePrefix(prefix);
|
||
|
||
var res = { metadata: undefined };
|
||
|
||
// Get metadata
|
||
var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength));
|
||
var metaChunk = Nacl.secretbox.open(metaBox, nonce, key);
|
||
|
||
Decrypt.increment(nonce);
|
||
|
||
try { res.metadata = JSON.parse(Nacl.util.encodeUTF8(metaChunk)); }
|
||
catch (e) { return void done('E_METADATA_DECRYPTION'); }
|
||
|
||
if (!res.metadata) { return void done('NO_METADATA'); }
|
||
|
||
var takeChunk = function (cb) {
|
||
setTimeout(function () {
|
||
var start = i * cypherChunkLength + 2 + metadataLength;
|
||
var end = start + cypherChunkLength;
|
||
i++;
|
||
|
||
// Get the chunk
|
||
var box = new Uint8Array(u8.subarray(start, end));
|
||
|
||
// Decrypt the chunk
|
||
var plaintext = Nacl.secretbox.open(box, nonce, key);
|
||
Decrypt.increment(nonce);
|
||
|
||
if (!plaintext) { return void cb('DECRYPTION_FAILURE'); }
|
||
|
||
progress(Math.min(end, u8.length));
|
||
|
||
cb(void 0, plaintext);
|
||
});
|
||
};
|
||
|
||
var chunks = [];
|
||
|
||
// decrypt file contents
|
||
var again = function () {
|
||
takeChunk(function (e, plaintext) {
|
||
if (e) { return setTimeout(function () { done(e); }); }
|
||
|
||
if (plaintext) {
|
||
if ((i * cypherChunkLength + 2 + metadataLength) < u8.length) { // not done
|
||
chunks.push(plaintext);
|
||
return again();
|
||
}
|
||
|
||
chunks.push(plaintext);
|
||
res.content = Decrypt.joinChunks(chunks);
|
||
return void done(void 0, res);
|
||
}
|
||
done('UNEXPECTED_ENDING');
|
||
});
|
||
};
|
||
again();
|
||
};
|
||
|
||
// Get type
|
||
var getType = function (mediaObject, metadata, cfg) {
|
||
var mime = metadata.type;
|
||
var s = metadata.type.split('/');
|
||
var type = s[0];
|
||
var extension = s[1];
|
||
|
||
mediaObject.name = metadata.name;
|
||
if (mime && cfg.allowed.indexOf(mime) !== -1) {
|
||
mediaObject.type = type;
|
||
mediaObject.extension = extension;
|
||
mediaObject.mime = mime;
|
||
return type;
|
||
} else if (cfg.allowed.indexOf('download') !== -1) {
|
||
mediaObject.type = type;
|
||
mediaObject.extension = extension;
|
||
mediaObject.mime = mime;
|
||
return 'download';
|
||
} else {
|
||
return;
|
||
}
|
||
};
|
||
|
||
// Copy attributes
|
||
var copyAttributes = function (origin, dest) {
|
||
Object.keys(origin.attributes).forEach(function (i) {
|
||
if (!/^data-attr/.test(origin.attributes[i].name)) { return; }
|
||
var name = origin.attributes[i].name.slice(10);
|
||
var value = origin.attributes[i].value;
|
||
dest.setAttribute(name, value);
|
||
});
|
||
};
|
||
|
||
// Process
|
||
var process = function (mediaObject, decrypted, cfg, cb) {
|
||
var metadata = decrypted.metadata;
|
||
var blob = decrypted.content;
|
||
|
||
var mediaType = getType(mediaObject, metadata, cfg);
|
||
if (isplainTextFile(metadata)) {
|
||
mediaType = "text";
|
||
}
|
||
|
||
if (mediaType === 'application') {
|
||
mediaType = mediaObject.extension;
|
||
}
|
||
|
||
if (!mediaType || !cfg.Plugins[mediaType]) {
|
||
return void cb('NO_PLUGIN_FOUND');
|
||
}
|
||
|
||
// Get blob URL
|
||
var url = decrypted.url;
|
||
if (!url && window.URL) {
|
||
url = decrypted.url = window.URL.createObjectURL(new Blob([blob], {
|
||
type: metadata.type
|
||
}));
|
||
}
|
||
|
||
cfg.Plugins[mediaType](metadata, url, blob, cfg, function (err, el) {
|
||
if (err || !el) { return void cb(err || 'ERR_MEDIATAG_DISPLAY'); }
|
||
copyAttributes(mediaObject.tag, el);
|
||
mediaObject.tag.innerHTML = '';
|
||
mediaObject.tag.appendChild(el);
|
||
cb();
|
||
});
|
||
};
|
||
|
||
var addMissingConfig = function (base, target) {
|
||
Object.keys(target).forEach(function (k) {
|
||
if (!target[k]) { return; }
|
||
// Target is an object, fix it recursively
|
||
if (typeof target[k] === "object" && !Array.isArray(target[k])) {
|
||
// Sub-object
|
||
if (base[k] && (typeof base[k] !== "object" || Array.isArray(base[k]))) { return; }
|
||
else if (base[k]) { addMissingConfig(base[k], target[k]); }
|
||
else {
|
||
base[k] = {};
|
||
addMissingConfig(base[k], target[k]);
|
||
}
|
||
}
|
||
// Target is array or immutable, copy the value if it's missing
|
||
if (!base[k]) {
|
||
base[k] = Array.isArray(target[k]) ? JSON.parse(JSON.stringify(target[k]))
|
||
: target[k];
|
||
}
|
||
});
|
||
};
|
||
|
||
// Initialize a media-tag
|
||
var init = function (el, cfg) {
|
||
cfg = cfg || {};
|
||
|
||
addMissingConfig(cfg, config);
|
||
|
||
// Handle jQuery elements
|
||
if (typeof(el) === "object" && el.jQuery) { el = el[0]; }
|
||
|
||
// Abort smoothly if the element is not a media-tag
|
||
if (!el || el.nodeName !== "MEDIA-TAG") {
|
||
console.error("Not a media-tag!");
|
||
return {
|
||
on: function () { return this; }
|
||
};
|
||
}
|
||
|
||
var handlers = cfg.handlers || {
|
||
'progress': [],
|
||
'complete': [],
|
||
'metadata': [],
|
||
'error': []
|
||
};
|
||
|
||
var mediaObject = el._mediaObject = {
|
||
handlers: handlers,
|
||
tag: el
|
||
};
|
||
|
||
var emit = function (ev, data) {
|
||
// Check if the event name is valid
|
||
if (Object.keys(handlers).indexOf(ev) === -1) {
|
||
return void console.error("Invalid mediatag event");
|
||
}
|
||
|
||
// Call the handlers
|
||
handlers[ev].forEach(function (h) {
|
||
// Make sure a bad handler won't break the media-tag script
|
||
try {
|
||
h(data);
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
});
|
||
};
|
||
|
||
mediaObject.on = function (ev, handler) {
|
||
// Check if the event name is valid
|
||
if (Object.keys(handlers).indexOf(ev) === -1) {
|
||
console.error("Invalid mediatag event");
|
||
return mediaObject;
|
||
}
|
||
// Check if the handler is valid
|
||
if (typeof (handler) !== "function") {
|
||
console.error("Handler is not a function!");
|
||
return mediaObject;
|
||
}
|
||
// Add the handler
|
||
handlers[ev].push(handler);
|
||
return mediaObject;
|
||
};
|
||
|
||
var src = el.getAttribute('src');
|
||
var strKey = el.getAttribute('data-crypto-key');
|
||
if (/^cryptpad:/.test(strKey)) {
|
||
strKey = strKey.slice(9);
|
||
}
|
||
var uid = [src, strKey].join('');
|
||
|
||
// End media-tag rendering: display the tag and emit the event
|
||
var end = function (decrypted) {
|
||
mediaObject.complete = true;
|
||
process(mediaObject, decrypted, cfg, function (err) {
|
||
if (err) { return void emit('error', err); }
|
||
mediaObject._blob = decrypted;
|
||
emit('complete', decrypted);
|
||
});
|
||
};
|
||
|
||
var error = function (err) {
|
||
mediaObject.tag.innerHTML = '<img style="width: 100px; height: 100px;" src="/images/broken.png">';
|
||
emit('error', err);
|
||
};
|
||
|
||
var getCache = function () {
|
||
var c = cache[uid];
|
||
if (!c || !c.promise || !c.mt) { return; }
|
||
return c;
|
||
};
|
||
|
||
var dl = function () {
|
||
// Download the encrypted blob
|
||
cache[uid] = getCache() || {
|
||
promise: new Promise(function (resolve, reject) {
|
||
download(src, function (err, u8Encrypted) {
|
||
if (err) {
|
||
return void reject(err);
|
||
}
|
||
// Decrypt the blob
|
||
decrypt(u8Encrypted, strKey, function (errDecryption, u8Decrypted) {
|
||
if (errDecryption) {
|
||
return void reject(errDecryption);
|
||
}
|
||
emit('metadata', u8Decrypted.metadata);
|
||
resolve(u8Decrypted);
|
||
}, function (progress) {
|
||
emit('progress', {
|
||
progress: 50+0.5*progress
|
||
});
|
||
});
|
||
}, function (progress) {
|
||
emit('progress', {
|
||
progress: 0.5*progress
|
||
});
|
||
});
|
||
}),
|
||
mt: mediaObject
|
||
};
|
||
if (cache[uid].mt !== mediaObject) {
|
||
// Add progress for other instances of this tag
|
||
cache[uid].mt.on('progress', function (obj) {
|
||
if (!mediaObject.bar && !cfg.force) { makeProgressBar(cfg, mediaObject); }
|
||
emit('progress', {
|
||
progress: obj.progress
|
||
});
|
||
});
|
||
}
|
||
cache[uid].promise.then(function (u8) {
|
||
end(u8);
|
||
}, function (err) {
|
||
error(err);
|
||
});
|
||
};
|
||
|
||
if (cfg.force) { dl(); return mediaObject; }
|
||
|
||
var maxSize = typeof(config.maxDownloadSize) === "number" ? config.maxDownloadSize
|
||
: (5 * 1024 * 1024);
|
||
fetchDecryptedMetadata(src, strKey, function (err, md) {
|
||
if (err) { return void error(err); }
|
||
cfg.metadata = md;
|
||
emit('metadata', md);
|
||
getFileSize(src, function (err, size) {
|
||
// If the size is smaller than the autodownload limit, load the blob.
|
||
// If the blob is already loaded or being loaded, don't show the button.
|
||
if (!size || size < maxSize || getCache()) {
|
||
makeProgressBar(cfg, mediaObject);
|
||
return void dl();
|
||
}
|
||
var sizeMb = Math.round(10 * size / 1024 / 1024) / 10;
|
||
makeDownloadButton(cfg, mediaObject, sizeMb, dl);
|
||
});
|
||
});
|
||
|
||
return mediaObject;
|
||
};
|
||
|
||
// Add the cache as a property of MediaTag
|
||
cache = init.__Cryptpad_Cache = {};
|
||
|
||
init.setDefaultConfig = function (key, value) {
|
||
config[key] = value;
|
||
};
|
||
|
||
init.fetchDecryptedMetadata = fetchDecryptedMetadata;
|
||
|
||
return init;
|
||
};
|
||
|
||
if (typeof(module) !== 'undefined' && module.exports) {
|
||
module.exports = factory();
|
||
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
|
||
define([
|
||
'/bower_components/es6-promise/es6-promise.min.js'
|
||
], function () {
|
||
return factory();
|
||
});
|
||
} else {
|
||
// unsupported initialization
|
||
}
|
||
}(typeof(window) !== 'undefined'? window : {}));
|