utils.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. /* global Android */
  2. let html;
  3. try {
  4. html = require('choo/html');
  5. } catch (e) {
  6. // running in the service worker
  7. }
  8. const b64 = require('base64-js');
  9. function arrayToB64(array) {
  10. return b64
  11. .fromByteArray(array)
  12. .replace(/\+/g, '-')
  13. .replace(/\//g, '_')
  14. .replace(/=/g, '');
  15. }
  16. function b64ToArray(str) {
  17. return b64.toByteArray(str + '==='.slice((str.length + 3) % 4));
  18. }
  19. function locale() {
  20. return document.querySelector('html').lang;
  21. }
  22. function loadShim(polyfill) {
  23. return new Promise((resolve, reject) => {
  24. const shim = document.createElement('script');
  25. shim.src = polyfill;
  26. shim.addEventListener('load', () => resolve(true));
  27. shim.addEventListener('error', () => resolve(false));
  28. document.head.appendChild(shim);
  29. });
  30. }
  31. function isFile(id) {
  32. return /^[0-9a-fA-F]{10,16}$/.test(id);
  33. }
  34. function copyToClipboard(str) {
  35. const aux = document.createElement('input');
  36. aux.setAttribute('value', str);
  37. aux.contentEditable = true;
  38. aux.readOnly = true;
  39. document.body.appendChild(aux);
  40. if (navigator.userAgent.match(/iphone|ipad|ipod/i)) {
  41. const range = document.createRange();
  42. range.selectNodeContents(aux);
  43. const sel = getSelection();
  44. sel.removeAllRanges();
  45. sel.addRange(range);
  46. aux.setSelectionRange(0, str.length);
  47. } else {
  48. aux.select();
  49. }
  50. const result = document.execCommand('copy');
  51. document.body.removeChild(aux);
  52. return result;
  53. }
  54. const LOCALIZE_NUMBERS = !!(
  55. typeof Intl === 'object' &&
  56. Intl &&
  57. typeof Intl.NumberFormat === 'function' &&
  58. typeof navigator === 'object'
  59. );
  60. const UNITS = ['bytes', 'kb', 'mb', 'gb'];
  61. function bytes(num) {
  62. if (num < 1) {
  63. return '0B';
  64. }
  65. const exponent = Math.min(Math.floor(Math.log10(num) / 3), UNITS.length - 1);
  66. const n = Number(num / Math.pow(1024, exponent));
  67. const decimalDigits = Math.floor(n) === n ? 0 : 1;
  68. let nStr = n.toFixed(decimalDigits);
  69. if (LOCALIZE_NUMBERS) {
  70. try {
  71. nStr = n.toLocaleString(locale(), {
  72. minimumFractionDigits: decimalDigits,
  73. maximumFractionDigits: decimalDigits
  74. });
  75. } catch (e) {
  76. // fall through
  77. }
  78. }
  79. return translate('fileSize', {
  80. num: nStr,
  81. units: translate(UNITS[exponent])
  82. });
  83. }
  84. function percent(ratio) {
  85. if (LOCALIZE_NUMBERS) {
  86. try {
  87. return ratio.toLocaleString(locale(), { style: 'percent' });
  88. } catch (e) {
  89. // fall through
  90. }
  91. }
  92. return `${Math.floor(ratio * 100)}%`;
  93. }
  94. function number(n) {
  95. if (LOCALIZE_NUMBERS) {
  96. return n.toLocaleString(locale());
  97. }
  98. return n.toString();
  99. }
  100. function allowedCopy() {
  101. const support = !!document.queryCommandSupported;
  102. return support ? document.queryCommandSupported('copy') : false;
  103. }
  104. function delay(delay = 100) {
  105. return new Promise(resolve => setTimeout(resolve, delay));
  106. }
  107. function fadeOut(selector) {
  108. const classes = document.querySelector(selector).classList;
  109. classes.remove('effect--fadeIn');
  110. classes.add('effect--fadeOut');
  111. return delay(300);
  112. }
  113. function openLinksInNewTab(links, should = true) {
  114. links = links || Array.from(document.querySelectorAll('a:not([target])'));
  115. if (should) {
  116. links.forEach(l => {
  117. l.setAttribute('target', '_blank');
  118. l.setAttribute('rel', 'noopener noreferrer');
  119. });
  120. } else {
  121. links.forEach(l => {
  122. l.removeAttribute('target');
  123. l.removeAttribute('rel');
  124. });
  125. }
  126. return links;
  127. }
  128. function browserName() {
  129. try {
  130. // order of these matters
  131. if (/firefox/i.test(navigator.userAgent)) {
  132. return 'firefox';
  133. }
  134. if (/edge/i.test(navigator.userAgent)) {
  135. return 'edge';
  136. }
  137. if (/edg/i.test(navigator.userAgent)) {
  138. return 'edgium';
  139. }
  140. if (/trident/i.test(navigator.userAgent)) {
  141. return 'ie';
  142. }
  143. if (/chrome/i.test(navigator.userAgent)) {
  144. return 'chrome';
  145. }
  146. if (/safari/i.test(navigator.userAgent)) {
  147. return 'safari';
  148. }
  149. if (/send android/i.test(navigator.userAgent)) {
  150. return 'android-app';
  151. }
  152. return 'other';
  153. } catch (e) {
  154. return 'unknown';
  155. }
  156. }
  157. async function streamToArrayBuffer(stream, size) {
  158. const reader = stream.getReader();
  159. let state = await reader.read();
  160. if (size) {
  161. const result = new Uint8Array(size);
  162. let offset = 0;
  163. while (!state.done) {
  164. result.set(state.value, offset);
  165. offset += state.value.length;
  166. state = await reader.read();
  167. }
  168. return result.buffer;
  169. }
  170. const parts = [];
  171. let len = 0;
  172. while (!state.done) {
  173. parts.push(state.value);
  174. len += state.value.length;
  175. state = await reader.read();
  176. }
  177. let offset = 0;
  178. const result = new Uint8Array(len);
  179. for (const part of parts) {
  180. result.set(part, offset);
  181. offset += part.length;
  182. }
  183. return result.buffer;
  184. }
  185. function list(items, ulStyle = '', liStyle = '') {
  186. const lis = items.map(
  187. i =>
  188. html`
  189. <li class="${liStyle}">${i}</li>
  190. `
  191. );
  192. return html`
  193. <ul class="${ulStyle}">
  194. ${lis}
  195. </ul>
  196. `;
  197. }
  198. function secondsToL10nId(seconds) {
  199. if (seconds < 3600) {
  200. return { id: 'timespanMinutes', num: Math.floor(seconds / 60) };
  201. } else if (seconds < 86400) {
  202. return { id: 'timespanHours', num: Math.floor(seconds / 3600) };
  203. } else {
  204. return { id: 'timespanDays', num: Math.floor(seconds / 86400) };
  205. }
  206. }
  207. function timeLeft(milliseconds) {
  208. if (milliseconds < 1) {
  209. return { id: 'linkExpiredAlt' };
  210. }
  211. const minutes = Math.floor(milliseconds / 1000 / 60);
  212. const hours = Math.floor(minutes / 60);
  213. const days = Math.floor(hours / 24);
  214. if (days >= 1) {
  215. return {
  216. id: 'expiresDaysHoursMinutes',
  217. days,
  218. hours: hours % 24,
  219. minutes: minutes % 60
  220. };
  221. }
  222. if (hours >= 1) {
  223. return {
  224. id: 'expiresHoursMinutes',
  225. hours,
  226. minutes: minutes % 60
  227. };
  228. } else if (hours === 0) {
  229. if (minutes === 0) {
  230. return { id: 'expiresMinutes', minutes: '< 1' };
  231. }
  232. return { id: 'expiresMinutes', minutes };
  233. }
  234. return null;
  235. }
  236. function platform() {
  237. if (typeof Android === 'object') {
  238. return 'android';
  239. }
  240. return 'web';
  241. }
  242. const ECE_RECORD_SIZE = 1024 * 64;
  243. const TAG_LENGTH = 16;
  244. function encryptedSize(size, rs = ECE_RECORD_SIZE, tagLength = TAG_LENGTH) {
  245. const chunk_meta = tagLength + 1; // Chunk metadata, tag and delimiter
  246. return 21 + size + chunk_meta * Math.ceil(size / (rs - chunk_meta));
  247. }
  248. let translate = function() {
  249. throw new Error('uninitialized translate function. call setTranslate first');
  250. };
  251. function setTranslate(t) {
  252. translate = t;
  253. }
  254. module.exports = {
  255. locale,
  256. fadeOut,
  257. delay,
  258. allowedCopy,
  259. bytes,
  260. percent,
  261. number,
  262. copyToClipboard,
  263. arrayToB64,
  264. b64ToArray,
  265. loadShim,
  266. isFile,
  267. openLinksInNewTab,
  268. browserName,
  269. streamToArrayBuffer,
  270. list,
  271. secondsToL10nId,
  272. timeLeft,
  273. platform,
  274. encryptedSize,
  275. setTranslate
  276. };