2017-06-28 18:30:14 +00:00
|
|
|
function arrayToHex(iv) {
|
2017-06-02 03:59:27 +00:00
|
|
|
let hexStr = '';
|
2017-07-27 15:03:54 +00:00
|
|
|
// eslint-disable-next-line prefer-const
|
2017-07-27 14:46:28 +00:00
|
|
|
for (let i in iv) {
|
2017-06-02 03:59:27 +00:00
|
|
|
if (iv[i] < 16) {
|
|
|
|
hexStr += '0' + iv[i].toString(16);
|
|
|
|
} else {
|
|
|
|
hexStr += iv[i].toString(16);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return hexStr;
|
|
|
|
}
|
|
|
|
|
2017-06-28 18:30:14 +00:00
|
|
|
function hexToArray(str) {
|
|
|
|
const iv = new Uint8Array(str.length / 2);
|
2017-06-02 03:59:27 +00:00
|
|
|
for (let i = 0; i < str.length; i += 2) {
|
|
|
|
iv[i / 2] = parseInt(str.charAt(i) + str.charAt(i + 1), 16);
|
|
|
|
}
|
|
|
|
|
|
|
|
return iv;
|
|
|
|
}
|
|
|
|
|
2017-06-21 20:23:36 +00:00
|
|
|
function notify(str) {
|
2017-07-26 18:24:58 +00:00
|
|
|
return str;
|
|
|
|
/* TODO: enable once we have an opt-in ui element
|
2017-06-20 19:18:14 +00:00
|
|
|
if (!('Notification' in window)) {
|
2017-06-21 20:23:36 +00:00
|
|
|
return;
|
|
|
|
} else if (Notification.permission === 'granted') {
|
2017-06-21 21:31:21 +00:00
|
|
|
new Notification(str);
|
2017-06-21 20:23:36 +00:00
|
|
|
} else if (Notification.permission !== 'denied') {
|
|
|
|
Notification.requestPermission(function(permission) {
|
2017-06-21 21:31:21 +00:00
|
|
|
if (permission === 'granted') new Notification(str);
|
|
|
|
});
|
2017-06-21 20:23:36 +00:00
|
|
|
}
|
2017-07-26 18:24:58 +00:00
|
|
|
*/
|
2017-06-21 20:23:36 +00:00
|
|
|
}
|
|
|
|
|
2017-07-10 20:27:01 +00:00
|
|
|
function gcmCompliant() {
|
|
|
|
try {
|
2017-07-12 17:53:29 +00:00
|
|
|
return window.crypto.subtle
|
|
|
|
.generateKey(
|
2017-07-10 20:27:01 +00:00
|
|
|
{
|
|
|
|
name: 'AES-GCM',
|
2017-07-12 17:53:29 +00:00
|
|
|
length: 128
|
2017-07-10 20:27:01 +00:00
|
|
|
},
|
2017-07-12 17:53:29 +00:00
|
|
|
true,
|
|
|
|
['encrypt', 'decrypt']
|
2017-07-10 20:27:01 +00:00
|
|
|
)
|
2017-07-12 17:53:29 +00:00
|
|
|
.then(key => {
|
|
|
|
return window.crypto.subtle
|
|
|
|
.encrypt(
|
|
|
|
{
|
|
|
|
name: 'AES-GCM',
|
|
|
|
iv: window.crypto.getRandomValues(new Uint8Array(12)),
|
|
|
|
additionalData: window.crypto.getRandomValues(new Uint8Array(6)),
|
|
|
|
tagLength: 128
|
|
|
|
},
|
|
|
|
key,
|
|
|
|
new ArrayBuffer(8)
|
|
|
|
)
|
|
|
|
.then(() => {
|
|
|
|
return Promise.resolve();
|
|
|
|
});
|
2017-07-10 20:27:01 +00:00
|
|
|
})
|
|
|
|
.catch(err => {
|
2017-08-03 21:07:22 +00:00
|
|
|
return loadShim();
|
2017-07-12 17:53:29 +00:00
|
|
|
});
|
|
|
|
} catch (err) {
|
2017-08-03 21:07:22 +00:00
|
|
|
return loadShim();
|
|
|
|
}
|
|
|
|
function loadShim() {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const shim = document.createElement('script');
|
|
|
|
shim.src = '/cryptofill.js';
|
|
|
|
shim.addEventListener('load', resolve);
|
|
|
|
shim.addEventListener('error', reject);
|
|
|
|
document.head.appendChild(shim);
|
|
|
|
});
|
2017-07-10 20:27:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-20 23:06:06 +00:00
|
|
|
function isFile(id) {
|
2017-08-02 18:17:23 +00:00
|
|
|
return /^[0-9a-fA-F]{10}$/.test(id);
|
2017-07-20 23:06:06 +00:00
|
|
|
}
|
|
|
|
|
2017-08-04 03:13:17 +00:00
|
|
|
function copyToClipboard(str) {
|
|
|
|
const aux = document.createElement('input');
|
|
|
|
aux.setAttribute('value', str);
|
|
|
|
aux.contentEditable = true;
|
|
|
|
aux.readOnly = true;
|
|
|
|
document.body.appendChild(aux);
|
|
|
|
if (navigator.userAgent.match(/iphone|ipad|ipod/i)) {
|
|
|
|
const range = document.createRange();
|
|
|
|
range.selectNodeContents(aux);
|
|
|
|
const sel = window.getSelection();
|
|
|
|
sel.removeAllRanges();
|
|
|
|
sel.addRange(range);
|
|
|
|
aux.setSelectionRange(0, str.length);
|
2017-08-04 21:44:11 +00:00
|
|
|
} else {
|
2017-08-04 03:13:17 +00:00
|
|
|
aux.select();
|
|
|
|
}
|
2017-08-04 21:44:11 +00:00
|
|
|
const result = document.execCommand('copy');
|
2017-08-04 03:13:17 +00:00
|
|
|
document.body.removeChild(aux);
|
2017-08-04 21:44:11 +00:00
|
|
|
return result;
|
2017-08-04 03:13:17 +00:00
|
|
|
}
|
|
|
|
|
2017-08-06 01:06:43 +00:00
|
|
|
const LOCALIZE_NUMBERS = !!(
|
|
|
|
typeof Intl === 'object' &&
|
|
|
|
Intl &&
|
|
|
|
typeof Intl.NumberFormat === 'function'
|
|
|
|
);
|
|
|
|
|
|
|
|
const UNITS = ['B', 'kB', 'MB', 'GB'];
|
|
|
|
function bytes(num) {
|
|
|
|
const exponent = Math.min(Math.floor(Math.log10(num) / 3), UNITS.length - 1);
|
|
|
|
const n = Number(num / Math.pow(1000, exponent));
|
|
|
|
const nStr = LOCALIZE_NUMBERS
|
|
|
|
? n.toLocaleString(navigator.languages, {
|
|
|
|
minimumFractionDigits: 1,
|
|
|
|
maximumFractionDigits: 1
|
|
|
|
})
|
|
|
|
: n.toFixed(1);
|
|
|
|
return `${nStr}${UNITS[exponent]}`;
|
|
|
|
}
|
|
|
|
|
2017-08-09 02:46:15 +00:00
|
|
|
function percent(ratio) {
|
|
|
|
return LOCALIZE_NUMBERS
|
|
|
|
? ratio.toLocaleString(navigator.languages, { style: 'percent' })
|
|
|
|
: `${Math.floor(ratio * 100)}%`;
|
|
|
|
}
|
|
|
|
|
2017-07-20 23:06:06 +00:00
|
|
|
const ONE_DAY_IN_MS = 86400000;
|
|
|
|
|
2017-06-02 03:59:27 +00:00
|
|
|
module.exports = {
|
2017-08-06 01:06:43 +00:00
|
|
|
bytes,
|
2017-08-09 02:46:15 +00:00
|
|
|
percent,
|
2017-08-04 03:13:17 +00:00
|
|
|
copyToClipboard,
|
2017-06-28 18:30:14 +00:00
|
|
|
arrayToHex,
|
|
|
|
hexToArray,
|
2017-07-10 20:27:01 +00:00
|
|
|
notify,
|
2017-07-20 22:16:00 +00:00
|
|
|
gcmCompliant,
|
2017-07-20 23:06:06 +00:00
|
|
|
isFile,
|
|
|
|
ONE_DAY_IN_MS
|
2017-06-02 03:59:27 +00:00
|
|
|
};
|