sandbox.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import * as fflate from '../../..';
  2. import toNativeStream from './stream-adapter';
  3. type Callback = (...args: unknown[]) => void;
  4. type WorkerProxy = Record<string, Callback>;
  5. const concat = (chunks: Uint8Array[]) => {
  6. const out = new Uint8Array(
  7. chunks.reduce((a, v) => v.length + a, 0)
  8. );
  9. let loc = 0;
  10. for (const chunk of chunks) {
  11. out.set(chunk, loc);
  12. loc += chunk.length;
  13. }
  14. return out;
  15. }
  16. const createWorkerProxy = (lib: string, keys: string[]): WorkerProxy => {
  17. const p: WorkerProxy = {};
  18. for (const k of keys) {
  19. const base = function(cb: (...args: unknown[]) => void) {
  20. const w = new Worker('../../util/workers.ts');
  21. w.postMessage([lib, k]);
  22. w.onmessage = function(msg) {
  23. const args = msg.data;
  24. args.unshift(null);
  25. cb.apply(null, args);
  26. }
  27. w.onerror = err => cb(err);
  28. return w;
  29. }
  30. if (k != 'zip' && k != 'unzip') {
  31. p[k] = function(dat, cb) {
  32. const chks: unknown[] = [];
  33. const w = base((err, dat, final) => {
  34. if (err) (cb as Callback)(err);
  35. else {
  36. if (final) {
  37. if (!chks.length) (cb as Callback)(null, dat);
  38. else (cb as Callback)(null, concat(chks as Uint8Array[]));
  39. } else chks.push(dat);
  40. }
  41. });
  42. w.postMessage([dat, true], [(dat as Uint8Array).buffer]);
  43. }
  44. p['create' + k.slice(0, 1).toUpperCase() + k.slice(1)] = function() {
  45. let trueCb = arguments[0];
  46. const w = base((err, dat, final) => {
  47. trueCb(err, dat, final);
  48. });
  49. const out = {
  50. ondata: trueCb,
  51. push(v: Uint8Array, f: boolean) {
  52. if (!out.ondata) throw 'no callback';
  53. trueCb = out.ondata;
  54. w.postMessage([v, f], [v.buffer]);
  55. },
  56. terminate() {
  57. w.terminate();
  58. }
  59. }
  60. return out;
  61. }
  62. } else {
  63. p[k] = function() {
  64. let trueCb = arguments[0];
  65. const w = base((err, dat) => {
  66. trueCb(err, dat);
  67. });
  68. const out = {
  69. ondata: trueCb,
  70. add(name: string, buf: Uint8Array) {
  71. buf = new Uint8Array(buf);
  72. w.postMessage([name, buf], [buf.buffer]);
  73. },
  74. end() {
  75. if (!out.ondata) throw 'no callback';
  76. trueCb = out.ondata;
  77. w.postMessage(null);
  78. }
  79. }
  80. return out;
  81. }
  82. }
  83. }
  84. return p;
  85. }
  86. const keys = ['zip', 'unzip', 'deflate', 'inflate', 'gzip', 'gunzip', 'zlib', 'unzlib'];
  87. const uzipWorker = createWorkerProxy('uzip', keys);
  88. const pakoWorker = createWorkerProxy('pako', keys);
  89. const fileToU8 = (file: File, cb: (out: Uint8Array) => void) => {
  90. const fr = new FileReader();
  91. fr.onloadend = () => {
  92. cb(new Uint8Array(fr.result as ArrayBuffer));
  93. }
  94. fr.readAsArrayBuffer(file);
  95. };
  96. const download = (file: BlobPart, name?: string) => {
  97. const url = URL.createObjectURL(new Blob([file]));
  98. const dl = document.createElement('a');
  99. dl.download = name || ('fflate-demo-' + Date.now() + '.dat');
  100. dl.href = url;
  101. dl.click();
  102. URL.revokeObjectURL(url);
  103. }
  104. const bts = ['B', ' kB', ' MB', ' GB'];
  105. const hrbt = (bt: number) => {
  106. let i = 0;
  107. for (; bt > 1023; ++i) bt /= 1024;
  108. return bt.toFixed((i != 0) as unknown as number) + bts[i];
  109. }
  110. const prettySizes = (files: Record<string, [number, number]>) => {
  111. let out = '\n\n';
  112. let tot = 0;
  113. let totc = 0;
  114. let cnt = 0;
  115. for (const k in files) {
  116. ++cnt;
  117. out += '<span style="font-weight:bold">' + k + '</span> compressed from <span style="font-weight:bold;color:red">' + hrbt(files[k][1]) + '</span> to <span style="font-weight:bold;color:green">' + hrbt(files[k][0]) + '</span>\n';
  118. totc += files[k][0];
  119. tot += files[k][1];
  120. }
  121. return out + (cnt > 1 ? '\n\n<span style="font-weight:bold">In total, all files originally <span style="font-style:italic;color:red">' + hrbt(tot) + '</span>, compressed to <span style="font-style:italic;color:green">' + hrbt(totc) + '</span></span>' : '');
  122. }
  123. const exec = (code: string, files: File[], callback: Callback) => {
  124. const scope = {
  125. fflate,
  126. uzipWorker,
  127. pakoWorker,
  128. toNativeStream,
  129. callback,
  130. fileToU8,
  131. files,
  132. download,
  133. prettySizes
  134. };
  135. try {
  136. new Function('"use strict";' + Object.keys(scope).map(k => 'var ' + k + ' = this["' + k + '"];').join('') + code).call(scope);
  137. } catch(e) {
  138. callback(e);
  139. }
  140. }
  141. export default exec;