Merge branch 'master' into dependabot/npm_and_yarn/next-11.1.0
This commit is contained in:
commit
238823a8bf
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -45,3 +45,4 @@ public/sw.js
|
|||
public/sw.js.map
|
||||
public/worker-*.js
|
||||
public/worker-*.js.map
|
||||
|
||||
|
|
12
README.md
12
README.md
|
@ -1,6 +1,12 @@
|
|||
Web application for [ente](https://ente.io) built with lots of ❤️ and a little bit of JavaScript.
|
||||
|
||||
## Getting Started
|
||||
You can read more about ente's encryption protocol here: https://ente.io/architecture.
|
||||
|
||||
For more information about the project please visit: https://ente.io.
|
||||
|
||||
---
|
||||
|
||||
## Building
|
||||
|
||||
First, pull and install dependencies
|
||||
```bash
|
||||
|
@ -17,3 +23,7 @@ yarn dev
|
|||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
---
|
||||
|
||||
If there's something you need help with, please write to [support@ente.io](mailto:support@ente.io), we'd be happy to help!
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
[build]
|
||||
command = "npm run build"
|
||||
publish = "out_publish"
|
||||
publish = "out"
|
||||
|
||||
[[headers]]
|
||||
for = "/*"
|
||||
[headers.values]
|
||||
X-Frame-Options = "DENY"
|
||||
X-XSS-Protection = "1; mode=block"
|
||||
Cross-Origin-Opener-Policy = "same-origin"
|
||||
Cross-Origin-Embedder-Policy = "require-corp"
|
|
@ -11,6 +11,18 @@ const gitSha = cp.execSync('git rev-parse --short HEAD', {
|
|||
encoding: 'utf8',
|
||||
});
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
const COOP_COEP_Headers = [
|
||||
{
|
||||
key: 'Cross-Origin-Opener-Policy',
|
||||
value: 'same-origin',
|
||||
},
|
||||
{
|
||||
key: 'Cross-Origin-Embedder-Policy',
|
||||
value: 'require-corp',
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = withSentryConfig(
|
||||
withWorkbox(
|
||||
withBundleAnalyzer({
|
||||
|
@ -26,6 +38,16 @@ module.exports = withSentryConfig(
|
|||
'static/webpack/[fullhash].[runtime].hot-update.json';
|
||||
return config;
|
||||
},
|
||||
|
||||
headers() {
|
||||
return [
|
||||
{
|
||||
// Apply these headers to all routes in your application....
|
||||
source: '/(.*)',
|
||||
headers: COOP_COEP_Headers,
|
||||
},
|
||||
];
|
||||
},
|
||||
})
|
||||
),
|
||||
{ release: gitSha }
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@ente-io/next-with-workbox": "^1.0.3",
|
||||
"@ffmpeg/core": "^0.10.0",
|
||||
"@ffmpeg/ffmpeg": "^0.10.1",
|
||||
"@sentry/nextjs": "^6.7.1",
|
||||
"@stripe/stripe-js": "^1.13.2",
|
||||
"@typescript-eslint/eslint-plugin": "^4.25.0",
|
||||
|
@ -27,6 +29,7 @@
|
|||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"exif-js": "^2.3.0",
|
||||
"file-type": "^16.5.3",
|
||||
"formik": "^2.1.5",
|
||||
"heic2any": "^0.0.3",
|
||||
"http-proxy-middleware": "^1.0.5",
|
||||
|
@ -40,6 +43,7 @@
|
|||
"react": "^17.0.2",
|
||||
"react-bootstrap": "^1.3.0",
|
||||
"react-burger-menu": "^3.0.4",
|
||||
"react-collapse": "^5.1.0",
|
||||
"react-dom": "16.13.1",
|
||||
"react-dropzone": "^11.2.4",
|
||||
"react-otp-input": "^2.3.1",
|
||||
|
@ -65,6 +69,7 @@
|
|||
"@types/node": "^14.6.4",
|
||||
"@types/photoswipe": "^4.1.1",
|
||||
"@types/react": "^16.9.49",
|
||||
"@types/react-collapse": "^5.0.1",
|
||||
"@types/react-select": "^4.0.15",
|
||||
"@types/react-window": "^1.8.2",
|
||||
"@types/react-window-infinite-loader": "^1.0.3",
|
||||
|
|
27
public/images/black-thumbnail-b64.ts
Normal file
27
public/images/black-thumbnail-b64.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
export const BLACK_THUMBNAIL_BASE64 = `/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBAQE
|
||||
BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQ
|
||||
EBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARC
|
||||
ACWASwDAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUF
|
||||
BAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk
|
||||
6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztL
|
||||
W2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAA
|
||||
AAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVY
|
||||
nLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImK
|
||||
kpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oAD
|
||||
AMBAAIRAxEAPwD/AD/6ACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA
|
||||
CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg
|
||||
AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKAC
|
||||
gAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo
|
||||
AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg
|
||||
AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg
|
||||
AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA
|
||||
CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA
|
||||
CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoA
|
||||
KACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg
|
||||
AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo
|
||||
AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA
|
||||
CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAK
|
||||
ACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoA
|
||||
KACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo
|
||||
AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo
|
||||
AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgD/9k=`;
|
BIN
public/images/black-thumbnail.jpeg
Normal file
BIN
public/images/black-thumbnail.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
253
public/js/ffmpeg/ffmpeg-core.js
Normal file
253
public/js/ffmpeg/ffmpeg-core.js
Normal file
|
@ -0,0 +1,253 @@
|
|||
|
||||
var createFFmpegCore = (function() {
|
||||
var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
|
||||
if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename;
|
||||
return (
|
||||
function(createFFmpegCore) {
|
||||
createFFmpegCore = createFFmpegCore || {};
|
||||
|
||||
|
||||
var f;f||(f=typeof createFFmpegCore !== 'undefined' ? createFFmpegCore : {});var ba,ca;f.ready=new Promise(function(a,b){ba=a;ca=b});var da={},ea;for(ea in f)f.hasOwnProperty(ea)&&(da[ea]=f[ea]);var fa=[],ha="./this.program";function ja(a,b){throw b;}var ka=!1,la=!1,h=!1,ma=!1;ka="object"===typeof window;la="function"===typeof importScripts;h="object"===typeof process&&"object"===typeof process.versions&&"string"===typeof process.versions.node;ma=!ka&&!h&&!la;var l=f.ENVIRONMENT_IS_PTHREAD||!1;
|
||||
l&&(oa=f.buffer);var pa="";function qa(a){return f.locateFile?f.locateFile(a,pa):pa+a}var ra,sa,ta,va;
|
||||
if(h){pa=la?require("path").dirname(pa)+"/":__dirname+"/";ra=function(a,b){ta||(ta=require("fs"));va||(va=require("path"));a=va.normalize(a);return ta.readFileSync(a,b?null:"utf8")};sa=function(a){a=ra(a,!0);a.buffer||(a=new Uint8Array(a));assert(a.buffer);return a};1<process.argv.length&&(ha=process.argv[1].replace(/\\/g,"/"));fa=process.argv.slice(2);process.on("uncaughtException",function(a){if(!(a instanceof wa))throw a;});process.on("unhandledRejection",n);ja=function(a){process.exit(a)};f.inspect=
|
||||
function(){return"[Emscripten Module object]"};var xa;try{xa=require("worker_threads")}catch(a){throw console.error('The "worker_threads" module is not supported in this node.js build - perhaps a newer version is needed?'),a;}global.Worker=xa.Worker}else if(ma)"undefined"!=typeof read&&(ra=function(a){return read(a)}),sa=function(a){if("function"===typeof readbuffer)return new Uint8Array(readbuffer(a));a=read(a,"binary");assert("object"===typeof a);return a},"undefined"!=typeof scriptArgs?fa=scriptArgs:
|
||||
"undefined"!=typeof arguments&&(fa=arguments),"function"===typeof quit&&(ja=function(a){quit(a)}),"undefined"!==typeof print&&("undefined"===typeof console&&(console={}),console.log=print,console.warn=console.error="undefined"!==typeof printErr?printErr:print);else if(ka||la)la?pa=self.location.href:"undefined"!==typeof document&&document.currentScript&&(pa=document.currentScript.src),_scriptDir&&(pa=_scriptDir),0!==pa.indexOf("blob:")?pa=pa.substr(0,pa.lastIndexOf("/")+1):pa="",h?(ra=function(a,
|
||||
b){ta||(ta=require("fs"));va||(va=require("path"));a=va.normalize(a);return ta.readFileSync(a,b?null:"utf8")},sa=function(a){a=ra(a,!0);a.buffer||(a=new Uint8Array(a));assert(a.buffer);return a}):(ra=function(a){var b=new XMLHttpRequest;b.open("GET",a,!1);b.send(null);return b.responseText},la&&(sa=function(a){var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)}));h&&"undefined"===typeof performance&&(global.performance=require("perf_hooks").performance);
|
||||
var ya=f.print||console.log.bind(console),u=f.printErr||console.warn.bind(console);for(ea in da)da.hasOwnProperty(ea)&&(f[ea]=da[ea]);da=null;f.arguments&&(fa=f.arguments);f.thisProgram&&(ha=f.thisProgram);f.quit&&(ja=f.quit);var za,Aa=0,Ba;f.wasmBinary&&(Ba=f.wasmBinary);var noExitRuntime;f.noExitRuntime&&(noExitRuntime=f.noExitRuntime);"object"!==typeof WebAssembly&&n("no native wasm support detected");var Ca,Da,threadInfoStruct=0,selfThreadId=0,Ea=!1;
|
||||
function assert(a,b){a||n("Assertion failed: "+b)}function Fa(a){var b=f["_"+a];assert(b,"Cannot call unknown function "+a+", make sure it is exported");return b}
|
||||
function Ga(a,b,c,d){var e={string:function(q){var t=0;if(null!==q&&void 0!==q&&0!==q){var w=(q.length<<2)+1;t=Ha(w);Ia(q,v,t,w)}return t},array:function(q){var t=Ha(q.length);y.set(q,t);return t}},g=Fa(a),k=[];a=0;if(d)for(var m=0;m<d.length;m++){var r=e[c[m]];r?(0===a&&(a=A()),k[m]=r(d[m])):k[m]=d[m]}c=g.apply(null,k);c="string"===b?C(c):"boolean"===b?!!c:c;0!==a&&D(a);return c}
|
||||
function Ja(a,b,c){c=b+c;for(var d="";!(b>=c);){var e=a[b++];if(!e)break;if(e&128){var g=a[b++]&63;if(192==(e&224))d+=String.fromCharCode((e&31)<<6|g);else{var k=a[b++]&63;e=224==(e&240)?(e&15)<<12|g<<6|k:(e&7)<<18|g<<12|k<<6|a[b++]&63;65536>e?d+=String.fromCharCode(e):(e-=65536,d+=String.fromCharCode(55296|e>>10,56320|e&1023))}}else d+=String.fromCharCode(e)}return d}function C(a,b){return a?Ja(v,a,b):""}
|
||||
function Ia(a,b,c,d){if(!(0<d))return 0;var e=c;d=c+d-1;for(var g=0;g<a.length;++g){var k=a.charCodeAt(g);if(55296<=k&&57343>=k){var m=a.charCodeAt(++g);k=65536+((k&1023)<<10)|m&1023}if(127>=k){if(c>=d)break;b[c++]=k}else{if(2047>=k){if(c+1>=d)break;b[c++]=192|k>>6}else{if(65535>=k){if(c+2>=d)break;b[c++]=224|k>>12}else{if(c+3>=d)break;b[c++]=240|k>>18;b[c++]=128|k>>12&63}b[c++]=128|k>>6&63}b[c++]=128|k&63}}b[c]=0;return c-e}
|
||||
function Ka(a){for(var b=0,c=0;c<a.length;++c){var d=a.charCodeAt(c);55296<=d&&57343>=d&&(d=65536+((d&1023)<<10)|a.charCodeAt(++c)&1023);127>=d?++b:b=2047>=d?b+2:65535>=d?b+3:b+4}return b}function La(a){var b=Ka(a)+1,c=Ma(b);c&&Ia(a,y,c,b);return c}function Na(a){var b=Ka(a)+1,c=Ha(b);Ia(a,y,c,b);return c}function Pa(a,b,c){for(var d=0;d<a.length;++d)y[b++>>0]=a.charCodeAt(d);c||(y[b>>0]=0)}var oa,y,v,Qa,Ra,E,F,G,Sa,Ta=f.INITIAL_MEMORY||2146435072;
|
||||
if(l)Ca=f.wasmMemory,oa=f.buffer;else if(f.wasmMemory)Ca=f.wasmMemory;else if(Ca=new WebAssembly.Memory({initial:Ta/65536,maximum:Ta/65536,shared:!0}),!(Ca.buffer instanceof SharedArrayBuffer))throw u("requested a shared WebAssembly.Memory but the returned buffer is not a SharedArrayBuffer, indicating that while the browser has SharedArrayBuffer it does not have WebAssembly threads support - you may need to set a flag"),h&&console.log("(on node you may need: --experimental-wasm-threads --experimental-wasm-bulk-memory and also use a recent version)"),
|
||||
Error("bad memory");Ca&&(oa=Ca.buffer);Ta=oa.byteLength;var Ua=oa;oa=Ua;f.HEAP8=y=new Int8Array(Ua);f.HEAP16=Qa=new Int16Array(Ua);f.HEAP32=E=new Int32Array(Ua);f.HEAPU8=v=new Uint8Array(Ua);f.HEAPU16=Ra=new Uint16Array(Ua);f.HEAPU32=F=new Uint32Array(Ua);f.HEAPF32=G=new Float32Array(Ua);f.HEAPF64=Sa=new Float64Array(Ua);var H,Va=[],Wa=[],Xa=[],Ya=[],Za=[];function $a(){var a=f.preRun.shift();Va.unshift(a)}var ab=0,bb=null,cb=null;
|
||||
function eb(){assert(!l,"addRunDependency cannot be used in a pthread worker");ab++;f.monitorRunDependencies&&f.monitorRunDependencies(ab)}function fb(){ab--;f.monitorRunDependencies&&f.monitorRunDependencies(ab);if(0==ab&&(null!==bb&&(clearInterval(bb),bb=null),cb)){var a=cb;cb=null;a()}}f.preloadedImages={};f.preloadedAudios={};
|
||||
function n(a){if(f.onAbort)f.onAbort(a);l&&console.error("Pthread aborting at "+Error().stack);u(a);Ea=!0;a=new WebAssembly.RuntimeError("abort("+a+"). Build with -s ASSERTIONS=1 for more info.");ca(a);throw a;}function gb(a){var b=hb;return String.prototype.startsWith?b.startsWith(a):0===b.indexOf(a)}function ib(){return gb("data:application/octet-stream;base64,")}var hb="ffmpeg-core.wasm";ib()||(hb=qa(hb));
|
||||
function jb(){try{if(Ba)return new Uint8Array(Ba);if(sa)return sa(hb);throw"both async and sync fetching of the wasm failed";}catch(a){n(a)}}function kb(){return Ba||!ka&&!la||"function"!==typeof fetch||gb("file://")?Promise.resolve().then(jb):fetch(hb,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+hb+"'";return a.arrayBuffer()}).catch(function(){return jb()})}
|
||||
var J,L,mb={5454720:function(){throw"Canceled!";},5454940:function(a,b){setTimeout(function(){lb(a,b)},0)},5455042:function(){return 5242880}};function nb(a){for(;0<a.length;){var b=a.shift();if("function"==typeof b)b(f);else{var c=b.vh;"number"===typeof c?void 0===b.Tf?H.get(c)():H.get(c)(b.Tf):c(void 0===b.Tf?null:b.Tf)}}}function ob(a){return a.replace(/\b_Z[\w\d_]+/g,function(b){return b===b?b:b+" ["+b+"]"})}
|
||||
f.dynCall=function(a,b,c){var d;-1!=a.indexOf("j")?d=c&&c.length?f["dynCall_"+a].apply(null,[b].concat(c)):f["dynCall_"+a].call(null,b):d=H.get(b).apply(null,c);return d};var pb=0,qb=0,rb=0;function sb(a,b,c){pb=a|0;rb=b|0;qb=c|0}f.registerPthreadPtr=sb;
|
||||
function tb(a,b){if(0>=a||a>y.length||a&1||0>b)return-28;if(0==b)return 0;2147483647<=b&&(b=Infinity);var c=Atomics.load(E,M.Vf>>2),d=0;if(c==a&&Atomics.compareExchange(E,M.Vf>>2,c,0)==c&&(--b,d=1,0>=b))return 1;a=Atomics.notify(E,a>>2,b);if(0<=a)return a+d;throw"Atomics.notify returned an unexpected value "+a;}f._emscripten_futex_wake=tb;
|
||||
function ub(a){if(l)throw"Internal Error! cancelThread() can only ever be called from main application thread!";if(!a)throw"Internal Error! Null pthread_ptr in cancelThread!";M.Ef[a].worker.postMessage({cmd:"cancel"})}function vb(a){if(l)throw"Internal Error! cleanupThread() can only ever be called from main application thread!";if(!a)throw"Internal Error! Null pthread_ptr in cleanupThread!";E[a+12>>2]=0;(a=M.Ef[a])&&M.Ag(a.worker)}
|
||||
var M={Ph:1,nj:{Ih:0,Jh:0},Gf:[],Kf:[],lj:function(){},pi:function(){M.xf=Ma(232);for(var a=0;58>a;++a)F[M.xf/4+a]=0;E[M.xf+12>>2]=M.xf;a=M.xf+156;E[a>>2]=a;var b=Ma(512);for(a=0;128>a;++a)F[b/4+a]=0;Atomics.store(F,M.xf+104>>2,b);Atomics.store(F,M.xf+40>>2,M.xf);Atomics.store(F,M.xf+44>>2,42);M.Ch();sb(M.xf,!la,1);wb(M.xf)},ri:function(){M.Ch();ba(f);M.receiveObjectTransfer=M.Ii;M.setThreadStatus=M.Li;M.threadCancel=M.Pi;M.threadExit=M.Qi},Ch:function(){M.Vf=xb},Ef:{},Dg:[],Li:function(){},eh:function(){for(;0<
|
||||
M.Dg.length;)M.Dg.pop()();l&&threadInfoStruct&&yb()},Qi:function(a){var b=pb|0;b&&(Atomics.store(F,b+4>>2,a),Atomics.store(F,b+0>>2,1),Atomics.store(F,b+60>>2,1),Atomics.store(F,b+64>>2,0),M.eh(),tb(b+0,2147483647),sb(0,0,0),threadInfoStruct=0,l&&postMessage({cmd:"exit"}))},Pi:function(){M.eh();Atomics.store(F,threadInfoStruct+4>>2,-1);Atomics.store(F,threadInfoStruct+0>>2,1);tb(threadInfoStruct+0,2147483647);threadInfoStruct=selfThreadId=0;sb(0,0,0);postMessage({cmd:"cancelDone"})},Oi:function(){for(var a in M.Ef){var b=
|
||||
M.Ef[a];b&&b.worker&&M.Ag(b.worker)}M.Ef={};for(a=0;a<M.Gf.length;++a){var c=M.Gf[a];c.terminate()}M.Gf=[];for(a=0;a<M.Kf.length;++a)c=M.Kf[a],b=c.yf,M.Pg(b),c.terminate();M.Kf=[]},Pg:function(a){if(a){if(a.threadInfoStruct){var b=E[a.threadInfoStruct+104>>2];E[a.threadInfoStruct+104>>2]=0;zb(b);zb(a.threadInfoStruct)}a.threadInfoStruct=0;a.Kg&&a.Rf&&zb(a.Rf);a.Rf=0;a.worker&&(a.worker.yf=null)}},Ag:function(a){delete M.Ef[a.yf.Lh];M.Gf.push(a);M.Kf.splice(M.Kf.indexOf(a),1);M.Pg(a.yf);a.yf=void 0},
|
||||
Ii:function(){},vi:function(a,b){a.onmessage=function(c){var d=c.data,e=d.cmd;a.yf&&(M.Mg=a.yf.threadInfoStruct);if(d.targetThread&&d.targetThread!=(pb|0)){var g=M.Ef[d.xj];g?g.worker.postMessage(c.data,d.transferList):console.error('Internal error! Worker sent a message "'+e+'" to target pthread '+d.targetThread+", but that thread no longer exists!")}else if("processQueuedMainThreadWork"===e)Ab();else if("spawnThread"===e)Bb(c.data);else if("cleanupThread"===e)vb(d.thread);else if("killThread"===
|
||||
e){c=d.thread;if(l)throw"Internal Error! killThread() can only ever be called from main application thread!";if(!c)throw"Internal Error! Null pthread_ptr in killThread!";E[c+12>>2]=0;c=M.Ef[c];c.worker.terminate();M.Pg(c);M.Kf.splice(M.Kf.indexOf(c.worker),1);c.worker.yf=void 0}else if("cancelThread"===e)ub(d.thread);else if("loaded"===e)a.loaded=!0,b&&b(a),a.og&&(a.og(),delete a.og);else if("print"===e)ya("Thread "+d.threadId+": "+d.text);else if("printErr"===e)u("Thread "+d.threadId+": "+d.text);
|
||||
else if("alert"===e)alert("Thread "+d.threadId+": "+d.text);else if("exit"===e)a.yf&&Atomics.load(F,a.yf.Lh+68>>2)&&M.Ag(a);else if("exitProcess"===e){noExitRuntime=!1;try{Cb(d.returnCode)}catch(k){if(k instanceof wa)return;throw k;}}else"cancelDone"===e?M.Ag(a):"objectTransfer"!==e&&("setimmediate"===c.data.target?a.postMessage(c.data):u("worker sent an unknown command "+e));M.Mg=void 0};a.onerror=function(c){u("pthread sent an error! "+c.filename+":"+c.lineno+": "+c.message)};h&&(a.on("message",
|
||||
function(c){a.onmessage({data:c})}),a.on("error",function(c){a.onerror(c)}),a.on("exit",function(){}));a.postMessage({cmd:"load",urlOrBlob:f.mainScriptUrlOrBlob||_scriptDir,wasmMemory:Ca,wasmModule:Da})},Vh:function(){var a=qa("ffmpeg-core.worker.js");M.Gf.push(new Worker(a))},li:function(){0==M.Gf.length&&(M.Vh(),M.vi(M.Gf[0]));return 0<M.Gf.length?M.Gf.pop():null},Zi:function(a){for(a=performance.now()+a;performance.now()<a;);}};f.establishStackSpace=function(a){D(a)};f.getNoExitRuntime=function(){return noExitRuntime};
|
||||
var Db;h?Db=function(){var a=process.hrtime();return 1E3*a[0]+a[1]/1E6}:l?Db=function(){return performance.now()-f.__performance_now_clock_drift}:"undefined"!==typeof dateNow?Db=dateNow:Db=function(){return performance.now()};function Eb(a){return E[Fb()>>2]=a}function Gb(a,b){if(0===a)a=Date.now();else if(1===a||4===a)a=Db();else return Eb(28),-1;E[b>>2]=a/1E3|0;E[b+4>>2]=a%1E3*1E6|0;return 0}function Hb(a,b){if(l)return N(1,1,a,b);Ya.unshift({vh:a,Tf:b})}
|
||||
function Ib(a,b){a=new Date(1E3*E[a>>2]);E[b>>2]=a.getUTCSeconds();E[b+4>>2]=a.getUTCMinutes();E[b+8>>2]=a.getUTCHours();E[b+12>>2]=a.getUTCDate();E[b+16>>2]=a.getUTCMonth();E[b+20>>2]=a.getUTCFullYear()-1900;E[b+24>>2]=a.getUTCDay();E[b+36>>2]=0;E[b+32>>2]=0;E[b+28>>2]=(a.getTime()-Date.UTC(a.getUTCFullYear(),0,1,0,0,0,0))/864E5|0;Ib.ih||(Ib.ih=La("GMT"));E[b+40>>2]=Ib.ih;return b}
|
||||
function Jb(){function a(k){return(k=k.toTimeString().match(/\(([A-Za-z ]+)\)$/))?k[1]:"GMT"}if(l)return N(2,1);if(!Jb.Yh){Jb.Yh=!0;var b=(new Date).getFullYear(),c=new Date(b,0,1),d=new Date(b,6,1);b=c.getTimezoneOffset();var e=d.getTimezoneOffset(),g=Math.max(b,e);E[Kb()>>2]=60*g;E[Lb()>>2]=Number(b!=e);c=a(c);d=a(d);c=La(c);d=La(d);e<b?(E[Mb()>>2]=c,E[Mb()+4>>2]=d):(E[Mb()>>2]=d,E[Mb()+4>>2]=c)}}
|
||||
function Nb(a,b){Jb();a=new Date(1E3*E[a>>2]);E[b>>2]=a.getSeconds();E[b+4>>2]=a.getMinutes();E[b+8>>2]=a.getHours();E[b+12>>2]=a.getDate();E[b+16>>2]=a.getMonth();E[b+20>>2]=a.getFullYear()-1900;E[b+24>>2]=a.getDay();var c=new Date(a.getFullYear(),0,1);E[b+28>>2]=(a.getTime()-c.getTime())/864E5|0;E[b+36>>2]=-(60*a.getTimezoneOffset());var d=(new Date(a.getFullYear(),6,1)).getTimezoneOffset();c=c.getTimezoneOffset();a=(d!=c&&a.getTimezoneOffset()==Math.min(c,d))|0;E[b+32>>2]=a;a=E[Mb()+(a?4:0)>>2];
|
||||
E[b+40>>2]=a;return b}function Ob(a,b){for(var c=0,d=a.length-1;0<=d;d--){var e=a[d];"."===e?a.splice(d,1):".."===e?(a.splice(d,1),c++):c&&(a.splice(d,1),c--)}if(b)for(;c;c--)a.unshift("..");return a}function Pb(a){var b="/"===a.charAt(0),c="/"===a.substr(-1);(a=Ob(a.split("/").filter(function(d){return!!d}),!b).join("/"))||b||(a=".");a&&c&&(a+="/");return(b?"/":"")+a}
|
||||
function Qb(a){var b=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(a).slice(1);a=b[0];b=b[1];if(!a&&!b)return".";b&&(b=b.substr(0,b.length-1));return a+b}function Rb(a){if("/"===a)return"/";a=Pb(a);a=a.replace(/\/$/,"");var b=a.lastIndexOf("/");return-1===b?a:a.substr(b+1)}function Sb(a,b){return Pb(a+"/"+b)}
|
||||
function Tb(){if("object"===typeof crypto&&"function"===typeof crypto.getRandomValues){var a=new Uint8Array(1);return function(){crypto.getRandomValues(a);return a[0]}}if(h)try{var b=require("crypto");return function(){return b.randomBytes(1)[0]}}catch(c){}return function(){n("randomDevice")}}
|
||||
function Ub(){for(var a="",b=!1,c=arguments.length-1;-1<=c&&!b;c--){b=0<=c?arguments[c]:O.cwd();if("string"!==typeof b)throw new TypeError("Arguments to path.resolve must be strings");if(!b)return"";a=b+"/"+a;b="/"===b.charAt(0)}a=Ob(a.split("/").filter(function(d){return!!d}),!b).join("/");return(b?"/":"")+a||"."}
|
||||
function Vb(a,b){function c(k){for(var m=0;m<k.length&&""===k[m];m++);for(var r=k.length-1;0<=r&&""===k[r];r--);return m>r?[]:k.slice(m,r-m+1)}a=Ub(a).substr(1);b=Ub(b).substr(1);a=c(a.split("/"));b=c(b.split("/"));for(var d=Math.min(a.length,b.length),e=d,g=0;g<d;g++)if(a[g]!==b[g]){e=g;break}d=[];for(g=e;g<a.length;g++)d.push("..");d=d.concat(b.slice(e));return d.join("/")}var Wb=[];function Xb(a,b){Wb[a]={input:[],output:[],Yf:b};O.dh(a,Yb)}
|
||||
var Yb={open:function(a){var b=Wb[a.node.rdev];if(!b)throw new O.af(43);a.tty=b;a.seekable=!1},close:function(a){a.tty.Yf.flush(a.tty)},flush:function(a){a.tty.Yf.flush(a.tty)},read:function(a,b,c,d){if(!a.tty||!a.tty.Yf.xh)throw new O.af(60);for(var e=0,g=0;g<d;g++){try{var k=a.tty.Yf.xh(a.tty)}catch(m){throw new O.af(29);}if(void 0===k&&0===e)throw new O.af(6);if(null===k||void 0===k)break;e++;b[c+g]=k}e&&(a.node.timestamp=Date.now());return e},write:function(a,b,c,d){if(!a.tty||!a.tty.Yf.Zg)throw new O.af(60);
|
||||
try{for(var e=0;e<d;e++)a.tty.Yf.Zg(a.tty,b[c+e])}catch(g){throw new O.af(29);}d&&(a.node.timestamp=Date.now());return e}},$b={xh:function(a){if(!a.input.length){var b=null;if(h){var c=Buffer.Sf?Buffer.Sf(256):new Buffer(256),d=0;try{d=ta.readSync(process.stdin.fd,c,0,256,null)}catch(e){if(-1!=e.toString().indexOf("EOF"))d=0;else throw e;}0<d?b=c.slice(0,d).toString("utf-8"):b=null}else"undefined"!=typeof window&&"function"==typeof window.prompt?(b=window.prompt("Input: "),null!==b&&(b+="\n")):"function"==
|
||||
typeof readline&&(b=readline(),null!==b&&(b+="\n"));if(!b)return null;a.input=Zb(b,!0)}return a.input.shift()},Zg:function(a,b){null===b||10===b?(ya(Ja(a.output,0)),a.output=[]):0!=b&&a.output.push(b)},flush:function(a){a.output&&0<a.output.length&&(ya(Ja(a.output,0)),a.output=[])}},dc={Zg:function(a,b){null===b||10===b?(u(Ja(a.output,0)),a.output=[]):0!=b&&a.output.push(b)},flush:function(a){a.output&&0<a.output.length&&(u(Ja(a.output,0)),a.output=[])}},P={Df:null,jf:function(){return P.createNode(null,
|
||||
"/",16895,0)},createNode:function(a,b,c,d){if(O.si(c)||O.isFIFO(c))throw new O.af(63);P.Df||(P.Df={dir:{node:{Af:P.cf.Af,nf:P.cf.nf,lookup:P.cf.lookup,Ff:P.cf.Ff,rename:P.cf.rename,unlink:P.cf.unlink,rmdir:P.cf.rmdir,readdir:P.cf.readdir,symlink:P.cf.symlink},stream:{tf:P.df.tf}},file:{node:{Af:P.cf.Af,nf:P.cf.nf},stream:{tf:P.df.tf,read:P.df.read,write:P.df.write,fg:P.df.fg,Wf:P.df.Wf,Xf:P.df.Xf}},link:{node:{Af:P.cf.Af,nf:P.cf.nf,readlink:P.cf.readlink},stream:{}},lh:{node:{Af:P.cf.Af,nf:P.cf.nf},
|
||||
stream:O.$h}});c=O.createNode(a,b,c,d);O.kf(c.mode)?(c.cf=P.Df.dir.node,c.df=P.Df.dir.stream,c.bf={}):O.isFile(c.mode)?(c.cf=P.Df.file.node,c.df=P.Df.file.stream,c.gf=0,c.bf=null):O.Mf(c.mode)?(c.cf=P.Df.link.node,c.df=P.Df.link.stream):O.hg(c.mode)&&(c.cf=P.Df.lh.node,c.df=P.Df.lh.stream);c.timestamp=Date.now();a&&(a.bf[b]=c);return c},gj:function(a){if(a.bf&&a.bf.subarray){for(var b=[],c=0;c<a.gf;++c)b.push(a.bf[c]);return b}return a.bf},hj:function(a){return a.bf?a.bf.subarray?a.bf.subarray(0,
|
||||
a.gf):new Uint8Array(a.bf):new Uint8Array(0)},sh:function(a,b){var c=a.bf?a.bf.length:0;c>=b||(b=Math.max(b,c*(1048576>c?2:1.125)>>>0),0!=c&&(b=Math.max(b,256)),c=a.bf,a.bf=new Uint8Array(b),0<a.gf&&a.bf.set(c.subarray(0,a.gf),0))},Ji:function(a,b){if(a.gf!=b)if(0==b)a.bf=null,a.gf=0;else{if(!a.bf||a.bf.subarray){var c=a.bf;a.bf=new Uint8Array(b);c&&a.bf.set(c.subarray(0,Math.min(b,a.gf)))}else if(a.bf||(a.bf=[]),a.bf.length>b)a.bf.length=b;else for(;a.bf.length<b;)a.bf.push(0);a.gf=b}},cf:{Af:function(a){var b=
|
||||
{};b.dev=O.hg(a.mode)?a.id:1;b.ino=a.id;b.mode=a.mode;b.nlink=1;b.uid=0;b.gid=0;b.rdev=a.rdev;O.kf(a.mode)?b.size=4096:O.isFile(a.mode)?b.size=a.gf:O.Mf(a.mode)?b.size=a.link.length:b.size=0;b.atime=new Date(a.timestamp);b.mtime=new Date(a.timestamp);b.ctime=new Date(a.timestamp);b.Xh=4096;b.blocks=Math.ceil(b.size/b.Xh);return b},nf:function(a,b){void 0!==b.mode&&(a.mode=b.mode);void 0!==b.timestamp&&(a.timestamp=b.timestamp);void 0!==b.size&&P.Ji(a,b.size)},lookup:function(){throw O.Qg[44];},Ff:function(a,
|
||||
b,c,d){return P.createNode(a,b,c,d)},rename:function(a,b,c){if(O.kf(a.mode)){try{var d=O.Bf(b,c)}catch(g){}if(d)for(var e in d.bf)throw new O.af(55);}delete a.parent.bf[a.name];a.name=c;b.bf[c]=a;a.parent=b},unlink:function(a,b){delete a.bf[b]},rmdir:function(a,b){var c=O.Bf(a,b),d;for(d in c.bf)throw new O.af(55);delete a.bf[b]},readdir:function(a){var b=[".",".."],c;for(c in a.bf)a.bf.hasOwnProperty(c)&&b.push(c);return b},symlink:function(a,b,c){a=P.createNode(a,b,41471,0);a.link=c;return a},readlink:function(a){if(!O.Mf(a.mode))throw new O.af(28);
|
||||
return a.link}},df:{read:function(a,b,c,d,e){var g=a.node.bf;if(e>=a.node.gf)return 0;a=Math.min(a.node.gf-e,d);if(8<a&&g.subarray)b.set(g.subarray(e,e+a),c);else for(d=0;d<a;d++)b[c+d]=g[e+d];return a},write:function(a,b,c,d,e,g){if(!d)return 0;a=a.node;a.timestamp=Date.now();if(b.subarray&&(!a.bf||a.bf.subarray)){if(g)return a.bf=b.subarray(c,c+d),a.gf=d;if(0===a.gf&&0===e)return a.bf=b.slice(c,c+d),a.gf=d;if(e+d<=a.gf)return a.bf.set(b.subarray(c,c+d),e),d}P.sh(a,e+d);if(a.bf.subarray&&b.subarray)a.bf.set(b.subarray(c,
|
||||
c+d),e);else for(g=0;g<d;g++)a.bf[e+g]=b[c+g];a.gf=Math.max(a.gf,e+d);return d},tf:function(a,b,c){1===c?b+=a.position:2===c&&O.isFile(a.node.mode)&&(b+=a.node.gf);if(0>b)throw new O.af(28);return b},fg:function(a,b,c){P.sh(a.node,b+c);a.node.gf=Math.max(a.node.gf,b+c)},Wf:function(a,b,c,d,e,g){assert(0===b);if(!O.isFile(a.node.mode))throw new O.af(43);a=a.node.bf;if(g&2||a.buffer!==oa){if(0<d||d+c<a.length)a.subarray?a=a.subarray(d,d+c):a=Array.prototype.slice.call(a,d,d+c);d=!0;g=16384*Math.ceil(c/
|
||||
16384);for(b=Ma(g);c<g;)y[b+c++]=0;c=b;if(!c)throw new O.af(48);y.set(a,c)}else d=!1,c=a.byteOffset;return{Hi:c,Jg:d}},Xf:function(a,b,c,d,e){if(!O.isFile(a.node.mode))throw new O.af(43);if(e&2)return 0;P.df.write(a,b,0,d,c,!1);return 0}}},O={root:null,mg:[],ph:{},streams:[],Ai:1,Cf:null,oh:"/",Tg:!1,Bh:!0,mf:{},Mh:{Gh:{Rh:1,Sh:2}},af:null,Qg:{},ii:null,Cg:0,kj:function(a){if(!(a instanceof O.af)){a:{var b=Error();if(!b.stack){try{throw Error();}catch(c){b=c}if(!b.stack){b="(no stack trace available)";
|
||||
break a}}b=b.stack.toString()}f.extraStackTrace&&(b+="\n"+f.extraStackTrace());b=ob(b);throw a+" : "+b;}return Eb(a.ef)},ff:function(a,b){a=Ub(O.cwd(),a);b=b||{};if(!a)return{path:"",node:null};var c={Og:!0,ah:0},d;for(d in c)void 0===b[d]&&(b[d]=c[d]);if(8<b.ah)throw new O.af(32);a=Ob(a.split("/").filter(function(k){return!!k}),!1);var e=O.root;c="/";for(d=0;d<a.length;d++){var g=d===a.length-1;if(g&&b.parent)break;e=O.Bf(e,a[d]);c=Sb(c,a[d]);O.Nf(e)&&(!g||g&&b.Og)&&(e=e.lg.root);if(!g||b.wf)for(g=
|
||||
0;O.Mf(e.mode);)if(e=O.readlink(c),c=Ub(Qb(c),e),e=O.ff(c,{ah:b.ah}).node,40<g++)throw new O.af(32);}return{path:c,node:e}},If:function(a){for(var b;;){if(O.wg(a))return a=a.jf.Eh,b?"/"!==a[a.length-1]?a+"/"+b:a+b:a;b=b?a.name+"/"+b:a.name;a=a.parent}},Sg:function(a,b){for(var c=0,d=0;d<b.length;d++)c=(c<<5)-c+b.charCodeAt(d)|0;return(a+c>>>0)%O.Cf.length},zh:function(a){var b=O.Sg(a.parent.id,a.name);a.Pf=O.Cf[b];O.Cf[b]=a},Ah:function(a){var b=O.Sg(a.parent.id,a.name);if(O.Cf[b]===a)O.Cf[b]=a.Pf;
|
||||
else for(b=O.Cf[b];b;){if(b.Pf===a){b.Pf=a.Pf;break}b=b.Pf}},Bf:function(a,b){var c=O.yi(a);if(c)throw new O.af(c,a);for(c=O.Cf[O.Sg(a.id,b)];c;c=c.Pf){var d=c.name;if(c.parent.id===a.id&&d===b)return c}return O.lookup(a,b)},createNode:function(a,b,c,d){a=new O.Oh(a,b,c,d);O.zh(a);return a},Ng:function(a){O.Ah(a)},wg:function(a){return a===a.parent},Nf:function(a){return!!a.lg},isFile:function(a){return 32768===(a&61440)},kf:function(a){return 16384===(a&61440)},Mf:function(a){return 40960===(a&61440)},
|
||||
hg:function(a){return 8192===(a&61440)},si:function(a){return 24576===(a&61440)},isFIFO:function(a){return 4096===(a&61440)},isSocket:function(a){return 49152===(a&49152)},ji:{r:0,rs:1052672,"r+":2,w:577,wx:705,xw:705,"w+":578,"wx+":706,"xw+":706,a:1089,ax:1217,xa:1217,"a+":1090,"ax+":1218,"xa+":1218},Dh:function(a){var b=O.ji[a];if("undefined"===typeof b)throw Error("Unknown file open mode: "+a);return b},th:function(a){var b=["r","w","rw"][a&3];a&512&&(b+="w");return b},Jf:function(a,b){if(O.Bh)return 0;
|
||||
if(-1===b.indexOf("r")||a.mode&292){if(-1!==b.indexOf("w")&&!(a.mode&146)||-1!==b.indexOf("x")&&!(a.mode&73))return 2}else return 2;return 0},yi:function(a){var b=O.Jf(a,"x");return b?b:a.cf.lookup?0:2},Yg:function(a,b){try{return O.Bf(a,b),20}catch(c){}return O.Jf(a,"wx")},xg:function(a,b,c){try{var d=O.Bf(a,b)}catch(e){return e.ef}if(a=O.Jf(a,"wx"))return a;if(c){if(!O.kf(d.mode))return 54;if(O.wg(d)||O.If(d)===O.cwd())return 10}else if(O.kf(d.mode))return 31;return 0},zi:function(a,b){return a?
|
||||
O.Mf(a.mode)?32:O.kf(a.mode)&&("r"!==O.th(b)||b&512)?31:O.Jf(a,O.th(b)):44},Qh:4096,Bi:function(a,b){b=b||O.Qh;for(a=a||0;a<=b;a++)if(!O.streams[a])return a;throw new O.af(33);},zf:function(a){return O.streams[a]},nh:function(a,b,c){O.Hg||(O.Hg=function(){},O.Hg.prototype={object:{get:function(){return this.node},set:function(g){this.node=g}}});var d=new O.Hg,e;for(e in a)d[e]=a[e];a=d;b=O.Bi(b,c);a.fd=b;return O.streams[b]=a},ai:function(a){O.streams[a]=null},$h:{open:function(a){a.df=O.ki(a.node.rdev).df;
|
||||
a.df.open&&a.df.open(a)},tf:function(){throw new O.af(70);}},Wg:function(a){return a>>8},oj:function(a){return a&255},Of:function(a,b){return a<<8|b},dh:function(a,b){O.ph[a]={df:b}},ki:function(a){return O.ph[a]},wh:function(a){var b=[];for(a=[a];a.length;){var c=a.pop();b.push(c);a.push.apply(a,c.mg)}return b},Kh:function(a,b){function c(k){O.Cg--;return b(k)}function d(k){if(k){if(!d.gi)return d.gi=!0,c(k)}else++g>=e.length&&c(null)}"function"===typeof a&&(b=a,a=!1);O.Cg++;1<O.Cg&&u("warning: "+
|
||||
O.Cg+" FS.syncfs operations in flight at once, probably just doing extra work");var e=O.wh(O.root.jf),g=0;e.forEach(function(k){if(!k.type.Kh)return d(null);k.type.Kh(k,a,d)})},jf:function(a,b,c){var d="/"===c,e=!c;if(d&&O.root)throw new O.af(10);if(!d&&!e){var g=O.ff(c,{Og:!1});c=g.path;g=g.node;if(O.Nf(g))throw new O.af(10);if(!O.kf(g.mode))throw new O.af(54);}b={type:a,tj:b,Eh:c,mg:[]};a=a.jf(b);a.jf=b;b.root=a;d?O.root=a:g&&(g.lg=b,g.jf&&g.jf.mg.push(b));return a},zj:function(a){a=O.ff(a,{Og:!1});
|
||||
if(!O.Nf(a.node))throw new O.af(28);a=a.node;var b=a.lg,c=O.wh(b);Object.keys(O.Cf).forEach(function(d){for(d=O.Cf[d];d;){var e=d.Pf;-1!==c.indexOf(d.jf)&&O.Ng(d);d=e}});a.lg=null;a.jf.mg.splice(a.jf.mg.indexOf(b),1)},lookup:function(a,b){return a.cf.lookup(a,b)},Ff:function(a,b,c){var d=O.ff(a,{parent:!0}).node;a=Rb(a);if(!a||"."===a||".."===a)throw new O.af(28);var e=O.Yg(d,a);if(e)throw new O.af(e);if(!d.cf.Ff)throw new O.af(63);return d.cf.Ff(d,a,b,c)},create:function(a,b){return O.Ff(a,(void 0!==
|
||||
b?b:438)&4095|32768,0)},mkdir:function(a,b){return O.Ff(a,(void 0!==b?b:511)&1023|16384,0)},qj:function(a,b){a=a.split("/");for(var c="",d=0;d<a.length;++d)if(a[d]){c+="/"+a[d];try{O.mkdir(c,b)}catch(e){if(20!=e.ef)throw e;}}},yg:function(a,b,c){"undefined"===typeof c&&(c=b,b=438);return O.Ff(a,b|8192,c)},symlink:function(a,b){if(!Ub(a))throw new O.af(44);var c=O.ff(b,{parent:!0}).node;if(!c)throw new O.af(44);b=Rb(b);var d=O.Yg(c,b);if(d)throw new O.af(d);if(!c.cf.symlink)throw new O.af(63);return c.cf.symlink(c,
|
||||
b,a)},rename:function(a,b){var c=Qb(a),d=Qb(b),e=Rb(a),g=Rb(b);var k=O.ff(a,{parent:!0});var m=k.node;k=O.ff(b,{parent:!0});k=k.node;if(!m||!k)throw new O.af(44);if(m.jf!==k.jf)throw new O.af(75);var r=O.Bf(m,e);d=Vb(a,d);if("."!==d.charAt(0))throw new O.af(28);d=Vb(b,c);if("."!==d.charAt(0))throw new O.af(55);try{var q=O.Bf(k,g)}catch(t){}if(r!==q){c=O.kf(r.mode);if(e=O.xg(m,e,c))throw new O.af(e);if(e=q?O.xg(k,g,c):O.Yg(k,g))throw new O.af(e);if(!m.cf.rename)throw new O.af(63);if(O.Nf(r)||q&&O.Nf(q))throw new O.af(10);
|
||||
if(k!==m&&(e=O.Jf(m,"w")))throw new O.af(e);try{O.mf.willMovePath&&O.mf.willMovePath(a,b)}catch(t){u("FS.trackingDelegate['willMovePath']('"+a+"', '"+b+"') threw an exception: "+t.message)}O.Ah(r);try{m.cf.rename(r,k,g)}catch(t){throw t;}finally{O.zh(r)}try{if(O.mf.onMovePath)O.mf.onMovePath(a,b)}catch(t){u("FS.trackingDelegate['onMovePath']('"+a+"', '"+b+"') threw an exception: "+t.message)}}},rmdir:function(a){var b=O.ff(a,{parent:!0}).node,c=Rb(a),d=O.Bf(b,c),e=O.xg(b,c,!0);if(e)throw new O.af(e);
|
||||
if(!b.cf.rmdir)throw new O.af(63);if(O.Nf(d))throw new O.af(10);try{O.mf.willDeletePath&&O.mf.willDeletePath(a)}catch(g){u("FS.trackingDelegate['willDeletePath']('"+a+"') threw an exception: "+g.message)}b.cf.rmdir(b,c);O.Ng(d);try{if(O.mf.onDeletePath)O.mf.onDeletePath(a)}catch(g){u("FS.trackingDelegate['onDeletePath']('"+a+"') threw an exception: "+g.message)}},readdir:function(a){a=O.ff(a,{wf:!0}).node;if(!a.cf.readdir)throw new O.af(54);return a.cf.readdir(a)},unlink:function(a){var b=O.ff(a,
|
||||
{parent:!0}).node,c=Rb(a),d=O.Bf(b,c),e=O.xg(b,c,!1);if(e)throw new O.af(e);if(!b.cf.unlink)throw new O.af(63);if(O.Nf(d))throw new O.af(10);try{O.mf.willDeletePath&&O.mf.willDeletePath(a)}catch(g){u("FS.trackingDelegate['willDeletePath']('"+a+"') threw an exception: "+g.message)}b.cf.unlink(b,c);O.Ng(d);try{if(O.mf.onDeletePath)O.mf.onDeletePath(a)}catch(g){u("FS.trackingDelegate['onDeletePath']('"+a+"') threw an exception: "+g.message)}},readlink:function(a){a=O.ff(a).node;if(!a)throw new O.af(44);
|
||||
if(!a.cf.readlink)throw new O.af(28);return Ub(O.If(a.parent),a.cf.readlink(a))},stat:function(a,b){a=O.ff(a,{wf:!b}).node;if(!a)throw new O.af(44);if(!a.cf.Af)throw new O.af(63);return a.cf.Af(a)},lstat:function(a){return O.stat(a,!0)},chmod:function(a,b,c){var d;"string"===typeof a?d=O.ff(a,{wf:!c}).node:d=a;if(!d.cf.nf)throw new O.af(63);d.cf.nf(d,{mode:b&4095|d.mode&-4096,timestamp:Date.now()})},lchmod:function(a,b){O.chmod(a,b,!0)},fchmod:function(a,b){a=O.zf(a);if(!a)throw new O.af(8);O.chmod(a.node,
|
||||
b)},chown:function(a,b,c,d){var e;"string"===typeof a?e=O.ff(a,{wf:!d}).node:e=a;if(!e.cf.nf)throw new O.af(63);e.cf.nf(e,{timestamp:Date.now()})},lchown:function(a,b,c){O.chown(a,b,c,!0)},fchown:function(a,b,c){a=O.zf(a);if(!a)throw new O.af(8);O.chown(a.node,b,c)},truncate:function(a,b){if(0>b)throw new O.af(28);var c;"string"===typeof a?c=O.ff(a,{wf:!0}).node:c=a;if(!c.cf.nf)throw new O.af(63);if(O.kf(c.mode))throw new O.af(31);if(!O.isFile(c.mode))throw new O.af(28);if(a=O.Jf(c,"w"))throw new O.af(a);
|
||||
c.cf.nf(c,{size:b,timestamp:Date.now()})},fj:function(a,b){a=O.zf(a);if(!a)throw new O.af(8);if(0===(a.flags&2097155))throw new O.af(28);O.truncate(a.node,b)},Aj:function(a,b,c){a=O.ff(a,{wf:!0}).node;a.cf.nf(a,{timestamp:Math.max(b,c)})},open:function(a,b,c,d,e){if(""===a)throw new O.af(44);b="string"===typeof b?O.Dh(b):b;c=b&64?("undefined"===typeof c?438:c)&4095|32768:0;if("object"===typeof a)var g=a;else{a=Pb(a);try{g=O.ff(a,{wf:!(b&131072)}).node}catch(m){}}var k=!1;if(b&64)if(g){if(b&128)throw new O.af(20);
|
||||
}else g=O.Ff(a,c,0),k=!0;if(!g)throw new O.af(44);O.hg(g.mode)&&(b&=-513);if(b&65536&&!O.kf(g.mode))throw new O.af(54);if(!k&&(c=O.zi(g,b)))throw new O.af(c);b&512&&O.truncate(g,0);b&=-131713;d=O.nh({node:g,path:O.If(g),flags:b,seekable:!0,position:0,df:g.df,Wi:[],error:!1},d,e);d.df.open&&d.df.open(d);!f.logReadFiles||b&1||(O.$g||(O.$g={}),a in O.$g||(O.$g[a]=1,u("FS.trackingDelegate error on read file: "+a)));try{O.mf.onOpenFile&&(e=0,1!==(b&2097155)&&(e|=O.Mh.Gh.Rh),0!==(b&2097155)&&(e|=O.Mh.Gh.Sh),
|
||||
O.mf.onOpenFile(a,e))}catch(m){u("FS.trackingDelegate['onOpenFile']('"+a+"', flags) threw an exception: "+m.message)}return d},close:function(a){if(O.ig(a))throw new O.af(8);a.Lf&&(a.Lf=null);try{a.df.close&&a.df.close(a)}catch(b){throw b;}finally{O.ai(a.fd)}a.fd=null},ig:function(a){return null===a.fd},tf:function(a,b,c){if(O.ig(a))throw new O.af(8);if(!a.seekable||!a.df.tf)throw new O.af(70);if(0!=c&&1!=c&&2!=c)throw new O.af(28);a.position=a.df.tf(a,b,c);a.Wi=[];return a.position},read:function(a,
|
||||
b,c,d,e){if(0>d||0>e)throw new O.af(28);if(O.ig(a))throw new O.af(8);if(1===(a.flags&2097155))throw new O.af(8);if(O.kf(a.node.mode))throw new O.af(31);if(!a.df.read)throw new O.af(28);var g="undefined"!==typeof e;if(!g)e=a.position;else if(!a.seekable)throw new O.af(70);b=a.df.read(a,b,c,d,e);g||(a.position+=b);return b},write:function(a,b,c,d,e,g){if(0>d||0>e)throw new O.af(28);if(O.ig(a))throw new O.af(8);if(0===(a.flags&2097155))throw new O.af(8);if(O.kf(a.node.mode))throw new O.af(31);if(!a.df.write)throw new O.af(28);
|
||||
a.seekable&&a.flags&1024&&O.tf(a,0,2);var k="undefined"!==typeof e;if(!k)e=a.position;else if(!a.seekable)throw new O.af(70);b=a.df.write(a,b,c,d,e,g);k||(a.position+=b);try{if(a.path&&O.mf.onWriteToFile)O.mf.onWriteToFile(a.path)}catch(m){u("FS.trackingDelegate['onWriteToFile']('"+a.path+"') threw an exception: "+m.message)}return b},fg:function(a,b,c){if(O.ig(a))throw new O.af(8);if(0>b||0>=c)throw new O.af(28);if(0===(a.flags&2097155))throw new O.af(8);if(!O.isFile(a.node.mode)&&!O.kf(a.node.mode))throw new O.af(43);
|
||||
if(!a.df.fg)throw new O.af(138);a.df.fg(a,b,c)},Wf:function(a,b,c,d,e,g){if(0!==(e&2)&&0===(g&2)&&2!==(a.flags&2097155))throw new O.af(2);if(1===(a.flags&2097155))throw new O.af(2);if(!a.df.Wf)throw new O.af(43);return a.df.Wf(a,b,c,d,e,g)},Xf:function(a,b,c,d,e){return a&&a.df.Xf?a.df.Xf(a,b,c,d,e):0},sj:function(){return 0},Uf:function(a,b,c){if(!a.df.Uf)throw new O.af(59);return a.df.Uf(a,b,c)},readFile:function(a,b){b=b||{};b.flags=b.flags||"r";b.encoding=b.encoding||"binary";if("utf8"!==b.encoding&&
|
||||
"binary"!==b.encoding)throw Error('Invalid encoding type "'+b.encoding+'"');var c,d=O.open(a,b.flags);a=O.stat(a).size;var e=new Uint8Array(a);O.read(d,e,0,a,0);"utf8"===b.encoding?c=Ja(e,0):"binary"===b.encoding&&(c=e);O.close(d);return c},writeFile:function(a,b,c){c=c||{};c.flags=c.flags||"w";a=O.open(a,c.flags,c.mode);if("string"===typeof b){var d=new Uint8Array(Ka(b)+1);b=Ia(b,d,0,d.length);O.write(a,d,0,b,void 0,c.Zh)}else if(ArrayBuffer.isView(b))O.write(a,b,0,b.byteLength,void 0,c.Zh);else throw Error("Unsupported data type");
|
||||
O.close(a)},cwd:function(){return O.oh},chdir:function(a){a=O.ff(a,{wf:!0});if(null===a.node)throw new O.af(44);if(!O.kf(a.node.mode))throw new O.af(54);var b=O.Jf(a.node,"x");if(b)throw new O.af(b);O.oh=a.path},ci:function(){O.mkdir("/tmp");O.mkdir("/home");O.mkdir("/home/web_user")},bi:function(){O.mkdir("/dev");O.dh(O.Of(1,3),{read:function(){return 0},write:function(b,c,d,e){return e}});O.yg("/dev/null",O.Of(1,3));Xb(O.Of(5,0),$b);Xb(O.Of(6,0),dc);O.yg("/dev/tty",O.Of(5,0));O.yg("/dev/tty1",O.Of(6,
|
||||
0));var a=Tb();O.Hf("/dev","random",a);O.Hf("/dev","urandom",a);O.mkdir("/dev/shm");O.mkdir("/dev/shm/tmp")},ei:function(){O.mkdir("/proc");O.mkdir("/proc/self");O.mkdir("/proc/self/fd");O.jf({jf:function(){var a=O.createNode("/proc/self","fd",16895,73);a.cf={lookup:function(b,c){var d=O.zf(+c);if(!d)throw new O.af(8);b={parent:null,jf:{Eh:"fake"},cf:{readlink:function(){return d.path}}};return b.parent=b}};return a}},{},"/proc/self/fd")},fi:function(){f.stdin?O.Hf("/dev","stdin",f.stdin):O.symlink("/dev/tty",
|
||||
"/dev/stdin");f.stdout?O.Hf("/dev","stdout",null,f.stdout):O.symlink("/dev/tty","/dev/stdout");f.stderr?O.Hf("/dev","stderr",null,f.stderr):O.symlink("/dev/tty1","/dev/stderr");O.open("/dev/stdin","r");O.open("/dev/stdout","w");O.open("/dev/stderr","w")},rh:function(){O.af||(O.af=function(a,b){this.node=b;this.Ki=function(c){this.ef=c};this.Ki(a);this.message="FS error"},O.af.prototype=Error(),O.af.prototype.constructor=O.af,[44].forEach(function(a){O.Qg[a]=new O.af(a);O.Qg[a].stack="<generic error, no stack>"}))},
|
||||
Ni:function(){O.rh();O.Cf=Array(4096);O.jf(P,{},"/");O.ci();O.bi();O.ei();O.ii={MEMFS:P}},gg:function(a,b,c){O.gg.Tg=!0;O.rh();f.stdin=a||f.stdin;f.stdout=b||f.stdout;f.stderr=c||f.stderr;O.fi()},quit:function(){O.gg.Tg=!1;var a=f._fflush;a&&a(0);for(a=0;a<O.streams.length;a++){var b=O.streams[a];b&&O.close(b)}},Rg:function(a,b){var c=0;a&&(c|=365);b&&(c|=146);return c},ej:function(a,b){a=O.Lg(a,b);if(a.exists)return a.object;Eb(a.error);return null},Lg:function(a,b){try{var c=O.ff(a,{wf:!b});a=c.path}catch(e){}var d=
|
||||
{wg:!1,exists:!1,error:0,name:null,path:null,object:null,Ci:!1,Ei:null,Di:null};try{c=O.ff(a,{parent:!0}),d.Ci=!0,d.Ei=c.path,d.Di=c.node,d.name=Rb(a),c=O.ff(a,{wf:!b}),d.exists=!0,d.path=c.path,d.object=c.node,d.name=c.node.name,d.wg="/"===c.path}catch(e){d.error=e.ef}return d},cj:function(a,b){a="string"===typeof a?a:O.If(a);for(b=b.split("/").reverse();b.length;){var c=b.pop();if(c){var d=Sb(a,c);try{O.mkdir(d)}catch(e){}a=d}}return d},di:function(a,b,c,d,e){a=Sb("string"===typeof a?a:O.If(a),
|
||||
b);return O.create(a,O.Rg(d,e))},mh:function(a,b,c,d,e,g){a=b?Sb("string"===typeof a?a:O.If(a),b):a;d=O.Rg(d,e);e=O.create(a,d);if(c){if("string"===typeof c){a=Array(c.length);b=0;for(var k=c.length;b<k;++b)a[b]=c.charCodeAt(b);c=a}O.chmod(e,d|146);a=O.open(e,"w");O.write(a,c,0,c.length,0,g);O.close(a);O.chmod(e,d)}return e},Hf:function(a,b,c,d){a=Sb("string"===typeof a?a:O.If(a),b);b=O.Rg(!!c,!!d);O.Hf.Wg||(O.Hf.Wg=64);var e=O.Of(O.Hf.Wg++,0);O.dh(e,{open:function(g){g.seekable=!1},close:function(){d&&
|
||||
d.buffer&&d.buffer.length&&d(10)},read:function(g,k,m,r){for(var q=0,t=0;t<r;t++){try{var w=c()}catch(B){throw new O.af(29);}if(void 0===w&&0===q)throw new O.af(6);if(null===w||void 0===w)break;q++;k[m+t]=w}q&&(g.node.timestamp=Date.now());return q},write:function(g,k,m,r){for(var q=0;q<r;q++)try{d(k[m+q])}catch(t){throw new O.af(29);}r&&(g.node.timestamp=Date.now());return q}});return O.yg(a,b,e)},uh:function(a){if(a.Ug||a.ti||a.link||a.bf)return!0;var b=!0;if("undefined"!==typeof XMLHttpRequest)throw Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.");
|
||||
if(ra)try{a.bf=Zb(ra(a.url),!0),a.gf=a.bf.length}catch(c){b=!1}else throw Error("Cannot load without read() or XMLHttpRequest.");b||Eb(29);return b},bj:function(a,b,c,d,e){function g(){this.Vg=!1;this.Sf=[]}g.prototype.get=function(q){if(!(q>this.length-1||0>q)){var t=q%this.chunkSize;return this.yh(q/this.chunkSize|0)[t]}};g.prototype.Wh=function(q){this.yh=q};g.prototype.kh=function(){var q=new XMLHttpRequest;q.open("HEAD",c,!1);q.send(null);if(!(200<=q.status&&300>q.status||304===q.status))throw Error("Couldn't load "+
|
||||
c+". Status: "+q.status);var t=Number(q.getResponseHeader("Content-length")),w,B=(w=q.getResponseHeader("Accept-Ranges"))&&"bytes"===w;q=(w=q.getResponseHeader("Content-Encoding"))&&"gzip"===w;var p=1048576;B||(p=t);var x=this;x.Wh(function(z){var I=z*p,W=(z+1)*p-1;W=Math.min(W,t-1);if("undefined"===typeof x.Sf[z]){var db=x.Sf;if(I>W)throw Error("invalid range ("+I+", "+W+") or no bytes requested!");if(W>t-1)throw Error("only "+t+" bytes available! programmer error!");var K=new XMLHttpRequest;K.open("GET",
|
||||
c,!1);t!==p&&K.setRequestHeader("Range","bytes="+I+"-"+W);"undefined"!=typeof Uint8Array&&(K.responseType="arraybuffer");K.overrideMimeType&&K.overrideMimeType("text/plain; charset=x-user-defined");K.send(null);if(!(200<=K.status&&300>K.status||304===K.status))throw Error("Couldn't load "+c+". Status: "+K.status);I=void 0!==K.response?new Uint8Array(K.response||[]):Zb(K.responseText||"",!0);db[z]=I}if("undefined"===typeof x.Sf[z])throw Error("doXHR failed!");return x.Sf[z]});if(q||!t)p=t=1,p=t=this.yh(0).length,
|
||||
ya("LazyFiles on gzip forces download of the whole file when length is accessed");this.Uh=t;this.Th=p;this.Vg=!0};if("undefined"!==typeof XMLHttpRequest){if(!la)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var k=new g;Object.defineProperties(k,{length:{get:function(){this.Vg||this.kh();return this.Uh}},chunkSize:{get:function(){this.Vg||this.kh();return this.Th}}});k={Ug:!1,bf:k}}else k={Ug:!1,url:c};var m=O.di(a,b,k,d,
|
||||
e);k.bf?m.bf=k.bf:k.url&&(m.bf=null,m.url=k.url);Object.defineProperties(m,{gf:{get:function(){return this.bf.length}}});var r={};Object.keys(m.df).forEach(function(q){var t=m.df[q];r[q]=function(){if(!O.uh(m))throw new O.af(29);return t.apply(null,arguments)}});r.read=function(q,t,w,B,p){if(!O.uh(m))throw new O.af(29);q=q.node.bf;if(p>=q.length)return 0;B=Math.min(q.length-p,B);if(q.slice)for(var x=0;x<B;x++)t[w+x]=q[p+x];else for(x=0;x<B;x++)t[w+x]=q.get(p+x);return B};m.df=r;return m},dj:function(a,
|
||||
b,c,d,e,g,k,m,r,q){function t(B){function p(z){q&&q();m||O.mh(a,b,z,d,e,r);g&&g();fb()}var x=!1;f.preloadPlugins.forEach(function(z){!x&&z.canHandle(w)&&(z.handle(B,w,p,function(){k&&k();fb()}),x=!0)});x||p(B)}ec.gg();var w=b?Ub(Sb(a,b)):a;eb();"string"==typeof c?ec.Xi(c,function(B){t(B)},k):t(c)},indexedDB:function(){return window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB},gh:function(){return"EM_FS_"+window.location.pathname},hh:20,eg:"FILE_DATA",wj:function(a,b,
|
||||
c){b=b||function(){};c=c||function(){};var d=O.indexedDB();try{var e=d.open(O.gh(),O.hh)}catch(g){return c(g)}e.onupgradeneeded=function(){ya("creating db");e.result.createObjectStore(O.eg)};e.onsuccess=function(){var g=e.result.transaction([O.eg],"readwrite"),k=g.objectStore(O.eg),m=0,r=0,q=a.length;a.forEach(function(t){t=k.put(O.Lg(t).object.bf,t);t.onsuccess=function(){m++;m+r==q&&(0==r?b():c())};t.onerror=function(){r++;m+r==q&&(0==r?b():c())}});g.onerror=c};e.onerror=c},mj:function(a,b,c){b=
|
||||
b||function(){};c=c||function(){};var d=O.indexedDB();try{var e=d.open(O.gh(),O.hh)}catch(g){return c(g)}e.onupgradeneeded=c;e.onsuccess=function(){var g=e.result;try{var k=g.transaction([O.eg],"readonly")}catch(w){c(w);return}var m=k.objectStore(O.eg),r=0,q=0,t=a.length;a.forEach(function(w){var B=m.get(w);B.onsuccess=function(){O.Lg(w).exists&&O.unlink(w);O.mh(Qb(w),Rb(w),B.result,!0,!0,!0);r++;r+q==t&&(0==q?b():c())};B.onerror=function(){q++;r+q==t&&(0==q?b():c())}});k.onerror=c};e.onerror=c}},
|
||||
fc={};
|
||||
function hc(a,b,c){try{var d=a(b)}catch(e){if(e&&e.node&&Pb(b)!==Pb(O.If(e.node)))return-54;throw e;}E[c>>2]=d.dev;E[c+4>>2]=0;E[c+8>>2]=d.ino;E[c+12>>2]=d.mode;E[c+16>>2]=d.nlink;E[c+20>>2]=d.uid;E[c+24>>2]=d.gid;E[c+28>>2]=d.rdev;E[c+32>>2]=0;L=[d.size>>>0,(J=d.size,1<=+Math.abs(J)?0<J?(Math.min(+Math.floor(J/4294967296),4294967295)|0)>>>0:~~+Math.ceil((J-+(~~J>>>0))/4294967296)>>>0:0)];E[c+40>>2]=L[0];E[c+44>>2]=L[1];E[c+48>>2]=4096;E[c+52>>2]=d.blocks;E[c+56>>2]=d.atime.getTime()/1E3|0;E[c+60>>
|
||||
2]=0;E[c+64>>2]=d.mtime.getTime()/1E3|0;E[c+68>>2]=0;E[c+72>>2]=d.ctime.getTime()/1E3|0;E[c+76>>2]=0;L=[d.ino>>>0,(J=d.ino,1<=+Math.abs(J)?0<J?(Math.min(+Math.floor(J/4294967296),4294967295)|0)>>>0:~~+Math.ceil((J-+(~~J>>>0))/4294967296)>>>0:0)];E[c+80>>2]=L[0];E[c+84>>2]=L[1];return 0}var ic=void 0;function Q(){ic+=4;return E[ic-4>>2]}function jc(a){a=O.zf(a);if(!a)throw new O.af(8);return a}
|
||||
function kc(a,b,c,d,e){if(l)return N(3,1,a,b,c,d,e);try{e=0;for(var g=b?E[b>>2]:0,k=b?E[b+4>>2]:0,m=c?E[c>>2]:0,r=c?E[c+4>>2]:0,q=d?E[d>>2]:0,t=d?E[d+4>>2]:0,w=0,B=0,p=0,x=0,z=0,I=0,W=(b?E[b>>2]:0)|(c?E[c>>2]:0)|(d?E[d>>2]:0),db=(b?E[b+4>>2]:0)|(c?E[c+4>>2]:0)|(d?E[d+4>>2]:0),K=0;K<a;K++){var Y=1<<K%32;if(32>K?W&Y:db&Y){var ia=O.zf(K);if(!ia)throw new O.af(8);var na=5;ia.df.Zf&&(na=ia.df.Zf(ia));na&1&&(32>K?g&Y:k&Y)&&(32>K?w|=Y:B|=Y,e++);na&4&&(32>K?m&Y:r&Y)&&(32>K?p|=Y:x|=Y,e++);na&2&&(32>K?q&Y:
|
||||
t&Y)&&(32>K?z|=Y:I|=Y,e++)}}b&&(E[b>>2]=w,E[b+4>>2]=B);c&&(E[c>>2]=p,E[c+4>>2]=x);d&&(E[d>>2]=z,E[d+4>>2]=I);return e}catch(ua){return"undefined"!==typeof O&&ua instanceof O.af||n(ua),-ua.ef}}function lc(a,b){if(l)return N(4,1,a,b);try{a=C(a);if(b&-8)var c=-28;else{var d;(d=O.ff(a,{wf:!0}).node)?(a="",b&4&&(a+="r"),b&2&&(a+="w"),b&1&&(a+="x"),c=a&&O.Jf(d,a)?-2:0):c=-44}return c}catch(e){return"undefined"!==typeof O&&e instanceof O.af||n(e),-e.ef}}
|
||||
function mc(a,b,c){if(l)return N(5,1,a,b,c);ic=c;try{var d=jc(a);switch(b){case 0:var e=Q();return 0>e?-28:O.open(d.path,d.flags,0,e).fd;case 1:case 2:return 0;case 3:return d.flags;case 4:return e=Q(),d.flags|=e,0;case 12:return e=Q(),Qa[e+0>>1]=2,0;case 13:case 14:return 0;case 16:case 8:return-28;case 9:return Eb(28),-1;default:return-28}}catch(g){return"undefined"!==typeof O&&g instanceof O.af||n(g),-g.ef}}
|
||||
function nc(a,b){if(l)return N(6,1,a,b);try{var c=jc(a);return hc(O.stat,c.path,b)}catch(d){return"undefined"!==typeof O&&d instanceof O.af||n(d),-d.ef}}
|
||||
function oc(a,b,c){if(l)return N(7,1,a,b,c);try{var d=jc(a);d.Lf||(d.Lf=O.readdir(d.path));a=0;for(var e=O.tf(d,0,1),g=Math.floor(e/280);g<d.Lf.length&&a+280<=c;){var k=d.Lf[g];if("."===k[0]){var m=1;var r=4}else{var q=O.Bf(d.node,k);m=q.id;r=O.hg(q.mode)?2:O.kf(q.mode)?4:O.Mf(q.mode)?10:8}L=[m>>>0,(J=m,1<=+Math.abs(J)?0<J?(Math.min(+Math.floor(J/4294967296),4294967295)|0)>>>0:~~+Math.ceil((J-+(~~J>>>0))/4294967296)>>>0:0)];E[b+a>>2]=L[0];E[b+a+4>>2]=L[1];L=[280*(g+1)>>>0,(J=280*(g+1),1<=+Math.abs(J)?
|
||||
0<J?(Math.min(+Math.floor(J/4294967296),4294967295)|0)>>>0:~~+Math.ceil((J-+(~~J>>>0))/4294967296)>>>0:0)];E[b+a+8>>2]=L[0];E[b+a+12>>2]=L[1];Qa[b+a+16>>1]=280;y[b+a+18>>0]=r;Ia(k,v,b+a+19,256);a+=280;g+=1}O.tf(d,280*g,0);return a}catch(t){return"undefined"!==typeof O&&t instanceof O.af||n(t),-t.ef}}function pc(a,b){if(l)return N(8,1,a,b);try{return qc(b,0,136),E[b>>2]=1,E[b+4>>2]=2,E[b+8>>2]=3,E[b+12>>2]=4,0}catch(c){return"undefined"!==typeof O&&c instanceof O.af||n(c),-c.ef}}
|
||||
function rc(a,b,c){if(l)return N(9,1,a,b,c);ic=c;try{var d=jc(a);switch(b){case 21509:case 21505:return d.tty?0:-59;case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:return d.tty?0:-59;case 21519:if(!d.tty)return-59;var e=Q();return E[e>>2]=0;case 21520:return d.tty?-28:-59;case 21531:return e=Q(),O.Uf(d,b,e);case 21523:return d.tty?0:-59;case 21524:return d.tty?0:-59;default:n("bad ioctl syscall "+b)}}catch(g){return"undefined"!==typeof O&&g instanceof O.af||n(g),-g.ef}}
|
||||
function sc(a,b){if(l)return N(10,1,a,b);try{return a=C(a),hc(O.lstat,a,b)}catch(c){return"undefined"!==typeof O&&c instanceof O.af||n(c),-c.ef}}function tc(a,b){if(l)return N(11,1,a,b);try{return a=C(a),a=Pb(a),"/"===a[a.length-1]&&(a=a.substr(0,a.length-1)),O.mkdir(a,b,0),0}catch(c){return"undefined"!==typeof O&&c instanceof O.af||n(c),-c.ef}}
|
||||
function uc(a,b,c,d,e,g){if(l)return N(12,1,a,b,c,d,e,g);try{a:{g<<=12;var k=!1;if(0!==(d&16)&&0!==a%16384)var m=-28;else{if(0!==(d&32)){var r=vc(16384,b);if(!r){m=-48;break a}qc(r,0,b);k=!0}else{var q=O.zf(e);if(!q){m=-8;break a}var t=O.Wf(q,a,b,g,c,d);r=t.Hi;k=t.Jg}fc[r]={xi:r,ui:b,Jg:k,fd:e,Gi:c,flags:d,offset:g};m=r}}return m}catch(w){return"undefined"!==typeof O&&w instanceof O.af||n(w),-w.ef}}
|
||||
function wc(a,b){if(l)return N(13,1,a,b);try{if(-1===(a|0)||0===b)var c=-28;else{var d=fc[a];if(d&&b===d.ui){var e=O.zf(d.fd);if(d.Gi&2){var g=d.flags,k=d.offset,m=v.slice(a,a+b);O.Xf(e,m,k,b,g)}fc[a]=null;d.Jg&&zb(d.xi)}c=0}return c}catch(r){return"undefined"!==typeof O&&r instanceof O.af||n(r),-r.ef}}function xc(a,b,c){if(l)return N(14,1,a,b,c);ic=c;try{var d=C(a),e=Q();return O.open(d,b,e).fd}catch(g){return"undefined"!==typeof O&&g instanceof O.af||n(g),-g.ef}}
|
||||
function yc(a,b,c){if(l)return N(15,1,a,b,c);try{for(var d=c=0;d<b;d++){var e=a+8*d,g=Qa[e+4>>1],k=32,m=O.zf(E[e>>2]);m&&(k=5,m.df.Zf&&(k=m.df.Zf(m)));(k&=g|24)&&c++;Qa[e+6>>1]=k}return c}catch(r){return"undefined"!==typeof O&&r instanceof O.af||n(r),-r.ef}}function zc(a,b,c,d){if(l)return N(16,1,a,b,c,d);try{return d&&(E[d>>2]=-1,E[d+4>>2]=-1,E[d+8>>2]=-1,E[d+12>>2]=-1),0}catch(e){return"undefined"!==typeof O&&e instanceof O.af||n(e),-e.ef}}
|
||||
function Ac(a,b,c){if(l)return N(17,1,a,b,c);try{var d=jc(a);return O.read(d,y,b,c)}catch(e){return"undefined"!==typeof O&&e instanceof O.af||n(e),-e.ef}}function Bc(a,b){if(l)return N(18,1,a,b);try{return a=C(a),b=C(b),O.rename(a,b),0}catch(c){return"undefined"!==typeof O&&c instanceof O.af||n(c),-c.ef}}function Cc(a){if(l)return N(19,1,a);try{return a=C(a),O.rmdir(a),0}catch(b){return"undefined"!==typeof O&&b instanceof O.af||n(b),-b.ef}}
|
||||
var R={jf:function(){f.websocket=f.websocket&&"object"===typeof f.websocket?f.websocket:{};f.websocket.Ig={};f.websocket.on=function(a,b){"function"===typeof b&&(this.Ig[a]=b);return this};f.websocket.emit=function(a,b){"function"===typeof this.Ig[a]&&this.Ig[a].call(this,b)};return O.createNode(null,"/",16895,0)},createSocket:function(a,b,c){b&=-526337;c&&assert(1==b==(6==c));a={family:a,type:b,protocol:c,lf:null,error:null,ng:{},pending:[],ag:[],pf:R.qf};b=R.zg();c=O.createNode(R.root,b,49152,0);
|
||||
c.bg=a;b=O.nh({path:b,node:c,flags:O.Dh("r+"),seekable:!1,df:R.df});a.stream=b;return a},mi:function(a){return(a=O.zf(a))&&O.isSocket(a.node.mode)?a.node.bg:null},df:{Zf:function(a){a=a.node.bg;return a.pf.Zf(a)},Uf:function(a,b,c){a=a.node.bg;return a.pf.Uf(a,b,c)},read:function(a,b,c,d){a=a.node.bg;d=a.pf.bh(a,d);if(!d)return 0;b.set(d.buffer,c);return d.buffer.length},write:function(a,b,c,d){a=a.node.bg;return a.pf.fh(a,b,c,d)},close:function(a){a=a.node.bg;a.pf.close(a)}},zg:function(){R.zg.current||
|
||||
(R.zg.current=0);return"socket["+R.zg.current++ +"]"},qf:{tg:function(a,b,c){if("object"===typeof b){var d=b;c=b=null}if(d)if(d._socket)b=d._socket.remoteAddress,c=d._socket.remotePort;else{c=/ws[s]?:\/\/([^:]+):(\d+)/.exec(d.url);if(!c)throw Error("WebSocket URL must be in the format ws(s)://address:port");b=c[1];c=parseInt(c[2],10)}else try{var e=f.websocket&&"object"===typeof f.websocket,g="ws:#".replace("#","//");e&&"string"===typeof f.websocket.url&&(g=f.websocket.url);if("ws://"===g||"wss://"===
|
||||
g){var k=b.split("/");g=g+k[0]+":"+c+"/"+k.slice(1).join("/")}k="binary";e&&"string"===typeof f.websocket.subprotocol&&(k=f.websocket.subprotocol);var m=void 0;"null"!==k&&(k=k.replace(/^ +| +$/g,"").split(/ *, */),m=h?{protocol:k.toString()}:k);e&&null===f.websocket.subprotocol&&(m=void 0);d=new (h?require("ws"):WebSocket)(g,m);d.binaryType="arraybuffer"}catch(r){throw new O.af(23);}b={hf:b,port:c,socket:d,ug:[]};R.qf.jh(a,b);R.qf.ni(a,b);2===a.type&&"undefined"!==typeof a.Qf&&b.ug.push(new Uint8Array([255,
|
||||
255,255,255,112,111,114,116,(a.Qf&65280)>>8,a.Qf&255]));return b},vg:function(a,b,c){return a.ng[b+":"+c]},jh:function(a,b){a.ng[b.hf+":"+b.port]=b},Hh:function(a,b){delete a.ng[b.hf+":"+b.port]},ni:function(a,b){function c(){f.websocket.emit("open",a.stream.fd);try{for(var g=b.ug.shift();g;)b.socket.send(g),g=b.ug.shift()}catch(k){b.socket.close()}}function d(g){if("string"===typeof g)g=(new TextEncoder).encode(g);else{assert(void 0!==g.byteLength);if(0==g.byteLength)return;g=new Uint8Array(g)}var k=
|
||||
e;e=!1;k&&10===g.length&&255===g[0]&&255===g[1]&&255===g[2]&&255===g[3]&&112===g[4]&&111===g[5]&&114===g[6]&&116===g[7]?(g=g[8]<<8|g[9],R.qf.Hh(a,b),b.port=g,R.qf.jh(a,b)):(a.ag.push({hf:b.hf,port:b.port,data:g}),f.websocket.emit("message",a.stream.fd))}var e=!0;h?(b.socket.on("open",c),b.socket.on("message",function(g,k){k.Yi&&d((new Uint8Array(g)).buffer)}),b.socket.on("close",function(){f.websocket.emit("close",a.stream.fd)}),b.socket.on("error",function(){a.error=14;f.websocket.emit("error",[a.stream.fd,
|
||||
a.error,"ECONNREFUSED: Connection refused"])})):(b.socket.onopen=c,b.socket.onclose=function(){f.websocket.emit("close",a.stream.fd)},b.socket.onmessage=function(g){d(g.data)},b.socket.onerror=function(){a.error=14;f.websocket.emit("error",[a.stream.fd,a.error,"ECONNREFUSED: Connection refused"])})},Zf:function(a){if(1===a.type&&a.lf)return a.pending.length?65:0;var b=0,c=1===a.type?R.qf.vg(a,a.sf,a.vf):null;if(a.ag.length||!c||c&&c.socket.readyState===c.socket.CLOSING||c&&c.socket.readyState===c.socket.CLOSED)b|=
|
||||
65;if(!c||c&&c.socket.readyState===c.socket.OPEN)b|=4;if(c&&c.socket.readyState===c.socket.CLOSING||c&&c.socket.readyState===c.socket.CLOSED)b|=16;return b},Uf:function(a,b,c){switch(b){case 21531:return b=0,a.ag.length&&(b=a.ag[0].data.length),E[c>>2]=b,0;default:return 28}},close:function(a){if(a.lf){try{a.lf.close()}catch(e){}a.lf=null}for(var b=Object.keys(a.ng),c=0;c<b.length;c++){var d=a.ng[b[c]];try{d.socket.close()}catch(e){}R.qf.Hh(a,d)}return 0},bind:function(a,b,c){if("undefined"!==typeof a.Bg||
|
||||
"undefined"!==typeof a.Qf)throw new O.af(28);a.Bg=b;a.Qf=c;if(2===a.type){a.lf&&(a.lf.close(),a.lf=null);try{a.pf.listen(a,0)}catch(d){if(!(d instanceof O.af))throw d;if(138!==d.ef)throw d;}}},connect:function(a,b,c){if(a.lf)throw new O.af(138);if("undefined"!==typeof a.sf&&"undefined"!==typeof a.vf){var d=R.qf.vg(a,a.sf,a.vf);if(d){if(d.socket.readyState===d.socket.CONNECTING)throw new O.af(7);throw new O.af(30);}}b=R.qf.tg(a,b,c);a.sf=b.hf;a.vf=b.port;throw new O.af(26);},listen:function(a){if(!h)throw new O.af(138);
|
||||
if(a.lf)throw new O.af(28);var b=require("ws").Server;a.lf=new b({host:a.Bg,port:a.Qf});f.websocket.emit("listen",a.stream.fd);a.lf.on("connection",function(c){if(1===a.type){var d=R.createSocket(a.family,a.type,a.protocol);c=R.qf.tg(d,c);d.sf=c.hf;d.vf=c.port;a.pending.push(d);f.websocket.emit("connection",d.stream.fd)}else R.qf.tg(a,c),f.websocket.emit("connection",a.stream.fd)});a.lf.on("closed",function(){f.websocket.emit("close",a.stream.fd);a.lf=null});a.lf.on("error",function(){a.error=23;
|
||||
f.websocket.emit("error",[a.stream.fd,a.error,"EHOSTUNREACH: Host is unreachable"])})},accept:function(a){if(!a.lf)throw new O.af(28);var b=a.pending.shift();b.stream.flags=a.stream.flags;return b},ij:function(a,b){if(b){if(void 0===a.sf||void 0===a.vf)throw new O.af(53);b=a.sf;a=a.vf}else b=a.Bg||0,a=a.Qf||0;return{hf:b,port:a}},fh:function(a,b,c,d,e,g){if(2===a.type){if(void 0===e||void 0===g)e=a.sf,g=a.vf;if(void 0===e||void 0===g)throw new O.af(17);}else e=a.sf,g=a.vf;var k=R.qf.vg(a,e,g);if(1===
|
||||
a.type){if(!k||k.socket.readyState===k.socket.CLOSING||k.socket.readyState===k.socket.CLOSED)throw new O.af(53);if(k.socket.readyState===k.socket.CONNECTING)throw new O.af(6);}ArrayBuffer.isView(b)&&(c+=b.byteOffset,b=b.buffer);var m;b instanceof SharedArrayBuffer?m=(new Uint8Array(new Uint8Array(b.slice(c,c+d)))).buffer:m=b.slice(c,c+d);if(2===a.type&&(!k||k.socket.readyState!==k.socket.OPEN))return k&&k.socket.readyState!==k.socket.CLOSING&&k.socket.readyState!==k.socket.CLOSED||(k=R.qf.tg(a,e,
|
||||
g)),k.ug.push(m),d;try{return k.socket.send(m),d}catch(r){throw new O.af(28);}},bh:function(a,b){if(1===a.type&&a.lf)throw new O.af(53);var c=a.ag.shift();if(!c){if(1===a.type){if(a=R.qf.vg(a,a.sf,a.vf)){if(a.socket.readyState===a.socket.CLOSING||a.socket.readyState===a.socket.CLOSED)return null;throw new O.af(6);}throw new O.af(53);}throw new O.af(6);}var d=c.data.byteLength||c.data.length,e=c.data.byteOffset||0,g=c.data.buffer||c.data;b=Math.min(b,d);var k={buffer:new Uint8Array(g,e,b),hf:c.hf,
|
||||
port:c.port};1===a.type&&b<d&&(c.data=new Uint8Array(g,e+b,d-b),a.ag.unshift(c));return k}}};function Dc(a){a=a.split(".");for(var b=0;4>b;b++){var c=Number(a[b]);if(isNaN(c))return null;a[b]=c}return(a[0]|a[1]<<8|a[2]<<16|a[3]<<24)>>>0}
|
||||
function Ec(a){var b,c,d=[];if(!/^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i.test(a))return null;if("::"===a)return[0,0,0,0,0,0,0,0];a=0===a.indexOf("::")?a.replace("::","Z:"):a.replace("::",":Z:");0<a.indexOf(".")?(a=a.replace(/[.]/g,":"),a=a.split(":"),a[a.length-4]=parseInt(a[a.length-4])+256*parseInt(a[a.length-3]),a[a.length-3]=parseInt(a[a.length-2])+256*parseInt(a[a.length-
|
||||
1]),a=a.slice(0,a.length-2)):a=a.split(":");for(b=c=0;b<a.length;b++)if("string"===typeof a[b])if("Z"===a[b]){for(c=0;c<8-a.length+1;c++)d[b+c]=0;--c}else d[b+c]=Fc(parseInt(a[b],16));else d[b+c]=a[b];return[d[1]<<16|d[0],d[3]<<16|d[2],d[5]<<16|d[4],d[7]<<16|d[6]]}var Gc=1,Hc={},Ic={};
|
||||
function Jc(a){var b=Dc(a);if(null!==b)return a;b=Ec(a);if(null!==b)return a;Hc[a]?b=Hc[a]:(b=Gc++,assert(65535>b,"exceeded max address mappings of 65535"),b="172.29."+(b&255)+"."+(b&65280),Ic[b]=a,Hc[a]=b);return b}function Kc(a){return Ic[a]?Ic[a]:null}function Lc(a){return(a&255)+"."+(a>>8&255)+"."+(a>>16&255)+"."+(a>>24&255)}
|
||||
function Mc(a){var b="",c,d=0,e=0,g=0,k=0;a=[a[0]&65535,a[0]>>16,a[1]&65535,a[1]>>16,a[2]&65535,a[2]>>16,a[3]&65535,a[3]>>16];var m=!0;for(c=0;5>c;c++)if(0!==a[c]){m=!1;break}if(m){c=Lc(a[6]|a[7]<<16);if(-1===a[5])return"::ffff:"+c;if(0===a[5])return"0.0.0.0"===c&&(c=""),"0.0.0.1"===c&&(c="1"),"::"+c}for(c=0;8>c;c++)0===a[c]&&(1<c-e&&(k=0),e=c,k++),k>d&&(d=k,g=c-d+1);for(c=0;8>c;c++)1<d&&0===a[c]&&c>=g&&c<g+d?c===g&&(b+=":",0===g&&(b+=":")):(b+=Number(Nc(a[c]&65535)).toString(16),b+=7>c?":":"");return b}
|
||||
function Oc(a,b){var c=Qa[a>>1],d=Nc(Ra[a+2>>1]);switch(c){case 2:if(16!==b)return{ef:28};a=E[a+4>>2];a=Lc(a);break;case 10:if(28!==b)return{ef:28};a=[E[a+8>>2],E[a+12>>2],E[a+16>>2],E[a+20>>2]];a=Mc(a);break;default:return{ef:5}}return{family:c,hf:a,port:d}}
|
||||
function Pc(a,b,c,d){switch(b){case 2:c=Dc(c);Qa[a>>1]=b;E[a+4>>2]=c;Qa[a+2>>1]=Fc(d);break;case 10:c=Ec(c);E[a>>2]=b;E[a+8>>2]=c[0];E[a+12>>2]=c[1];E[a+16>>2]=c[2];E[a+20>>2]=c[3];Qa[a+2>>1]=Fc(d);E[a+4>>2]=0;E[a+24>>2]=0;break;default:return{ef:5}}return{}}
|
||||
function Qc(a,b){if(l)return N(20,1,a,b);try{ic=b;b=function(){var aa=R.mi(Q());if(!aa)throw new O.af(8);return aa};var c=function(aa){var pd=Q(),ge=Q();if(aa&&0===pd)return null;aa=Oc(pd,ge);if(aa.ef)throw new O.af(aa.ef);aa.hf=Kc(aa.hf)||aa.hf;return aa};switch(a){case 1:var d=Q(),e=Q(),g=Q(),k=R.createSocket(d,e,g);return k.stream.fd;case 2:k=b();var m=c();k.pf.bind(k,m.hf,m.port);return 0;case 3:return k=b(),m=c(),k.pf.connect(k,m.hf,m.port),0;case 4:k=b();var r=Q();k.pf.listen(k,r);return 0;
|
||||
case 5:k=b();var q=Q();Q();var t=k.pf.accept(k);q&&Pc(q,t.family,Jc(t.sf),t.vf);return t.stream.fd;case 6:return k=b(),q=Q(),Q(),Pc(q,k.family,Jc(k.Bg||"0.0.0.0"),k.Qf),0;case 7:k=b();q=Q();Q();if(!k.sf)return-53;Pc(q,k.family,Jc(k.sf),k.vf);return 0;case 11:k=b();var w=Q(),B=Q();Q();var p=c(!0);return p?k.pf.fh(k,y,w,B,p.hf,p.port):O.write(k.stream,y,w,B);case 12:k=b();var x=Q(),z=Q();Q();q=Q();Q();var I=k.pf.bh(k,z);if(!I)return 0;q&&Pc(q,k.family,Jc(I.hf),I.port);v.set(I.buffer,x);return I.buffer.byteLength;
|
||||
case 14:return-50;case 15:k=b();var W=Q(),db=Q(),K=Q(),Y=Q();return 1===W&&4===db?(E[K>>2]=k.error,E[Y>>2]=4,k.error=null,0):-50;case 16:k=b();w=Q();Q();var ia=E[w+8>>2],na=E[w+12>>2],ua=E[w>>2],he=E[w+4>>2];if(ua){m=Oc(ua,he);if(m.ef)return-m.ef;var ie=m.port;q=Kc(m.hf)||m.hf}for(var Oa=0,X=0;X<na;X++)Oa+=E[ia+(8*X+4)>>2];var qd=new Uint8Array(Oa);for(X=B=0;X<na;X++){var ac=E[ia+8*X>>2],bc=E[ia+(8*X+4)>>2];for(x=0;x<bc;x++)qd[B++]=y[ac+x>>0]}return k.pf.fh(k,qd,0,Oa,q,ie);case 17:k=b();w=Q();Q();
|
||||
ia=E[w+8>>2];na=E[w+12>>2];for(X=Oa=0;X<na;X++)Oa+=E[ia+(8*X+4)>>2];I=k.pf.bh(k,Oa);if(!I)return 0;(ua=E[w>>2])&&Pc(ua,k.family,Jc(I.hf),I.port);k=0;var cc=I.buffer.byteLength;for(X=0;0<cc&&X<na;X++)if(ac=E[ia+8*X>>2],bc=E[ia+(8*X+4)>>2])B=Math.min(bc,cc),x=I.buffer.subarray(k,k+B),v.set(x,ac+k),k+=B,cc-=B;return k;default:return-52}}catch(aa){return"undefined"!==typeof O&&aa instanceof O.af||n(aa),-aa.ef}}
|
||||
function Rc(a,b){if(l)return N(21,1,a,b);try{return a=C(a),hc(O.stat,a,b)}catch(c){return"undefined"!==typeof O&&c instanceof O.af||n(c),-c.ef}}function Sc(a){if(l)return N(22,1,a);try{return a=C(a),O.unlink(a),0}catch(b){return"undefined"!==typeof O&&b instanceof O.af||n(b),-b.ef}}function Tc(){void 0===Tc.start&&(Tc.start=Date.now());return 1E3*(Date.now()-Tc.start)|0}
|
||||
function Uc(){h||la||(za||(za={}),za["Blocking on the main thread is very dangerous, see https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread"]||(za["Blocking on the main thread is very dangerous, see https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread"]=1,u("Blocking on the main thread is very dangerous, see https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread")))}
|
||||
function Vc(a,b,c){if(0>=a||a>y.length||a&1)return-28;if(ka){if(Atomics.load(E,a>>2)!=b)return-6;var d=performance.now();c=d+c;for(Atomics.exchange(E,M.Vf>>2,a);;){d=performance.now();if(d>c)return Atomics.exchange(E,M.Vf>>2,0),-73;d=Atomics.exchange(E,M.Vf>>2,0);if(0==d)break;Ab();if(Atomics.load(E,a>>2)!=b)return-6;Atomics.exchange(E,M.Vf>>2,a)}return 0}a=Atomics.wait(E,a>>2,b,c);if("timed-out"===a)return-73;if("not-equal"===a)return-6;if("ok"===a)return 0;throw"Atomics.wait returned an unexpected value "+
|
||||
a;}function Wc(a){var b=a.getExtension("ANGLE_instanced_arrays");b&&(a.vertexAttribDivisor=function(c,d){b.vertexAttribDivisorANGLE(c,d)},a.drawArraysInstanced=function(c,d,e,g){b.drawArraysInstancedANGLE(c,d,e,g)},a.drawElementsInstanced=function(c,d,e,g,k){b.drawElementsInstancedANGLE(c,d,e,g,k)})}
|
||||
function Xc(a){var b=a.getExtension("OES_vertex_array_object");b&&(a.createVertexArray=function(){return b.createVertexArrayOES()},a.deleteVertexArray=function(c){b.deleteVertexArrayOES(c)},a.bindVertexArray=function(c){b.bindVertexArrayOES(c)},a.isVertexArray=function(c){return b.isVertexArrayOES(c)})}function Yc(a){var b=a.getExtension("WEBGL_draw_buffers");b&&(a.drawBuffers=function(c,d){b.drawBuffersWEBGL(c,d)})}var Zc=1,$c=[],S=[],ad=[],bd=[],cd=[],T=[],dd=[],ed=[],fd=[],gd={},hd={},id=4;
|
||||
function U(a){jd||(jd=a)}function kd(a){for(var b=Zc++,c=a.length;c<b;c++)a[c]=null;return b}
|
||||
function ld(a){a||(a=md);if(!a.oi){a.oi=!0;var b=a.qg;Wc(b);Xc(b);Yc(b);b.uf=b.getExtension("EXT_disjoint_timer_query");b.rj=b.getExtension("WEBGL_multi_draw");var c="OES_texture_float OES_texture_half_float OES_standard_derivatives OES_vertex_array_object WEBGL_compressed_texture_s3tc WEBGL_depth_texture OES_element_index_uint EXT_texture_filter_anisotropic EXT_frag_depth WEBGL_draw_buffers ANGLE_instanced_arrays OES_texture_float_linear OES_texture_half_float_linear EXT_blend_minmax EXT_shader_texture_lod EXT_texture_norm16 WEBGL_compressed_texture_pvrtc EXT_color_buffer_half_float WEBGL_color_buffer_float EXT_sRGB WEBGL_compressed_texture_etc1 EXT_disjoint_timer_query WEBGL_compressed_texture_etc WEBGL_compressed_texture_astc EXT_color_buffer_float WEBGL_compressed_texture_s3tc_srgb EXT_disjoint_timer_query_webgl2 WEBKIT_WEBGL_compressed_texture_pvrtc".split(" ");(b.getSupportedExtensions()||
|
||||
[]).forEach(function(d){-1!=c.indexOf(d)&&b.getExtension(d)})}}var jd,md,nd=[];function od(a,b,c,d){for(var e=0;e<a;e++){var g=V[c](),k=g&&kd(d);g?(g.name=k,d[k]=g):U(1282);E[b+4*e>>2]=k}}function rd(a,b,c,d,e,g,k,m){b=S[b];if(a=V[a](b,c))d=m&&Ia(a.name,v,m,d),e&&(E[e>>2]=d),g&&(E[g>>2]=a.size),k&&(E[k>>2]=a.type)}function sd(a,b){F[a>>2]=b;F[a+4>>2]=(b-F[a>>2])/4294967296}
|
||||
function td(a,b,c){if(b){var d=void 0;switch(a){case 36346:d=1;break;case 36344:0!=c&&1!=c&&U(1280);return;case 36345:d=0;break;case 34466:var e=V.getParameter(34467);d=e?e.length:0}if(void 0===d)switch(e=V.getParameter(a),typeof e){case "number":d=e;break;case "boolean":d=e?1:0;break;case "string":U(1280);return;case "object":if(null===e)switch(a){case 34964:case 35725:case 34965:case 36006:case 36007:case 32873:case 34229:case 34068:d=0;break;default:U(1280);return}else{if(e instanceof Float32Array||
|
||||
e instanceof Uint32Array||e instanceof Int32Array||e instanceof Array){for(a=0;a<e.length;++a)switch(c){case 0:E[b+4*a>>2]=e[a];break;case 2:G[b+4*a>>2]=e[a];break;case 4:y[b+a>>0]=e[a]?1:0}return}try{d=e.name|0}catch(g){U(1280);u("GL_INVALID_ENUM in glGet"+c+"v: Unknown object returned from WebGL getParameter("+a+")! (error: "+g+")");return}}break;default:U(1280);u("GL_INVALID_ENUM in glGet"+c+"v: Native code calling glGet"+c+"v("+a+") and it returns "+e+" of type "+typeof e+"!");return}switch(c){case 1:sd(b,
|
||||
d);break;case 0:E[b>>2]=d;break;case 2:G[b>>2]=d;break;case 4:y[b>>0]=d?1:0}}else U(1281)}function ud(a){var b=Ka(a)+1,c=Ma(b);Ia(a,v,c,b);return c}function vd(a,b,c,d){if(c)if(a=V.getUniform(S[a],T[b]),"number"==typeof a||"boolean"==typeof a)switch(d){case 0:E[c>>2]=a;break;case 2:G[c>>2]=a}else for(b=0;b<a.length;b++)switch(d){case 0:E[c+4*b>>2]=a[b];break;case 2:G[c+4*b>>2]=a[b]}else U(1281)}
|
||||
function wd(a,b,c,d){if(c)if(a=V.getVertexAttrib(a,b),34975==b)E[c>>2]=a&&a.name;else if("number"==typeof a||"boolean"==typeof a)switch(d){case 0:E[c>>2]=a;break;case 2:G[c>>2]=a;break;case 5:E[c>>2]=Math.fround(a)}else for(b=0;b<a.length;b++)switch(d){case 0:E[c+4*b>>2]=a[b];break;case 2:G[c+4*b>>2]=a[b];break;case 5:E[c+4*b>>2]=Math.fround(a[b])}else U(1281)}
|
||||
function xd(a,b,c,d,e){a-=5120;a=1==a?v:4==a?E:6==a?G:5==a||28922==a?F:Ra;var g=31-Math.clz32(a.BYTES_PER_ELEMENT),k=id;return a.subarray(e>>g,e+d*(c*({5:3,6:4,8:2,29502:3,29504:4}[b-6402]||1)*(1<<g)+k-1&-k)>>g)}var yd=[],zd=[];function N(a,b){for(var c=arguments.length-2,d=A(),e=Ha(8*c),g=e>>3,k=0;k<c;k++)Sa[g+k]=arguments[2+k];c=Ad(a,c,e,b);D(d);return c}var Bd=[],Cd=[],Dd=[0,"undefined"!==typeof document?document:0,"undefined"!==typeof window?window:0];
|
||||
function Ed(a){a=2<a?C(a):a;return Dd[a]||("undefined"!==typeof document?document.querySelector(a):void 0)}
|
||||
function Fd(a,b,c){var d=Ed(a);if(!d)return-4;d.sg&&(E[d.sg>>2]=b,E[d.sg+4>>2]=c);if(d.Fh||!d.aj)d.Fh&&(d=d.Fh),a=!1,d.rg&&d.rg.qg&&(a=d.rg.qg.getParameter(2978),a=0===a[0]&&0===a[1]&&a[2]===d.width&&a[3]===d.height),d.width=b,d.height=c,a&&d.rg.qg.viewport(0,0,b,c);else{if(d.sg){a=a?C(a):"";d=E[d.sg+8>>2];var e=A(),g=Ha(12),k=0;a&&(k=ud(a));E[g>>2]=k;E[g+4>>2]=b;E[g+8>>2]=c;Gd(0,d,657457152,0,k,g);D(e);return 1}return-4}return 0}function Hd(a,b,c){return l?N(23,1,a,b,c):Fd(a,b,c)}
|
||||
var Id=["default","low-power","high-performance"],Jd={};function Kd(){if(!Ld){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"===typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:ha||"./this.program"},b;for(b in Jd)a[b]=Jd[b];var c=[];for(b in a)c.push(b+"="+a[b]);Ld=c}return Ld}var Ld;
|
||||
function Md(a){if(l)return N(24,1,a);try{var b=jc(a);O.close(b);return 0}catch(c){return"undefined"!==typeof O&&c instanceof O.af||n(c),c.ef}}function Nd(a,b){if(l)return N(25,1,a,b);try{var c=jc(a);y[b>>0]=c.tty?2:O.kf(c.mode)?3:O.Mf(c.mode)?7:4;return 0}catch(d){return"undefined"!==typeof O&&d instanceof O.af||n(d),d.ef}}
|
||||
function Od(a,b,c,d){if(l)return N(26,1,a,b,c,d);try{a:{for(var e=jc(a),g=a=0;g<c;g++){var k=E[b+(8*g+4)>>2],m=O.read(e,y,E[b+8*g>>2],k,void 0);if(0>m){var r=-1;break a}a+=m;if(m<k)break}r=a}E[d>>2]=r;return 0}catch(q){return"undefined"!==typeof O&&q instanceof O.af||n(q),q.ef}}
|
||||
function Pd(a,b,c,d,e){if(l)return N(27,1,a,b,c,d,e);try{var g=jc(a);a=4294967296*c+(b>>>0);if(-9007199254740992>=a||9007199254740992<=a)return-61;O.tf(g,a,d);L=[g.position>>>0,(J=g.position,1<=+Math.abs(J)?0<J?(Math.min(+Math.floor(J/4294967296),4294967295)|0)>>>0:~~+Math.ceil((J-+(~~J>>>0))/4294967296)>>>0:0)];E[e>>2]=L[0];E[e+4>>2]=L[1];g.Lf&&0===a&&0===d&&(g.Lf=null);return 0}catch(k){return"undefined"!==typeof O&&k instanceof O.af||n(k),k.ef}}
|
||||
function Qd(a,b,c,d){if(l)return N(28,1,a,b,c,d);try{a:{for(var e=jc(a),g=a=0;g<c;g++){var k=O.write(e,y,E[b+8*g>>2],E[b+(8*g+4)>>2],void 0);if(0>k){var m=-1;break a}a+=k}m=a}E[d>>2]=m;return 0}catch(r){return"undefined"!==typeof O&&r instanceof O.af||n(r),r.ef}}var Rd={};
|
||||
function Sd(a){Sd.buffer||(Sd.buffer=Ma(256),Rd["0"]="Success",Rd["-1"]="Invalid value for 'ai_flags' field",Rd["-2"]="NAME or SERVICE is unknown",Rd["-3"]="Temporary failure in name resolution",Rd["-4"]="Non-recoverable failure in name res",Rd["-6"]="'ai_family' not supported",Rd["-7"]="'ai_socktype' not supported",Rd["-8"]="SERVICE not supported for 'ai_socktype'",Rd["-10"]="Memory allocation failure",Rd["-11"]="System error returned in 'errno'",Rd["-12"]="Argument buffer overflow");var b="Unknown error";
|
||||
a in Rd&&(255<Rd[a].length?b="Message too long":b=Rd[a]);Pa(b,Sd.buffer);return Sd.buffer}
|
||||
function Td(a,b,c,d){function e(w,B,p,x,z,I){var W=10===w?28:16;z=10===w?Mc(z):Lc(z);W=Ma(W);z=Pc(W,w,z,I);assert(!z.ef);z=Ma(32);E[z+4>>2]=w;E[z+8>>2]=B;E[z+12>>2]=p;E[z+24>>2]=x;E[z+20>>2]=W;E[z+16>>2]=10===w?28:16;E[z+28>>2]=0;return z}if(l)return N(29,1,a,b,c,d);var g=0,k=0,m=0,r=0,q=0,t=0;c&&(m=E[c>>2],r=E[c+4>>2],q=E[c+8>>2],t=E[c+12>>2]);q&&!t&&(t=2===q?17:6);!q&&t&&(q=17===t?2:1);0===t&&(t=6);0===q&&(q=1);if(!a&&!b)return-2;if(m&-1088||0!==c&&E[c>>2]&2&&!a)return-1;if(m&32)return-2;if(0!==
|
||||
q&&1!==q&&2!==q)return-7;if(0!==r&&2!==r&&10!==r)return-6;if(b&&(b=C(b),k=parseInt(b,10),isNaN(k)))return m&1024?-2:-8;if(!a)return 0===r&&(r=2),0===(m&1)&&(2===r?g=Ud(2130706433):g=[0,0,0,1]),a=e(r,q,t,null,g,k),E[d>>2]=a,0;a=C(a);g=Dc(a);if(null!==g)if(0===r||2===r)r=2;else if(10===r&&m&8)g=[0,0,Ud(65535),g],r=10;else return-2;else if(g=Ec(a),null!==g)if(0===r||10===r)r=10;else return-2;if(null!=g)return a=e(r,q,t,a,g,k),E[d>>2]=a,0;if(m&4)return-2;a=Jc(a);g=Dc(a);0===r?r=2:10===r&&(g=[0,0,Ud(65535),
|
||||
g]);a=e(r,q,t,null,g,k);E[d>>2]=a;return 0}
|
||||
function Bb(a){if(l)throw"Internal Error! spawnThread() can only ever be called from main application thread!";var b=M.li();if(void 0!==b.yf)throw"Internal error!";if(!a.$f)throw"Internal error, no pthread ptr!";M.Kf.push(b);for(var c=Ma(512),d=0;128>d;++d)E[c+4*d>>2]=0;var e=a.Rf+a.cg;d=M.Ef[a.$f]={worker:b,Rf:a.Rf,cg:a.cg,Kg:a.Kg,Lh:a.$f,threadInfoStruct:a.$f};var g=d.threadInfoStruct>>2;Atomics.store(F,g,0);Atomics.store(F,g+1,0);Atomics.store(F,g+2,0);Atomics.store(F,g+17,a.detached);Atomics.store(F,
|
||||
g+26,c);Atomics.store(F,g+12,0);Atomics.store(F,g+10,d.threadInfoStruct);Atomics.store(F,g+11,42);Atomics.store(F,g+27,a.cg);Atomics.store(F,g+21,a.cg);Atomics.store(F,g+20,e);Atomics.store(F,g+29,e);Atomics.store(F,g+30,a.detached);Atomics.store(F,g+32,a.Ih);Atomics.store(F,g+33,a.Jh);c=Vd()+40;Atomics.store(F,g+44,c);b.yf=d;var k={cmd:"run",start_routine:a.Mi,arg:a.Tf,threadInfoStruct:a.$f,selfThreadId:a.$f,parentThreadId:a.Fi,stackBase:a.Rf,stackSize:a.cg};b.og=function(){k.time=performance.now();
|
||||
b.postMessage(k,a.Vi)};b.loaded&&(b.og(),delete b.og)}function Wd(){return pb|0}f._pthread_self=Wd;
|
||||
function Xd(a,b){if(!a)return u("pthread_join attempted on a null thread pointer!"),71;if(l&&selfThreadId==a)return u("PThread "+a+" is attempting to join to itself!"),16;if(!l&&M.xf==a)return u("Main thread "+a+" is attempting to join to itself!"),16;if(E[a+12>>2]!==a)return u("pthread_join attempted on thread "+a+", which does not point to a valid thread, or does not exist anymore!"),71;if(Atomics.load(F,a+68>>2))return u("Attempted to join thread "+a+", which was already detached!"),28;for(Uc();;){var c=
|
||||
Atomics.load(F,a>>2);if(1==c)return c=Atomics.load(F,a+4>>2),b&&(E[b>>2]=c),Atomics.store(F,a+68>>2,1),l?postMessage({cmd:"cleanupThread",thread:a}):vb(a),0;if(l&&threadInfoStruct&&!Atomics.load(F,threadInfoStruct+60>>2)&&2==Atomics.load(F,threadInfoStruct+0>>2))throw"Canceled!";l||Ab();Vc(a,c,l?100:1)}}function Yd(a){return 0===a%4&&(0!==a%100||0===a%400)}function Zd(a,b){for(var c=0,d=0;d<=b;c+=a[d++]);return c}var $d=[31,29,31,30,31,30,31,31,30,31,30,31],ae=[31,28,31,30,31,30,31,31,30,31,30,31];
|
||||
function be(a,b){for(a=new Date(a.getTime());0<b;){var c=a.getMonth(),d=(Yd(a.getFullYear())?$d:ae)[c];if(b>d-a.getDate())b-=d-a.getDate()+1,a.setDate(1),11>c?a.setMonth(c+1):(a.setMonth(0),a.setFullYear(a.getFullYear()+1));else{a.setDate(a.getDate()+b);break}}return a}
|
||||
function ce(a){if(l)return N(30,1,a);switch(a){case 30:return 16384;case 85:return v.length/16384;case 132:case 133:case 12:case 137:case 138:case 15:case 235:case 16:case 17:case 18:case 19:case 20:case 149:case 13:case 10:case 236:case 153:case 9:case 21:case 22:case 159:case 154:case 14:case 77:case 78:case 139:case 80:case 81:case 82:case 68:case 67:case 164:case 11:case 29:case 47:case 48:case 95:case 52:case 51:case 46:case 79:return 200809;case 27:case 246:case 127:case 128:case 23:case 24:case 160:case 161:case 181:case 182:case 242:case 183:case 184:case 243:case 244:case 245:case 165:case 178:case 179:case 49:case 50:case 168:case 169:case 175:case 170:case 171:case 172:case 97:case 76:case 32:case 173:case 35:return-1;
|
||||
case 176:case 177:case 7:case 155:case 8:case 157:case 125:case 126:case 92:case 93:case 129:case 130:case 131:case 94:case 91:return 1;case 74:case 60:case 69:case 70:case 4:return 1024;case 31:case 42:case 72:return 32;case 87:case 26:case 33:return 2147483647;case 34:case 1:return 47839;case 38:case 36:return 99;case 43:case 37:return 2048;case 0:return 2097152;case 3:return 65536;case 28:return 32768;case 44:return 32767;case 75:return 16384;case 39:return 1E3;case 89:return 700;case 71:return 256;
|
||||
case 40:return 255;case 2:return 100;case 180:return 64;case 25:return 20;case 5:return 16;case 6:return 6;case 73:return 4;case 84:return"object"===typeof navigator?navigator.hardwareConcurrency||1:1}Eb(28);return-1}function de(a,b,c,d){a||(a=this);this.parent=a;this.jf=a.jf;this.lg=null;this.id=O.Ai++;this.name=b;this.mode=c;this.cf={};this.df={};this.rdev=d}
|
||||
Object.defineProperties(de.prototype,{read:{get:function(){return 365===(this.mode&365)},set:function(a){a?this.mode|=365:this.mode&=-366}},write:{get:function(){return 146===(this.mode&146)},set:function(a){a?this.mode|=146:this.mode&=-147}},ti:{get:function(){return O.kf(this.mode)}},Ug:{get:function(){return O.hg(this.mode)}}});O.Oh=de;O.Ni();for(var ec,V,ee=0;32>ee;++ee)nd.push(Array(ee));var fe=new Float32Array(288);for(ee=0;288>ee;++ee)yd[ee]=fe.subarray(0,ee+1);var je=new Int32Array(288);
|
||||
for(ee=0;288>ee;++ee)zd[ee]=je.subarray(0,ee+1);var ke=[null,Hb,Jb,kc,lc,mc,nc,oc,pc,rc,sc,tc,uc,wc,xc,yc,zc,Ac,Bc,Cc,Qc,Rc,Sc,Hd,Md,Nd,Od,Pd,Qd,Td,ce];function Zb(a,b){var c=Array(Ka(a)+1);a=Ia(a,c,0,c.length);b&&(c.length=a);return c}l||Wa.push({vh:function(){le()}});
|
||||
var Fe={c:function(a,b,c,d){n("Assertion failed: "+C(a)+", at: "+[b?C(b):"unknown filename",c,d?C(d):"unknown function"])},K:function(a,b){a=me(a,b);if(!noExitRuntime)return postMessage({cmd:"exitProcess",returnCode:a}),a},W:function(a,b){return Gb(a,b)},aa:function(a,b){return Hb(a,b)},va:function(a,b){return Ib(a,b)},ua:function(a,b){return Nb(a,b)},Ma:kc,Ea:lc,u:mc,Na:nc,Ka:oc,Ha:pc,V:rc,Oa:sc,Pa:tc,ya:uc,Aa:function(){return 0},za:wc,Da:function(){return-63},Y:xc,La:yc,Ja:zc,Ca:Ac,wa:Bc,Ga:Cc,
|
||||
Ia:function(){return 0},t:Qc,X:Rc,Fa:function(a){try{if(!a)return-21;var b={__size__:390,sysname:0,nodename:65,release:130,version:195,machine:260,domainname:325};Pa("Emscripten",a+b.sysname);Pa("emscripten",a+b.nodename);Pa("1.0",a+b.release);Pa("#1",a+b.version);Pa("x86-JS",a+b.machine);return 0}catch(c){return"undefined"!==typeof O&&c instanceof O.af||n(c),-c.ef}},Ba:Sc,pa:function(a,b){if(a==b)postMessage({cmd:"processQueuedMainThreadWork"});else if(l)postMessage({targetThread:a,cmd:"processThreadQueue"});
|
||||
else{a=(a=M.Ef[a])&&a.worker;if(!a)return;a.postMessage({cmd:"processThreadQueue"})}return 1},b:function(){n()},Qa:Tc,Ta:Gb,$:function(){n("To use dlopen, you need to use Emscripten's linking support, see https://github.com/emscripten-core/emscripten/wiki/Linking")},Ua:function(){n("To use dlopen, you need to use Emscripten's linking support, see https://github.com/emscripten-core/emscripten/wiki/Linking")},F:function(a,b,c){Cd.length=0;var d;for(c>>=2;d=v[b++];)(d=105>d)&&c&1&&c++,Cd.push(d?Sa[c++>>
|
||||
1]:E[c]),++c;return mb[a].apply(null,Cd)},qa:Uc,I:function(){},A:Vc,p:tb,z:Db,Ed:function(a){V.activeTexture(a)},Dd:function(a,b){V.attachShader(S[a],dd[b])},ea:function(a,b){V.uf.beginQueryEXT(a,fd[b])},Cd:function(a,b,c){V.bindAttribLocation(S[a],b,C(c))},Bd:function(a,b){V.bindBuffer(a,$c[b])},Ad:function(a,b){V.bindFramebuffer(a,ad[b])},zd:function(a,b){V.bindRenderbuffer(a,bd[b])},yd:function(a,b){V.bindTexture(a,cd[b])},Md:function(a){V.bindVertexArray(ed[a])},xd:function(a,b,c,d){V.blendColor(a,
|
||||
b,c,d)},wd:function(a){V.blendEquation(a)},vd:function(a,b){V.blendEquationSeparate(a,b)},ud:function(a,b){V.blendFunc(a,b)},td:function(a,b,c,d){V.blendFuncSeparate(a,b,c,d)},sd:function(a,b,c,d){V.bufferData(a,c?v.subarray(c,c+b):b,d)},rd:function(a,b,c,d){V.bufferSubData(a,b,v.subarray(d,d+c))},qd:function(a){return V.checkFramebufferStatus(a)},pd:function(a){V.clear(a)},od:function(a,b,c,d){V.clearColor(a,b,c,d)},nd:function(a){V.clearDepth(a)},md:function(a){V.clearStencil(a)},ld:function(a,
|
||||
b,c,d){V.colorMask(!!a,!!b,!!c,!!d)},kd:function(a){V.compileShader(dd[a])},jd:function(a,b,c,d,e,g,k,m){V.compressedTexImage2D(a,b,c,d,e,g,m?v.subarray(m,m+k):null)},id:function(a,b,c,d,e,g,k,m,r){V.compressedTexSubImage2D(a,b,c,d,e,g,k,r?v.subarray(r,r+m):null)},hd:function(a,b,c,d,e,g,k,m){V.copyTexImage2D(a,b,c,d,e,g,k,m)},gd:function(a,b,c,d,e,g,k,m){V.copyTexSubImage2D(a,b,c,d,e,g,k,m)},fd:function(){var a=kd(S),b=V.createProgram();b.name=a;S[a]=b;return a},ed:function(a){var b=kd(dd);dd[b]=
|
||||
V.createShader(a);return b},dd:function(a){V.cullFace(a)},cd:function(a,b){for(var c=0;c<a;c++){var d=E[b+4*c>>2],e=$c[d];e&&(V.deleteBuffer(e),e.name=0,$c[d]=null)}},bd:function(a,b){for(var c=0;c<a;++c){var d=E[b+4*c>>2],e=ad[d];e&&(V.deleteFramebuffer(e),e.name=0,ad[d]=null)}},ad:function(a){if(a){var b=S[a];b?(V.deleteProgram(b),b.name=0,S[a]=null,gd[a]=null):U(1281)}},ga:function(a,b){for(var c=0;c<a;c++){var d=E[b+4*c>>2],e=fd[d];e&&(V.uf.deleteQueryEXT(e),fd[d]=null)}},$c:function(a,b){for(var c=
|
||||
0;c<a;c++){var d=E[b+4*c>>2],e=bd[d];e&&(V.deleteRenderbuffer(e),e.name=0,bd[d]=null)}},_c:function(a){if(a){var b=dd[a];b?(V.deleteShader(b),dd[a]=null):U(1281)}},Zc:function(a,b){for(var c=0;c<a;c++){var d=E[b+4*c>>2],e=cd[d];e&&(V.deleteTexture(e),e.name=0,cd[d]=null)}},Ld:function(a,b){for(var c=0;c<a;c++){var d=E[b+4*c>>2];V.deleteVertexArray(ed[d]);ed[d]=null}},Yc:function(a){V.depthFunc(a)},Xc:function(a){V.depthMask(!!a)},Wc:function(a,b){V.depthRange(a,b)},Vc:function(a,b){V.detachShader(S[a],
|
||||
dd[b])},Uc:function(a){V.disable(a)},Tc:function(a){V.disableVertexAttribArray(a)},Sc:function(a,b,c){V.drawArrays(a,b,c)},Hd:function(a,b,c,d){V.drawArraysInstanced(a,b,c,d)},Id:function(a,b){for(var c=nd[a],d=0;d<a;d++)c[d]=E[b+4*d>>2];V.drawBuffers(c)},Rc:function(a,b,c,d){V.drawElements(a,b,c,d)},Gd:function(a,b,c,d,e){V.drawElementsInstanced(a,b,c,d,e)},Qc:function(a){V.enable(a)},Pc:function(a){V.enableVertexAttribArray(a)},da:function(a){V.uf.endQueryEXT(a)},Oc:function(){V.finish()},Nc:function(){V.flush()},
|
||||
Mc:function(a,b,c,d){V.framebufferRenderbuffer(a,b,c,bd[d])},Lc:function(a,b,c,d,e){V.framebufferTexture2D(a,b,c,cd[d],e)},Kc:function(a){V.frontFace(a)},Jc:function(a,b){od(a,b,"createBuffer",$c)},Hc:function(a,b){od(a,b,"createFramebuffer",ad)},ha:function(a,b){for(var c=0;c<a;c++){var d=V.uf.createQueryEXT();if(!d){for(U(1282);c<a;)E[b+4*c++>>2]=0;break}var e=kd(fd);d.name=e;fd[e]=d;E[b+4*c>>2]=e}},Gc:function(a,b){od(a,b,"createRenderbuffer",bd)},Fc:function(a,b){od(a,b,"createTexture",cd)},Kd:function(a,
|
||||
b){od(a,b,"createVertexArray",ed)},Ic:function(a){V.generateMipmap(a)},Ec:function(a,b,c,d,e,g,k){rd("getActiveAttrib",a,b,c,d,e,g,k)},Dc:function(a,b,c,d,e,g,k){rd("getActiveUniform",a,b,c,d,e,g,k)},Cc:function(a,b,c,d){a=V.getAttachedShaders(S[a]);var e=a.length;e>b&&(e=b);E[c>>2]=e;for(b=0;b<e;++b)E[d+4*b>>2]=dd.indexOf(a[b])},Bc:function(a,b){return V.getAttribLocation(S[a],C(b))},Ac:function(a,b){td(a,b,4)},zc:function(a,b,c){c?E[c>>2]=V.getBufferParameter(a,b):U(1281)},yc:function(){var a=V.getError()||
|
||||
jd;jd=0;return a},xc:function(a,b){td(a,b,2)},wc:function(a,b,c,d){a=V.getFramebufferAttachmentParameter(a,b,c);if(a instanceof WebGLRenderbuffer||a instanceof WebGLTexture)a=a.name|0;E[d>>2]=a},vc:function(a,b){td(a,b,0)},tc:function(a,b,c,d){a=V.getProgramInfoLog(S[a]);null===a&&(a="(unknown error)");b=0<b&&d?Ia(a,v,d,b):0;c&&(E[c>>2]=b)},uc:function(a,b,c){if(c)if(a>=Zc)U(1281);else{var d=gd[a];if(d)if(35716==b)a=V.getProgramInfoLog(S[a]),null===a&&(a="(unknown error)"),E[c>>2]=a.length+1;else if(35719==
|
||||
b)E[c>>2]=d.Xg;else if(35722==b){if(-1==d.jg){a=S[a];var e=V.getProgramParameter(a,35721);for(b=d.jg=0;b<e;++b)d.jg=Math.max(d.jg,V.getActiveAttrib(a,b).name.length+1)}E[c>>2]=d.jg}else if(35381==b){if(-1==d.kg)for(a=S[a],e=V.getProgramParameter(a,35382),b=d.kg=0;b<e;++b)d.kg=Math.max(d.kg,V.getActiveUniformBlockName(a,b).length+1);E[c>>2]=d.kg}else E[c>>2]=V.getProgramParameter(S[a],b);else U(1282)}else U(1281)},Od:function(a,b,c){if(c){a=V.uf.getQueryObjectEXT(fd[a],b);var d;"boolean"==typeof a?
|
||||
d=a?1:0:d=a;sd(c,d)}else U(1281)},Qd:function(a,b,c){if(c){a=V.uf.getQueryObjectEXT(fd[a],b);var d;"boolean"==typeof a?d=a?1:0:d=a;E[c>>2]=d}else U(1281)},Nd:function(a,b,c){if(c){a=V.uf.getQueryObjectEXT(fd[a],b);var d;"boolean"==typeof a?d=a?1:0:d=a;sd(c,d)}else U(1281)},Pd:function(a,b,c){if(c){a=V.uf.getQueryObjectEXT(fd[a],b);var d;"boolean"==typeof a?d=a?1:0:d=a;E[c>>2]=d}else U(1281)},Rd:function(a,b,c){c?E[c>>2]=V.uf.getQueryEXT(a,b):U(1281)},sc:function(a,b,c){c?E[c>>2]=V.getRenderbufferParameter(a,
|
||||
b):U(1281)},qc:function(a,b,c,d){a=V.getShaderInfoLog(dd[a]);null===a&&(a="(unknown error)");b=0<b&&d?Ia(a,v,d,b):0;c&&(E[c>>2]=b)},pc:function(a,b,c,d){a=V.getShaderPrecisionFormat(a,b);E[c>>2]=a.rangeMin;E[c+4>>2]=a.rangeMax;E[d>>2]=a.precision},oc:function(a,b,c,d){if(a=V.getShaderSource(dd[a]))b=0<b&&d?Ia(a,v,d,b):0,c&&(E[c>>2]=b)},rc:function(a,b,c){c?35716==b?(a=V.getShaderInfoLog(dd[a]),null===a&&(a="(unknown error)"),E[c>>2]=a?a.length+1:0):35720==b?(a=V.getShaderSource(dd[a]),E[c>>2]=a?a.length+
|
||||
1:0):E[c>>2]=V.getShaderParameter(dd[a],b):U(1281)},nc:function(a){if(hd[a])return hd[a];switch(a){case 7939:var b=V.getSupportedExtensions()||[];b=b.concat(b.map(function(d){return"GL_"+d}));b=ud(b.join(" "));break;case 7936:case 7937:case 37445:case 37446:(b=V.getParameter(a))||U(1280);b=ud(b);break;case 7938:b=ud("OpenGL ES 2.0 ("+V.getParameter(7938)+")");break;case 35724:b=V.getParameter(35724);var c=b.match(/^WebGL GLSL ES ([0-9]\.[0-9][0-9]?)(?:$| .*)/);null!==c&&(3==c[1].length&&(c[1]+="0"),
|
||||
b="OpenGL ES GLSL ES "+c[1]+" ("+b+")");b=ud(b);break;default:return U(1280),0}return hd[a]=b},mc:function(a,b,c){c?G[c>>2]=V.getTexParameter(a,b):U(1281)},lc:function(a,b,c){c?E[c>>2]=V.getTexParameter(a,b):U(1281)},ic:function(a,b){b=C(b);var c=0;if("]"==b[b.length-1]){var d=b.lastIndexOf("[");c="]"!=b[d+1]?parseInt(b.slice(d+1)):0;b=b.slice(0,d)}return(a=gd[a]&&gd[a].Nh[b])&&0<=c&&c<a[0]?a[1]+c:-1},kc:function(a,b,c){vd(a,b,c,2)},jc:function(a,b,c){vd(a,b,c,0)},fc:function(a,b,c){c?E[c>>2]=V.getVertexAttribOffset(a,
|
||||
b):U(1281)},hc:function(a,b,c){wd(a,b,c,2)},gc:function(a,b,c){wd(a,b,c,5)},ec:function(a,b){V.hint(a,b)},dc:function(a){return(a=$c[a])?V.isBuffer(a):0},cc:function(a){return V.isEnabled(a)},bc:function(a){return(a=ad[a])?V.isFramebuffer(a):0},ac:function(a){return(a=S[a])?V.isProgram(a):0},fa:function(a){return(a=fd[a])?V.uf.isQueryEXT(a):0},$b:function(a){return(a=bd[a])?V.isRenderbuffer(a):0},_b:function(a){return(a=dd[a])?V.isShader(a):0},Zb:function(a){return(a=cd[a])?V.isTexture(a):0},Jd:function(a){return(a=
|
||||
ed[a])?V.isVertexArray(a):0},Yb:function(a){V.lineWidth(a)},Xb:function(a){V.linkProgram(S[a]);var b=S[a];a=gd[a]={Nh:{},Xg:0,jg:-1,kg:-1};for(var c=a.Nh,d=V.getProgramParameter(b,35718),e=0;e<d;++e){var g=V.getActiveUniform(b,e),k=g.name;a.Xg=Math.max(a.Xg,k.length+1);"]"==k.slice(-1)&&(k=k.slice(0,k.lastIndexOf("[")));var m=V.getUniformLocation(b,k);if(m){var r=kd(T);c[k]=[g.size,r];T[r]=m;for(var q=1;q<g.size;++q)m=V.getUniformLocation(b,k+"["+q+"]"),r=kd(T),T[r]=m}}},Wb:function(a,b){3317==a&&
|
||||
(id=b);V.pixelStorei(a,b)},Vb:function(a,b){V.polygonOffset(a,b)},ca:function(a,b){V.uf.queryCounterEXT(fd[a],b)},Ub:function(a,b,c,d,e,g,k){(k=xd(g,e,c,d,k))?V.readPixels(a,b,c,d,e,g,k):U(1280)},Tb:function(){},Sb:function(a,b,c,d){V.renderbufferStorage(a,b,c,d)},Rb:function(a,b){V.sampleCoverage(a,!!b)},Qb:function(a,b,c,d){V.scissor(a,b,c,d)},Pb:function(){U(1280)},Ob:function(a,b,c,d){for(var e="",g=0;g<b;++g){var k=d?E[d+4*g>>2]:-1;e+=C(E[c+4*g>>2],0>k?void 0:k)}V.shaderSource(dd[a],e)},Nb:function(a,
|
||||
b,c){V.stencilFunc(a,b,c)},Mb:function(a,b,c,d){V.stencilFuncSeparate(a,b,c,d)},Lb:function(a){V.stencilMask(a)},Kb:function(a,b){V.stencilMaskSeparate(a,b)},Jb:function(a,b,c){V.stencilOp(a,b,c)},Ib:function(a,b,c,d){V.stencilOpSeparate(a,b,c,d)},Hb:function(a,b,c,d,e,g,k,m,r){V.texImage2D(a,b,c,d,e,g,k,m,r?xd(m,k,d,e,r):null)},Gb:function(a,b,c){V.texParameterf(a,b,c)},Fb:function(a,b,c){V.texParameterf(a,b,G[c>>2])},Eb:function(a,b,c){V.texParameteri(a,b,c)},Db:function(a,b,c){V.texParameteri(a,
|
||||
b,E[c>>2])},Cb:function(a,b,c,d,e,g,k,m,r){var q=null;r&&(q=xd(m,k,e,g,r));V.texSubImage2D(a,b,c,d,e,g,k,m,q)},Bb:function(a,b){V.uniform1f(T[a],b)},Ab:function(a,b,c){if(288>=b)for(var d=yd[b-1],e=0;e<b;++e)d[e]=G[c+4*e>>2];else d=G.subarray(c>>2,c+4*b>>2);V.uniform1fv(T[a],d)},zb:function(a,b){V.uniform1i(T[a],b)},yb:function(a,b,c){if(288>=b)for(var d=zd[b-1],e=0;e<b;++e)d[e]=E[c+4*e>>2];else d=E.subarray(c>>2,c+4*b>>2);V.uniform1iv(T[a],d)},xb:function(a,b,c){V.uniform2f(T[a],b,c)},wb:function(a,
|
||||
b,c){if(144>=b)for(var d=yd[2*b-1],e=0;e<2*b;e+=2)d[e]=G[c+4*e>>2],d[e+1]=G[c+(4*e+4)>>2];else d=G.subarray(c>>2,c+8*b>>2);V.uniform2fv(T[a],d)},vb:function(a,b,c){V.uniform2i(T[a],b,c)},ub:function(a,b,c){if(144>=b)for(var d=zd[2*b-1],e=0;e<2*b;e+=2)d[e]=E[c+4*e>>2],d[e+1]=E[c+(4*e+4)>>2];else d=E.subarray(c>>2,c+8*b>>2);V.uniform2iv(T[a],d)},tb:function(a,b,c,d){V.uniform3f(T[a],b,c,d)},sb:function(a,b,c){if(96>=b)for(var d=yd[3*b-1],e=0;e<3*b;e+=3)d[e]=G[c+4*e>>2],d[e+1]=G[c+(4*e+4)>>2],d[e+2]=
|
||||
G[c+(4*e+8)>>2];else d=G.subarray(c>>2,c+12*b>>2);V.uniform3fv(T[a],d)},rb:function(a,b,c,d){V.uniform3i(T[a],b,c,d)},qb:function(a,b,c){if(96>=b)for(var d=zd[3*b-1],e=0;e<3*b;e+=3)d[e]=E[c+4*e>>2],d[e+1]=E[c+(4*e+4)>>2],d[e+2]=E[c+(4*e+8)>>2];else d=E.subarray(c>>2,c+12*b>>2);V.uniform3iv(T[a],d)},pb:function(a,b,c,d,e){V.uniform4f(T[a],b,c,d,e)},ob:function(a,b,c){if(72>=b){var d=yd[4*b-1];c>>=2;for(var e=0;e<4*b;e+=4){var g=c+e;d[e]=G[g];d[e+1]=G[g+1];d[e+2]=G[g+2];d[e+3]=G[g+3]}}else d=G.subarray(c>>
|
||||
2,c+16*b>>2);V.uniform4fv(T[a],d)},nb:function(a,b,c,d,e){V.uniform4i(T[a],b,c,d,e)},mb:function(a,b,c){if(72>=b)for(var d=zd[4*b-1],e=0;e<4*b;e+=4)d[e]=E[c+4*e>>2],d[e+1]=E[c+(4*e+4)>>2],d[e+2]=E[c+(4*e+8)>>2],d[e+3]=E[c+(4*e+12)>>2];else d=E.subarray(c>>2,c+16*b>>2);V.uniform4iv(T[a],d)},lb:function(a,b,c,d){if(72>=b)for(var e=yd[4*b-1],g=0;g<4*b;g+=4)e[g]=G[d+4*g>>2],e[g+1]=G[d+(4*g+4)>>2],e[g+2]=G[d+(4*g+8)>>2],e[g+3]=G[d+(4*g+12)>>2];else e=G.subarray(d>>2,d+16*b>>2);V.uniformMatrix2fv(T[a],
|
||||
!!c,e)},kb:function(a,b,c,d){if(32>=b)for(var e=yd[9*b-1],g=0;g<9*b;g+=9)e[g]=G[d+4*g>>2],e[g+1]=G[d+(4*g+4)>>2],e[g+2]=G[d+(4*g+8)>>2],e[g+3]=G[d+(4*g+12)>>2],e[g+4]=G[d+(4*g+16)>>2],e[g+5]=G[d+(4*g+20)>>2],e[g+6]=G[d+(4*g+24)>>2],e[g+7]=G[d+(4*g+28)>>2],e[g+8]=G[d+(4*g+32)>>2];else e=G.subarray(d>>2,d+36*b>>2);V.uniformMatrix3fv(T[a],!!c,e)},jb:function(a,b,c,d){if(18>=b){var e=yd[16*b-1];d>>=2;for(var g=0;g<16*b;g+=16){var k=d+g;e[g]=G[k];e[g+1]=G[k+1];e[g+2]=G[k+2];e[g+3]=G[k+3];e[g+4]=G[k+4];
|
||||
e[g+5]=G[k+5];e[g+6]=G[k+6];e[g+7]=G[k+7];e[g+8]=G[k+8];e[g+9]=G[k+9];e[g+10]=G[k+10];e[g+11]=G[k+11];e[g+12]=G[k+12];e[g+13]=G[k+13];e[g+14]=G[k+14];e[g+15]=G[k+15]}}else e=G.subarray(d>>2,d+64*b>>2);V.uniformMatrix4fv(T[a],!!c,e)},ib:function(a){V.useProgram(S[a])},hb:function(a){V.validateProgram(S[a])},gb:function(a,b){V.vertexAttrib1f(a,b)},fb:function(a,b){V.vertexAttrib1f(a,G[b>>2])},eb:function(a,b,c){V.vertexAttrib2f(a,b,c)},db:function(a,b){V.vertexAttrib2f(a,G[b>>2],G[b+4>>2])},cb:function(a,
|
||||
b,c,d){V.vertexAttrib3f(a,b,c,d)},bb:function(a,b){V.vertexAttrib3f(a,G[b>>2],G[b+4>>2],G[b+8>>2])},ab:function(a,b,c,d,e){V.vertexAttrib4f(a,b,c,d,e)},$a:function(a,b){V.vertexAttrib4f(a,G[b>>2],G[b+4>>2],G[b+8>>2],G[b+12>>2])},Fd:function(a,b){V.vertexAttribDivisor(a,b)},_a:function(a,b,c,d,e,g){V.vertexAttribPointer(a,b,c,!!d,e,g)},Za:function(a,b,c,d){V.viewport(a,b,c,d)},ka:function(){return"undefined"!==typeof SharedArrayBuffer},G:function(){return rb|0},R:function(){return qb|0},f:function(a,
|
||||
b){Z(a,b||1);throw"longjmp";},ja:function(a,b,c){v.copyWithin(a,b,b+c)},ma:function(a,b,c){Bd.length=b;c>>=3;for(var d=0;d<b;d++)Bd[d]=Sa[c+d];return(0>a?mb[-a-1]:ke[a]).apply(null,Bd)},ra:function(){n("OOM")},na:function(a,b,c){return Ed(a)?Fd(a,b,c):Hd(a,b,c)},Q:function(){},la:function(){},oa:function(a,b){var c={};b>>=2;c.alpha=!!E[b];c.depth=!!E[b+1];c.stencil=!!E[b+2];c.antialias=!!E[b+3];c.premultipliedAlpha=!!E[b+4];c.preserveDrawingBuffer=!!E[b+5];c.powerPreference=Id[E[b+6]];c.failIfMajorPerformanceCaveat=
|
||||
!!E[b+7];c.wi=E[b+8];c.pj=E[b+9];c.qh=E[b+10];c.hi=E[b+11];c.uj=E[b+12];c.vj=E[b+13];a=Ed(a);if(!a||c.hi)c=0;else if(a=a.getContext("webgl",c)){b=Ma(8);E[b+4>>2]=pb|0;var d={jj:b,attributes:c,version:c.wi,qg:a};a.canvas&&(a.canvas.rg=d);("undefined"===typeof c.qh||c.qh)&&ld(d);c=b}else c=0;return c},sa:function(a,b){var c=0;Kd().forEach(function(d,e){var g=b+c;E[a+4*e>>2]=g;Pa(d,g);c+=d.length+1});return 0},ta:function(a,b){var c=Kd();E[a>>2]=c.length;var d=0;c.forEach(function(e){d+=e.length+1});
|
||||
E[b>>2]=d;return 0},D:function(a){Cb(a)},H:Md,U:Nd,xa:Od,Va:Pd,M:Qd,B:Sd,d:function(){return Aa|0},y:Td,v:function(a,b,c,d,e,g,k){b=Oc(a,b);if(b.ef)return-6;a=b.port;var m=b.hf;b=!1;if(c&&d){var r;if(k&1||!(r=Kc(m))){if(k&8)return-2}else m=r;c=Ia(m,v,c,d);c+1>=d&&(b=!0)}e&&g&&(c=Ia(""+a,v,e,g),c+1>=g&&(b=!0));return b?-12:0},l:function(a){var b=Date.now();E[a>>2]=b/1E3|0;E[a+4>>2]=b%1E3*1E3|0;return 0},r:Ib,ia:function(){M.pi()},ba:ne,j:oe,h:pe,C:qe,P:re,_:se,O:te,Xa:ue,Wa:ve,k:we,w:xe,J:ye,g:ze,
|
||||
N:Ae,Sa:Be,Z:Ce,Ya:De,q:Nb,a:Ca||f.wasmMemory,T:function(a){Jb();var b=new Date(E[a+20>>2]+1900,E[a+16>>2],E[a+12>>2],E[a+8>>2],E[a+4>>2],E[a>>2],0),c=E[a+32>>2],d=b.getTimezoneOffset(),e=new Date(b.getFullYear(),0,1),g=(new Date(b.getFullYear(),6,1)).getTimezoneOffset(),k=e.getTimezoneOffset(),m=Math.min(k,g);0>c?E[a+32>>2]=Number(g!=k&&m==d):0<c!=(m==d)&&(g=Math.max(k,g),b.setTime(b.getTime()+6E4*((0<c?m:g)-d)));E[a+24>>2]=b.getDay();E[a+28>>2]=(b.getTime()-e.getTime())/864E5|0;return b.getTime()/
|
||||
1E3|0},Ra:function(a){if(a===M.Ph)return u("Main thread (id="+a+") cannot be canceled!"),71;if(!a)return u("pthread_cancel attempted on a null thread pointer!"),71;if(E[a+12>>2]!==a)return u("pthread_cancel attempted on thread "+a+", which does not point to a valid thread, or does not exist anymore!"),71;Atomics.compareExchange(F,a>>2,0,2);l?postMessage({cmd:"cancelThread",thread:a}):ub(a);return 0},S:function(a){var b=M.Dg.pop();a&&b()},L:function(a,b){M.Dg.push(function(){H.get(a)(b)})},n:function(a,
|
||||
b,c,d){if("undefined"===typeof SharedArrayBuffer)return u("Current environment does not support SharedArrayBuffer, pthreads are not available!"),6;if(!a)return u("pthread_create called with a null thread pointer!"),28;var e=[];if(l&&0===e.length)return Ee(687865856,a,b,c,d);var g=0,k=0,m=0,r=0;if(b){var q=E[b>>2];q+=81920;g=E[b+8>>2];k=0!==E[b+12>>2];if(0===E[b+16>>2]){var t=E[b+20>>2],w=E[b+24>>2];m=b+20;r=b+24;var B=M.Mg?M.Mg:pb|0;if(m||r)if(B)if(E[B+12>>2]!==B)u("pthread_getschedparam attempted on thread "+
|
||||
B+", which does not point to a valid thread, or does not exist anymore!");else{var p=Atomics.load(F,B+128>>2);B=Atomics.load(F,B+132>>2);m&&(E[m>>2]=p);r&&(E[r>>2]=B)}else u("pthread_getschedparam called with a null thread pointer!");m=E[b+20>>2];r=E[b+24>>2];E[b+20>>2]=t;E[b+24>>2]=w}else m=E[b+20>>2],r=E[b+24>>2]}else q=2097152;(b=0==g)?g=vc(16,q):(g-=q,assert(0<g));t=Ma(232);for(w=0;58>w;++w)F[(t>>2)+w]=0;E[a>>2]=t;E[t+12>>2]=t;a=t+156;E[a>>2]=a;c={Rf:g,cg:q,Kg:b,Ih:m,Jh:r,detached:k,Mi:c,$f:t,
|
||||
Fi:pb|0,Tf:d,Vi:e};l?(c.$i="spawnThread",postMessage(c,e)):Bb(c);return 0},o:function(a,b){return Xd(a,b)},i:Wd,e:function(a){Aa=a|0},E:function(){return 0},m:function(a,b,c,d){function e(p,x,z){for(p="number"===typeof p?p.toString():p||"";p.length<x;)p=z[0]+p;return p}function g(p,x){return e(p,x,"0")}function k(p,x){function z(W){return 0>W?-1:0<W?1:0}var I;0===(I=z(p.getFullYear()-x.getFullYear()))&&0===(I=z(p.getMonth()-x.getMonth()))&&(I=z(p.getDate()-x.getDate()));return I}function m(p){switch(p.getDay()){case 0:return new Date(p.getFullYear()-
|
||||
1,11,29);case 1:return p;case 2:return new Date(p.getFullYear(),0,3);case 3:return new Date(p.getFullYear(),0,2);case 4:return new Date(p.getFullYear(),0,1);case 5:return new Date(p.getFullYear()-1,11,31);case 6:return new Date(p.getFullYear()-1,11,30)}}function r(p){p=be(new Date(p.rf+1900,0,1),p.Gg);var x=new Date(p.getFullYear()+1,0,4),z=m(new Date(p.getFullYear(),0,4));x=m(x);return 0>=k(z,p)?0>=k(x,p)?p.getFullYear()+1:p.getFullYear():p.getFullYear()-1}var q=E[d+40>>2];d={Ti:E[d>>2],Si:E[d+4>>
|
||||
2],Eg:E[d+8>>2],pg:E[d+12>>2],dg:E[d+16>>2],rf:E[d+20>>2],Fg:E[d+24>>2],Gg:E[d+28>>2],yj:E[d+32>>2],Ri:E[d+36>>2],Ui:q?C(q):""};c=C(c);q={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};
|
||||
for(var t in q)c=c.replace(new RegExp(t,"g"),q[t]);var w="Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),B="January February March April May June July August September October November December".split(" ");q={"%a":function(p){return w[p.Fg].substring(0,3)},"%A":function(p){return w[p.Fg]},"%b":function(p){return B[p.dg].substring(0,3)},"%B":function(p){return B[p.dg]},"%C":function(p){return g((p.rf+1900)/100|0,2)},"%d":function(p){return g(p.pg,2)},"%e":function(p){return e(p.pg,
|
||||
2," ")},"%g":function(p){return r(p).toString().substring(2)},"%G":function(p){return r(p)},"%H":function(p){return g(p.Eg,2)},"%I":function(p){p=p.Eg;0==p?p=12:12<p&&(p-=12);return g(p,2)},"%j":function(p){return g(p.pg+Zd(Yd(p.rf+1900)?$d:ae,p.dg-1),3)},"%m":function(p){return g(p.dg+1,2)},"%M":function(p){return g(p.Si,2)},"%n":function(){return"\n"},"%p":function(p){return 0<=p.Eg&&12>p.Eg?"AM":"PM"},"%S":function(p){return g(p.Ti,2)},"%t":function(){return"\t"},"%u":function(p){return p.Fg||
|
||||
7},"%U":function(p){var x=new Date(p.rf+1900,0,1),z=0===x.getDay()?x:be(x,7-x.getDay());p=new Date(p.rf+1900,p.dg,p.pg);return 0>k(z,p)?g(Math.ceil((31-z.getDate()+(Zd(Yd(p.getFullYear())?$d:ae,p.getMonth()-1)-31)+p.getDate())/7),2):0===k(z,x)?"01":"00"},"%V":function(p){var x=new Date(p.rf+1901,0,4),z=m(new Date(p.rf+1900,0,4));x=m(x);var I=be(new Date(p.rf+1900,0,1),p.Gg);return 0>k(I,z)?"53":0>=k(x,I)?"01":g(Math.ceil((z.getFullYear()<p.rf+1900?p.Gg+32-z.getDate():p.Gg+1-z.getDate())/7),2)},"%w":function(p){return p.Fg},
|
||||
"%W":function(p){var x=new Date(p.rf,0,1),z=1===x.getDay()?x:be(x,0===x.getDay()?1:7-x.getDay()+1);p=new Date(p.rf+1900,p.dg,p.pg);return 0>k(z,p)?g(Math.ceil((31-z.getDate()+(Zd(Yd(p.getFullYear())?$d:ae,p.getMonth()-1)-31)+p.getDate())/7),2):0===k(z,x)?"01":"00"},"%y":function(p){return(p.rf+1900).toString().substring(2)},"%Y":function(p){return p.rf+1900},"%z":function(p){p=p.Ri;var x=0<=p;p=Math.abs(p)/60;return(x?"+":"-")+String("0000"+(p/60*100+p%60)).slice(-4)},"%Z":function(p){return p.Ui},
|
||||
"%%":function(){return"%"}};for(t in q)0<=c.indexOf(t)&&(c=c.replace(new RegExp(t,"g"),q[t](d)));t=Zb(c,!1);if(t.length>b)return 0;y.set(t,a);return t.length-1},x:ce,s:function(a){var b=Date.now()/1E3|0;a&&(E[a>>2]=b);return b}};
|
||||
(function(){function a(e,g){f.asm=e.exports;H=f.asm.Sd;Da=g;l||fb()}function b(e){a(e.instance,e.module)}function c(e){return kb().then(function(g){return WebAssembly.instantiate(g,d)}).then(e,function(g){u("failed to asynchronously prepare wasm: "+g);n(g)})}var d={a:Fe};l||eb();if(f.instantiateWasm)try{return f.instantiateWasm(d,a)}catch(e){return u("Module.instantiateWasm callback failed with error: "+e),!1}(function(){return Ba||"function"!==typeof WebAssembly.instantiateStreaming||ib()||gb("file://")||
|
||||
"function"!==typeof fetch?c(b):fetch(hb,{credentials:"same-origin"}).then(function(e){return WebAssembly.instantiateStreaming(e,d).then(b,function(g){u("wasm streaming compile failed: "+g);u("falling back to ArrayBuffer instantiation");return c(b)})})})().catch(ca);return{}})();
|
||||
var le=f.___wasm_call_ctors=function(){return(le=f.___wasm_call_ctors=f.asm.Td).apply(null,arguments)},zb=f._free=function(){return(zb=f._free=f.asm.Ud).apply(null,arguments)},Ma=f._malloc=function(){return(Ma=f._malloc=f.asm.Vd).apply(null,arguments)},Fb=f.___errno_location=function(){return(Fb=f.___errno_location=f.asm.Wd).apply(null,arguments)},qc=f._memset=function(){return(qc=f._memset=f.asm.Xd).apply(null,arguments)};f._fflush=function(){return(f._fflush=f.asm.Yd).apply(null,arguments)};
|
||||
var vc=f._memalign=function(){return(vc=f._memalign=f.asm.Zd).apply(null,arguments)},Nc=f._ntohs=function(){return(Nc=f._ntohs=f.asm._d).apply(null,arguments)},Fc=f._htons=function(){return(Fc=f._htons=f.asm.$d).apply(null,arguments)},me=f._main=function(){return(me=f._main=f.asm.ae).apply(null,arguments)},Vd=f._emscripten_get_global_libc=function(){return(Vd=f._emscripten_get_global_libc=f.asm.be).apply(null,arguments)};
|
||||
f.___em_js__initPthreadsJS=function(){return(f.___em_js__initPthreadsJS=f.asm.ce).apply(null,arguments)};
|
||||
var Ud=f._htonl=function(){return(Ud=f._htonl=f.asm.de).apply(null,arguments)},Mb=f.__get_tzname=function(){return(Mb=f.__get_tzname=f.asm.ee).apply(null,arguments)},Lb=f.__get_daylight=function(){return(Lb=f.__get_daylight=f.asm.fe).apply(null,arguments)},Kb=f.__get_timezone=function(){return(Kb=f.__get_timezone=f.asm.ge).apply(null,arguments)},A=f.stackSave=function(){return(A=f.stackSave=f.asm.he).apply(null,arguments)},D=f.stackRestore=function(){return(D=f.stackRestore=f.asm.ie).apply(null,arguments)},
|
||||
Ha=f.stackAlloc=function(){return(Ha=f.stackAlloc=f.asm.je).apply(null,arguments)},Z=f._setThrew=function(){return(Z=f._setThrew=f.asm.ke).apply(null,arguments)};f._emscripten_main_browser_thread_id=function(){return(f._emscripten_main_browser_thread_id=f.asm.le).apply(null,arguments)};
|
||||
var yb=f.___pthread_tsd_run_dtors=function(){return(yb=f.___pthread_tsd_run_dtors=f.asm.me).apply(null,arguments)},Ab=f._emscripten_main_thread_process_queued_calls=function(){return(Ab=f._emscripten_main_thread_process_queued_calls=f.asm.ne).apply(null,arguments)};f._emscripten_current_thread_process_queued_calls=function(){return(f._emscripten_current_thread_process_queued_calls=f.asm.oe).apply(null,arguments)};
|
||||
var wb=f._emscripten_register_main_browser_thread_id=function(){return(wb=f._emscripten_register_main_browser_thread_id=f.asm.pe).apply(null,arguments)},lb=f._do_emscripten_dispatch_to_thread=function(){return(lb=f._do_emscripten_dispatch_to_thread=f.asm.qe).apply(null,arguments)};f._emscripten_async_run_in_main_thread=function(){return(f._emscripten_async_run_in_main_thread=f.asm.re).apply(null,arguments)};
|
||||
f._emscripten_sync_run_in_main_thread=function(){return(f._emscripten_sync_run_in_main_thread=f.asm.se).apply(null,arguments)};f._emscripten_sync_run_in_main_thread_0=function(){return(f._emscripten_sync_run_in_main_thread_0=f.asm.te).apply(null,arguments)};f._emscripten_sync_run_in_main_thread_1=function(){return(f._emscripten_sync_run_in_main_thread_1=f.asm.ue).apply(null,arguments)};
|
||||
f._emscripten_sync_run_in_main_thread_2=function(){return(f._emscripten_sync_run_in_main_thread_2=f.asm.ve).apply(null,arguments)};f._emscripten_sync_run_in_main_thread_xprintf_varargs=function(){return(f._emscripten_sync_run_in_main_thread_xprintf_varargs=f.asm.we).apply(null,arguments)};f._emscripten_sync_run_in_main_thread_3=function(){return(f._emscripten_sync_run_in_main_thread_3=f.asm.xe).apply(null,arguments)};
|
||||
var Ee=f._emscripten_sync_run_in_main_thread_4=function(){return(Ee=f._emscripten_sync_run_in_main_thread_4=f.asm.ye).apply(null,arguments)};f._emscripten_sync_run_in_main_thread_5=function(){return(f._emscripten_sync_run_in_main_thread_5=f.asm.ze).apply(null,arguments)};f._emscripten_sync_run_in_main_thread_6=function(){return(f._emscripten_sync_run_in_main_thread_6=f.asm.Ae).apply(null,arguments)};
|
||||
f._emscripten_sync_run_in_main_thread_7=function(){return(f._emscripten_sync_run_in_main_thread_7=f.asm.Be).apply(null,arguments)};var Ad=f._emscripten_run_in_main_runtime_thread_js=function(){return(Ad=f._emscripten_run_in_main_runtime_thread_js=f.asm.Ce).apply(null,arguments)},Gd=f.__emscripten_call_on_thread=function(){return(Gd=f.__emscripten_call_on_thread=f.asm.De).apply(null,arguments)};f._proxy_main=function(){return(f._proxy_main=f.asm.Ee).apply(null,arguments)};
|
||||
f._emscripten_tls_init=function(){return(f._emscripten_tls_init=f.asm.Fe).apply(null,arguments)};f.dynCall_ijiii=function(){return(f.dynCall_ijiii=f.asm.Ge).apply(null,arguments)};var Ge=f.dynCall_vijjjid=function(){return(Ge=f.dynCall_vijjjid=f.asm.He).apply(null,arguments)},He=f.dynCall_iiiijj=function(){return(He=f.dynCall_iiiijj=f.asm.Ie).apply(null,arguments)};f.dynCall_iiijiii=function(){return(f.dynCall_iiijiii=f.asm.Je).apply(null,arguments)};
|
||||
f.dynCall_jiiii=function(){return(f.dynCall_jiiii=f.asm.Ke).apply(null,arguments)};f.dynCall_jii=function(){return(f.dynCall_jii=f.asm.Le).apply(null,arguments)};var Ie=f.dynCall_iij=function(){return(Ie=f.dynCall_iij=f.asm.Me).apply(null,arguments)};f.dynCall_viiijj=function(){return(f.dynCall_viiijj=f.asm.Ne).apply(null,arguments)};f.dynCall_jij=function(){return(f.dynCall_jij=f.asm.Oe).apply(null,arguments)};f.dynCall_jiji=function(){return(f.dynCall_jiji=f.asm.Pe).apply(null,arguments)};
|
||||
f.dynCall_iiiji=function(){return(f.dynCall_iiiji=f.asm.Qe).apply(null,arguments)};f.dynCall_iiiiij=function(){return(f.dynCall_iiiiij=f.asm.Re).apply(null,arguments)};f.dynCall_jiiij=function(){return(f.dynCall_jiiij=f.asm.Se).apply(null,arguments)};f.dynCall_iiijjji=function(){return(f.dynCall_iiijjji=f.asm.Te).apply(null,arguments)};f.dynCall_iiiiiij=function(){return(f.dynCall_iiiiiij=f.asm.Ue).apply(null,arguments)};f.dynCall_jiiji=function(){return(f.dynCall_jiiji=f.asm.Ve).apply(null,arguments)};
|
||||
f.dynCall_viiiiijji=function(){return(f.dynCall_viiiiijji=f.asm.We).apply(null,arguments)};f.dynCall_viiiji=function(){return(f.dynCall_viiiji=f.asm.Xe).apply(null,arguments)};f.dynCall_viiiiji=function(){return(f.dynCall_viiiiji=f.asm.Ye).apply(null,arguments)};f.dynCall_jiiiii=function(){return(f.dynCall_jiiiii=f.asm.Ze).apply(null,arguments)};f.dynCall_jiii=function(){return(f.dynCall_jiii=f.asm._e).apply(null,arguments)};
|
||||
f.dynCall_jiiiiii=function(){return(f.dynCall_jiiiiii=f.asm.$e).apply(null,arguments)};f._ff_h264_cabac_tables=2115974;var xb=f._main_thread_futex=17195328;function pe(a,b,c){var d=A();try{return H.get(a)(b,c)}catch(e){D(d);if(e!==e+0&&"longjmp"!==e)throw e;Z(1,0)}}function we(a,b){var c=A();try{H.get(a)(b)}catch(d){D(c);if(d!==d+0&&"longjmp"!==d)throw d;Z(1,0)}}function ze(a,b,c,d,e){var g=A();try{H.get(a)(b,c,d,e)}catch(k){D(g);if(k!==k+0&&"longjmp"!==k)throw k;Z(1,0)}}
|
||||
function xe(a,b,c){var d=A();try{H.get(a)(b,c)}catch(e){D(d);if(e!==e+0&&"longjmp"!==e)throw e;Z(1,0)}}function oe(a,b){var c=A();try{return H.get(a)(b)}catch(d){D(c);if(d!==d+0&&"longjmp"!==d)throw d;Z(1,0)}}function re(a,b,c,d,e){var g=A();try{return H.get(a)(b,c,d,e)}catch(k){D(g);if(k!==k+0&&"longjmp"!==k)throw k;Z(1,0)}}function te(a,b,c,d,e,g,k,m,r){var q=A();try{return H.get(a)(b,c,d,e,g,k,m,r)}catch(t){D(q);if(t!==t+0&&"longjmp"!==t)throw t;Z(1,0)}}
|
||||
function ye(a,b,c,d){var e=A();try{H.get(a)(b,c,d)}catch(g){D(e);if(g!==g+0&&"longjmp"!==g)throw g;Z(1,0)}}function ne(a){var b=A();try{return H.get(a)()}catch(c){D(b);if(c!==c+0&&"longjmp"!==c)throw c;Z(1,0)}}function Ae(a,b,c,d,e,g){var k=A();try{H.get(a)(b,c,d,e,g)}catch(m){D(k);if(m!==m+0&&"longjmp"!==m)throw m;Z(1,0)}}function qe(a,b,c,d){var e=A();try{return H.get(a)(b,c,d)}catch(g){D(e);if(g!==g+0&&"longjmp"!==g)throw g;Z(1,0)}}
|
||||
function se(a,b,c,d,e,g){var k=A();try{return H.get(a)(b,c,d,e,g)}catch(m){D(k);if(m!==m+0&&"longjmp"!==m)throw m;Z(1,0)}}function Ce(a,b,c,d,e,g,k,m,r){var q=A();try{H.get(a)(b,c,d,e,g,k,m,r)}catch(t){D(q);if(t!==t+0&&"longjmp"!==t)throw t;Z(1,0)}}function Be(a,b,c,d,e,g,k){var m=A();try{H.get(a)(b,c,d,e,g,k)}catch(r){D(m);if(r!==r+0&&"longjmp"!==r)throw r;Z(1,0)}}function De(a,b,c,d,e,g,k,m,r,q){var t=A();try{Ge(a,b,c,d,e,g,k,m,r,q)}catch(w){D(t);if(w!==w+0&&"longjmp"!==w)throw w;Z(1,0)}}
|
||||
function ue(a,b,c,d,e,g,k,m){var r=A();try{return He(a,b,c,d,e,g,k,m)}catch(q){D(r);if(q!==q+0&&"longjmp"!==q)throw q;Z(1,0)}}function ve(a,b,c,d){var e=A();try{return Ie(a,b,c,d)}catch(g){D(e);if(g!==g+0&&"longjmp"!==g)throw g;Z(1,0)}}f.ccall=Ga;f.cwrap=function(a,b,c,d){c=c||[];var e=c.every(function(g){return"number"===g});return"string"!==b&&e&&!d?Fa(a):function(){return Ga(a,b,c,arguments,d)}};
|
||||
f.setValue=function(a,b,c){c=c||"i8";"*"===c.charAt(c.length-1)&&(c="i32");switch(c){case "i1":y[a>>0]=b;break;case "i8":y[a>>0]=b;break;case "i16":Qa[a>>1]=b;break;case "i32":E[a>>2]=b;break;case "i64":L=[b>>>0,(J=b,1<=+Math.abs(J)?0<J?(Math.min(+Math.floor(J/4294967296),4294967295)|0)>>>0:~~+Math.ceil((J-+(~~J>>>0))/4294967296)>>>0:0)];E[a>>2]=L[0];E[a+4>>2]=L[1];break;case "float":G[a>>2]=b;break;case "double":Sa[a>>3]=b;break;default:n("invalid type for setValue: "+c)}};f.writeAsciiToMemory=Pa;
|
||||
f.FS=O;f.PThread=M;f.PThread=M;f._pthread_self=Wd;f.wasmMemory=Ca;f.ExitStatus=wa;var Je;function wa(a){this.name="ExitStatus";this.message="Program terminated with exit("+a+")";this.status=a}cb=function Ke(){Je||Le();Je||(cb=Ke)};
|
||||
function Le(a){function b(){if(!Je&&(Je=!0,f.calledRun=!0,!Ea)){f.noFSInit||O.gg.Tg||O.gg();R.root=O.jf(R,{},null);nb(Wa);l||(O.Bh=!1,nb(Xa));ba(f);if(f.onRuntimeInitialized)f.onRuntimeInitialized();if(Me){var c=a;c=c||[];var d=c.length+1,e=Ha(4*(d+1));E[e>>2]=Na(ha);for(var g=1;g<d;g++)E[(e>>2)+g]=Na(c[g-1]);E[(e>>2)+d]=0;f._proxy_main(d,e)}if(!l){if(f.postRun)for("function"==typeof f.postRun&&(f.postRun=[f.postRun]);f.postRun.length;)c=f.postRun.shift(),Za.unshift(c);nb(Za)}}}a=a||fa;if(!(0<ab)){if(!l){if(f.preRun)for("function"==
|
||||
typeof f.preRun&&(f.preRun=[f.preRun]);f.preRun.length;)$a();nb(Va)}0<ab||(f.setStatus?(f.setStatus("Running..."),setTimeout(function(){setTimeout(function(){f.setStatus("")},1);b()},1)):b())}}f.run=Le;function Cb(a,b){if(!b||!noExitRuntime||0!==a){if(!noExitRuntime){M.Oi();l||(nb(Ya),O.quit(),M.eh());if(f.onExit)f.onExit(a);Ea=!0}ja(a,new wa(a))}}if(f.preInit)for("function"==typeof f.preInit&&(f.preInit=[f.preInit]);0<f.preInit.length;)f.preInit.pop()();var Me=!1;f.noInitialRun&&(Me=!1);
|
||||
l?M.ri():Le();f.exit=Cb;
|
||||
|
||||
|
||||
return createFFmpegCore.ready
|
||||
}
|
||||
);
|
||||
})();
|
||||
if (typeof exports === 'object' && typeof module === 'object')
|
||||
module.exports = createFFmpegCore;
|
||||
else if (typeof define === 'function' && define['amd'])
|
||||
define([], function() { return createFFmpegCore; });
|
||||
else if (typeof exports === 'object')
|
||||
exports["createFFmpegCore"] = createFFmpegCore;
|
||||
|
BIN
public/js/ffmpeg/ffmpeg-core.wasm
Executable file
BIN
public/js/ffmpeg/ffmpeg-core.wasm
Executable file
Binary file not shown.
1
public/js/ffmpeg/ffmpeg-core.worker.js
Normal file
1
public/js/ffmpeg/ffmpeg-core.worker.js
Normal file
|
@ -0,0 +1 @@
|
|||
var threadInfoStruct=0;var selfThreadId=0;var parentThreadId=0;var Module={};function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:selfThreadId})}var err=threadPrintErr;this.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);Module["wasmModule"]=null;receiveInstance(instance);return instance.exports};this.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob==="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}createFFmpegCore(Module).then(function(instance){Module=instance;postMessage({"cmd":"loaded"})})}else if(e.data.cmd==="objectTransfer"){Module["PThread"].receiveObjectTransfer(e.data)}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;threadInfoStruct=e.data.threadInfoStruct;Module["registerPthreadPtr"](threadInfoStruct,/*isMainBrowserThread=*/0,/*isMainRuntimeThread=*/0);selfThreadId=e.data.selfThreadId;parentThreadId=e.data.parentThreadId;var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;Module["establishStackSpace"](top,max);Module["_emscripten_tls_init"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].setThreadStatus(Module["_pthread_self"](),1);/*EM_THREAD_STATUS_RUNNING*/try{var result=Module["dynCall"]("ii",e.data.start_routine,[e.data.arg]);if(!Module["getNoExitRuntime"]())Module["PThread"].threadExit(result)}catch(ex){if(ex==="Canceled!"){Module["PThread"].threadCancel()}else if(ex!="unwind"){Atomics.store(Module["HEAPU32"],(threadInfoStruct+4)>>/*C_STRUCTS.pthread.threadExitCode*/2,(ex instanceof Module["ExitStatus"])?ex.status:-2);/*A custom entry specific to Emscripten denoting that the thread crashed.*/Atomics.store(Module["HEAPU32"],(threadInfoStruct+0)>>/*C_STRUCTS.pthread.threadStatus*/2,1);Module["_emscripten_futex_wake"](threadInfoStruct+0,/*C_STRUCTS.pthread.threadStatus*/2147483647);if(!(ex instanceof Module["ExitStatus"]))throw ex}}}else if(e.data.cmd==="cancel"){if(threadInfoStruct){Module["PThread"].threadCancel()}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(threadInfoStruct){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};if(typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string"){self={location:{href:__filename}};var onmessage=this.onmessage;var nodeWorkerThreads=require("worker_threads");global.Worker=nodeWorkerThreads.Worker;var parentPort=nodeWorkerThreads.parentPort;parentPort.on("message",function(data){onmessage({data:data})});var nodeFS=require("fs");var nodeRead=function(filename){return nodeFS.readFileSync(filename,"utf8")};function globalEval(x){global.require=require;global.Module=Module;eval.call(null,x)}importScripts=function(f){globalEval(nodeRead(f))};postMessage=function(msg){parentPort.postMessage(msg)};if(typeof performance==="undefined"){performance={now:function(){return Date.now()}}}}
|
BIN
public/share_icon.png
Normal file
BIN
public/share_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 157 B |
|
@ -2,8 +2,9 @@ import * as Sentry from '@sentry/nextjs';
|
|||
import { getSentryTunnelUrl } from 'utils/common/apiUtil';
|
||||
import { getUserAnonymizedID } from 'utils/user';
|
||||
|
||||
|
||||
const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN ?? 'https://860186db60c54c7fbacfe255124958e8@errors.ente.io/4';
|
||||
const SENTRY_DSN =
|
||||
process.env.NEXT_PUBLIC_SENTRY_DSN ??
|
||||
'https://860186db60c54c7fbacfe255124958e8@errors.ente.io/4';
|
||||
const SENTRY_ENV = process.env.NEXT_PUBLIC_SENTRY_ENV ?? 'development';
|
||||
|
||||
Sentry.setUser({ id: getUserAnonymizedID() });
|
||||
|
@ -13,6 +14,7 @@ Sentry.init({
|
|||
environment: SENTRY_ENV,
|
||||
release: process.env.SENTRY_RELEASE,
|
||||
attachStacktrace: true,
|
||||
autoSessionTracking: false,
|
||||
tunnel: getSentryTunnelUrl(),
|
||||
// ...
|
||||
// Note: if you want to override the automatic release value, do not set a
|
||||
|
|
|
@ -3,7 +3,7 @@ import styled from 'styled-components';
|
|||
|
||||
const Wrapper = styled.button`
|
||||
border: none;
|
||||
background-color: #2dc262;
|
||||
background-color: #51cd7c;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
bottom: 20px;
|
||||
|
|
|
@ -9,6 +9,7 @@ import { changeEmail, getOTTForEmailChange } from 'services/userService';
|
|||
import styled from 'styled-components';
|
||||
import { AppContext, FLASH_MESSAGE_TYPE } from 'pages/_app';
|
||||
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
|
||||
import { PAGES } from 'types';
|
||||
|
||||
interface formValues {
|
||||
email: string;
|
||||
|
@ -79,7 +80,7 @@ function ChangeEmailForm(props: Props) {
|
|||
message: constants.EMAIL_UDPATE_SUCCESSFUL,
|
||||
type: FLASH_MESSAGE_TYPE.SUCCESS,
|
||||
});
|
||||
router.push('/gallery');
|
||||
router.push(PAGES.GALLERY);
|
||||
} catch (e) {
|
||||
setFieldError('ott', `${constants.INCORRECT_CODE}`);
|
||||
}
|
||||
|
|
|
@ -101,7 +101,9 @@ function CollectionShare(props: Props) {
|
|||
onHide={props.onHide}
|
||||
attributes={{ title: constants.SHARE_COLLECTION }}>
|
||||
<DeadCenter style={{ width: '85%', margin: 'auto' }}>
|
||||
<h6>{constants.SHARE_WITH_PEOPLE}</h6>
|
||||
<h6 style={{ marginTop: '8px' }}>
|
||||
{constants.SHARE_WITH_PEOPLE}
|
||||
</h6>
|
||||
<p />
|
||||
<Formik<formValues>
|
||||
initialValues={{ email: '' }}
|
||||
|
|
|
@ -55,3 +55,9 @@ export const Value = styled.div<{ width?: string }>`
|
|||
text-align: center;
|
||||
color: #ddd;
|
||||
`;
|
||||
|
||||
export const FlexWrapper = styled.div`
|
||||
display: flex;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
|
|
@ -6,7 +6,12 @@ export default function EnteSpinner(props) {
|
|||
<Spinner
|
||||
{...props}
|
||||
animation="border"
|
||||
variant="success"
|
||||
style={{
|
||||
width: '36px',
|
||||
height: '36px',
|
||||
borderWidth: '0.20em',
|
||||
color: '#51cd7c',
|
||||
}}
|
||||
role="status"
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -20,6 +20,7 @@ interface Props {
|
|||
cancelExport: () => void;
|
||||
pauseExport: () => void;
|
||||
}
|
||||
|
||||
export default function ExportInProgress(props: Props) {
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -30,7 +30,7 @@ const Overlay = styled.div`
|
|||
font-weight: 900;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
border-color: #2dc262;
|
||||
border-color: #51cd7c;
|
||||
border-style: solid;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
z-index: 3000;
|
||||
|
|
|
@ -11,6 +11,7 @@ import { setData, LS_KEYS, getData } from 'utils/storage/localStorage';
|
|||
import SubmitButton from 'components/SubmitButton';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import LogoImg from './LogoImg';
|
||||
import { PAGES } from 'types';
|
||||
|
||||
interface formValues {
|
||||
email: string;
|
||||
|
@ -27,10 +28,10 @@ export default function Login(props: LoginProps) {
|
|||
|
||||
useEffect(() => {
|
||||
const main = async () => {
|
||||
router.prefetch('/verify');
|
||||
router.prefetch(PAGES.VERIFY);
|
||||
const user = getData(LS_KEYS.USER);
|
||||
if (user?.email) {
|
||||
await router.push('/verify');
|
||||
await router.push(PAGES.VERIFY);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
@ -45,7 +46,7 @@ export default function Login(props: LoginProps) {
|
|||
setWaiting(true);
|
||||
await getOtt(email);
|
||||
setData(LS_KEYS.USER, { email });
|
||||
router.push('/verify');
|
||||
router.push(PAGES.VERIFY);
|
||||
} catch (e) {
|
||||
setFieldError('email', `${constants.UNKNOWN_ERROR} ${e.message}`);
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ const EmptyScreen = styled.div`
|
|||
align-items: center;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
color: #2dc262;
|
||||
color: #51cd7c;
|
||||
|
||||
& > svg {
|
||||
filter: drop-shadow(3px 3px 5px rgba(45, 194, 98, 0.5));
|
||||
|
@ -491,6 +491,9 @@ const PhotoFrame = ({
|
|||
{!isFirstLoad && files.length === 0 && !searchMode ? (
|
||||
<EmptyScreen>
|
||||
<img height={150} src="/images/gallery.png" />
|
||||
<div style={{ color: '#a6a6a6', marginTop: '16px' }}>
|
||||
{constants.UPLOAD_FIRST_PHOTO_DESCRIPTION}
|
||||
</div>
|
||||
<Button
|
||||
variant="outline-success"
|
||||
onClick={openFileUploader}
|
||||
|
@ -500,6 +503,7 @@ const PhotoFrame = ({
|
|||
paddingRight: '32px',
|
||||
paddingTop: '12px',
|
||||
paddingBottom: '12px',
|
||||
fontWeight: 900,
|
||||
}}>
|
||||
{constants.UPLOAD_FIRST_PHOTO}
|
||||
</Button>
|
||||
|
|
|
@ -417,8 +417,8 @@ function PhotoSwipe(props: Iprops) {
|
|||
constants.UPDATED_ON,
|
||||
formatDateTime(metadata.modificationTime / 1000)
|
||||
)}
|
||||
{metadata?.longitude &&
|
||||
metadata?.longitude &&
|
||||
{metadata?.longitude > 0 &&
|
||||
metadata?.longitude > 0 &&
|
||||
renderInfoItem(
|
||||
constants.LOCATION,
|
||||
<a
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Search, SearchStats, SetCollections } from 'pages/gallery';
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import AsyncSelect from 'react-select/async';
|
||||
import { components } from 'react-select';
|
||||
|
@ -19,38 +19,37 @@ import DateIcon from './icons/DateIcon';
|
|||
import SearchIcon from './icons/SearchIcon';
|
||||
import CrossIcon from './icons/CrossIcon';
|
||||
|
||||
const Wrapper = styled.div<{ width: number; isDisabled: boolean }>`
|
||||
const Wrapper = styled.div<{ isDisabled: boolean; isOpen: boolean }>`
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
top: 0;
|
||||
left: ${(props) => `max(0px, 50% - min(360px,${props.width / 2}px))`};
|
||||
z-index: 1000;
|
||||
display: ${({ isOpen }) => (isOpen ? 'flex' : 'none')};
|
||||
width: 100%;
|
||||
max-width: 720px;
|
||||
background: #111;
|
||||
@media (min-width: 625px) {
|
||||
display: flex;
|
||||
width: calc(100vw - 140px);
|
||||
margin: 0 70px;
|
||||
}
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 5%;
|
||||
background-color: #111;
|
||||
color: #fff;
|
||||
min-height: 64px;
|
||||
transition: opacity 1s ease;
|
||||
opacity: ${(props) => (props.isDisabled ? 0 : 1)};
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const SearchButton = styled.div<{ isDisabled: boolean }>`
|
||||
top: 1px;
|
||||
z-index: 100;
|
||||
const SearchButton = styled.div<{ isOpen: boolean }>`
|
||||
display: none;
|
||||
@media (max-width: 624px) {
|
||||
display: ${({ isOpen }) => (!isOpen ? 'flex' : 'none')};
|
||||
right: 80px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
transition: opacity 1s ease;
|
||||
opacity: ${(props) => (props.isDisabled ? 0 : 1)};
|
||||
min-height: 64px;
|
||||
position: fixed;
|
||||
display: flex;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 64px;
|
||||
}
|
||||
`;
|
||||
|
||||
const SearchStatsContainer = styled.div`
|
||||
|
@ -61,6 +60,14 @@ const SearchStatsContainer = styled.div`
|
|||
margin-bottom: 8px;
|
||||
`;
|
||||
|
||||
const SearchInput = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 484px;
|
||||
margin: auto;
|
||||
`;
|
||||
|
||||
export enum SuggestionType {
|
||||
DATE,
|
||||
LOCATION,
|
||||
|
@ -86,7 +93,6 @@ interface Props {
|
|||
searchStats: SearchStats;
|
||||
}
|
||||
export default function SearchBar(props: Props) {
|
||||
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
||||
const selectRef = useRef(null);
|
||||
useEffect(() => {
|
||||
if (props.isOpen) {
|
||||
|
@ -96,11 +102,6 @@ export default function SearchBar(props: Props) {
|
|||
}
|
||||
}, [props.isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', () =>
|
||||
setWindowWidth(window.innerWidth)
|
||||
);
|
||||
});
|
||||
// = =========================
|
||||
// Functionality
|
||||
// = =========================
|
||||
|
@ -220,12 +221,12 @@ export default function SearchBar(props: Props) {
|
|||
...style,
|
||||
backgroundColor: '#282828',
|
||||
color: '#d1d1d1',
|
||||
borderColor: isFocused ? '#2dc262' : '#444',
|
||||
borderColor: isFocused ? '#51cd7c' : '#444',
|
||||
boxShadow: 'none',
|
||||
':hover': {
|
||||
borderColor: '#2dc262',
|
||||
borderColor: '#51cd7c',
|
||||
cursor: 'text',
|
||||
'&>.icon': { color: '#2dc262' },
|
||||
'&>.icon': { color: '#51cd7c' },
|
||||
},
|
||||
}),
|
||||
input: (style) => ({
|
||||
|
@ -273,8 +274,8 @@ export default function SearchBar(props: Props) {
|
|||
{constants.SEARCH_STATS(props.searchStats)}
|
||||
</SearchStatsContainer>
|
||||
)}
|
||||
{windowWidth > 1000 || props.isOpen ? (
|
||||
<Wrapper isDisabled={props.isFirstFetch} width={windowWidth}>
|
||||
<Wrapper isDisabled={props.isFirstFetch} isOpen={props.isOpen}>
|
||||
<SearchInput>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
|
@ -304,14 +305,13 @@ export default function SearchBar(props: Props) {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SearchInput>
|
||||
</Wrapper>
|
||||
) : (
|
||||
<SearchButton
|
||||
isDisabled={props.isFirstFetch}
|
||||
isOpen={props.isOpen}
|
||||
onClick={() => !props.isFirstFetch && props.setOpen(true)}>
|
||||
<SearchIcon />
|
||||
</SearchButton>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -32,10 +32,7 @@ function SetPasswordForm(props: Props) {
|
|||
setFieldError('confirm', constants.PASSPHRASE_MATCH_ERROR);
|
||||
}
|
||||
} catch (e) {
|
||||
setFieldError(
|
||||
'passphrase',
|
||||
`${constants.UNKNOWN_ERROR} ${e.message}`
|
||||
);
|
||||
setFieldError('confirm', `${constants.UNKNOWN_ERROR} ${e.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
|
||||
import { slide as Menu } from 'react-burger-menu';
|
||||
import constants from 'utils/strings/constants';
|
||||
|
@ -8,7 +8,6 @@ import { getEndpoint } from 'utils/common/apiUtil';
|
|||
import { Button } from 'react-bootstrap';
|
||||
import {
|
||||
isSubscriptionActive,
|
||||
convertBytesToGBs,
|
||||
getUserSubscription,
|
||||
isOnFreePlan,
|
||||
isSubscriptionCancelled,
|
||||
|
@ -28,10 +27,11 @@ import EnteSpinner from './EnteSpinner';
|
|||
import RecoveryKeyModal from './RecoveryKeyModal';
|
||||
import TwoFactorModal from './TwoFactorModal';
|
||||
import ExportModal from './ExportModal';
|
||||
import { SetLoading } from 'pages/gallery';
|
||||
import { GalleryContext, SetLoading } from 'pages/gallery';
|
||||
import InProgressIcon from './icons/InProgressIcon';
|
||||
import exportService from 'services/exportService';
|
||||
import { Subscription } from 'services/billingService';
|
||||
import { PAGES } from 'types';
|
||||
|
||||
interface Props {
|
||||
collections: Collection[];
|
||||
|
@ -51,6 +51,8 @@ export default function Sidebar(props: Props) {
|
|||
const [recoverModalView, setRecoveryModalView] = useState(false);
|
||||
const [twoFactorModalView, setTwoFactorModalView] = useState(false);
|
||||
const [exportModalView, setExportModalView] = useState(false);
|
||||
const galleryContext = useContext(GalleryContext);
|
||||
galleryContext.showPlanSelectorModal = props.showPlanSelectorModal;
|
||||
useEffect(() => {
|
||||
const main = async () => {
|
||||
if (!isOpen) {
|
||||
|
@ -76,13 +78,14 @@ export default function Sidebar(props: Props) {
|
|||
const win = window.open(feedbackURL, '_blank');
|
||||
win.focus();
|
||||
}
|
||||
function openSupportMail() {
|
||||
const a = document.createElement('a');
|
||||
a.href = 'mailto:contact@ente.io';
|
||||
|
||||
function initiateEmail(email: string) {
|
||||
const a = document.createElement('a');
|
||||
a.href = 'mailto:' + email;
|
||||
a.rel = 'noreferrer noopener';
|
||||
a.click();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function exportFiles() {
|
||||
if (isElectron()) {
|
||||
|
@ -185,7 +188,7 @@ export default function Sidebar(props: Props) {
|
|||
{usage ? (
|
||||
constants.USAGE_INFO(
|
||||
usage,
|
||||
Number(convertBytesToGBs(subscription?.storage))
|
||||
convertToHumanReadable(subscription?.storage)
|
||||
)
|
||||
) : (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
|
@ -215,7 +218,7 @@ export default function Sidebar(props: Props) {
|
|||
</LinkButton>
|
||||
<LinkButton
|
||||
style={{ marginTop: '30px' }}
|
||||
onClick={openSupportMail}>
|
||||
onClick={() => initiateEmail('contact@ente.io')}>
|
||||
{constants.SUPPORT}
|
||||
</LinkButton>
|
||||
<>
|
||||
|
@ -252,16 +255,14 @@ export default function Sidebar(props: Props) {
|
|||
<LinkButton
|
||||
style={{ marginTop: '30px' }}
|
||||
onClick={() => {
|
||||
setData(LS_KEYS.SHOW_BACK_BUTTON, { value: true });
|
||||
router.push('change-password');
|
||||
router.push(PAGES.CHANGE_PASSWORD);
|
||||
}}>
|
||||
{constants.CHANGE_PASSWORD}
|
||||
</LinkButton>
|
||||
<LinkButton
|
||||
style={{ marginTop: '30px' }}
|
||||
onClick={() => {
|
||||
setData(LS_KEYS.SHOW_BACK_BUTTON, { value: true });
|
||||
router.push('change-email');
|
||||
router.push(PAGES.CHANGE_EMAIL);
|
||||
}}>
|
||||
{constants.UPDATE_EMAIL}
|
||||
</LinkButton>
|
||||
|
@ -309,6 +310,26 @@ export default function Sidebar(props: Props) {
|
|||
}>
|
||||
{constants.LOGOUT}
|
||||
</LinkButton>
|
||||
<LinkButton
|
||||
variant="danger"
|
||||
style={{ marginTop: '30px' }}
|
||||
onClick={() =>
|
||||
props.setDialogMessage({
|
||||
title: `${constants.DELETE_ACCOUNT}`,
|
||||
content: constants.DELETE_MESSAGE(),
|
||||
staticBackdrop: true,
|
||||
proceed: {
|
||||
text: constants.DELETE_ACCOUNT,
|
||||
action: () => {
|
||||
initiateEmail('account-deletion@ente.io');
|
||||
},
|
||||
variant: 'danger',
|
||||
},
|
||||
close: { text: constants.CANCEL },
|
||||
})
|
||||
}>
|
||||
{constants.DELETE_ACCOUNT}
|
||||
</LinkButton>
|
||||
<div
|
||||
style={{
|
||||
marginTop: '40px',
|
||||
|
|
|
@ -19,6 +19,7 @@ import { setJustSignedUp } from 'utils/storage';
|
|||
import LogoImg from './LogoImg';
|
||||
import { logError } from 'utils/sentry';
|
||||
import { SESSION_KEYS } from 'utils/storage/sessionStorage';
|
||||
import { PAGES } from 'types';
|
||||
|
||||
interface FormValues {
|
||||
email: string;
|
||||
|
@ -40,11 +41,16 @@ export default function SignUp(props: SignUpProps) {
|
|||
{ setFieldError }: FormikHelpers<FormValues>
|
||||
) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
try {
|
||||
setData(LS_KEYS.USER, { email });
|
||||
await getOtt(email);
|
||||
} catch (e) {
|
||||
setFieldError('email', `${constants.UNKNOWN_ERROR} ${e.message}`);
|
||||
setFieldError(
|
||||
'confirm',
|
||||
`${constants.UNKNOWN_ERROR} ${e.message}`
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
try {
|
||||
if (passphrase === confirm) {
|
||||
|
@ -62,13 +68,19 @@ export default function SignUp(props: SignUpProps) {
|
|||
masterKey
|
||||
);
|
||||
setJustSignedUp(true);
|
||||
router.push('/verify');
|
||||
router.push(PAGES.VERIFY);
|
||||
} else {
|
||||
setFieldError('confirm', constants.PASSPHRASE_MATCH_ERROR);
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
setFieldError('passphrase', constants.PASSWORD_GENERATION_FAILED);
|
||||
setFieldError(
|
||||
'passphrase',
|
||||
constants.PASSWORD_GENERATION_FAILED
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
} catch (err) {
|
||||
logError(err, 'signup failed');
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ const SubmitButton = ({ loading, buttonText, inline, disabled }: Props) => (
|
|||
width: '22px',
|
||||
height: '22px',
|
||||
borderWidth: '0.20em',
|
||||
color: '#2dc262',
|
||||
color: '#51cd7c',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
@ -2,11 +2,12 @@ import { useRouter } from 'next/router';
|
|||
import { DeadCenter, SetLoading } from 'pages/gallery';
|
||||
import { AppContext, FLASH_MESSAGE_TYPE } from 'pages/_app';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Button, Row } from 'react-bootstrap';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { disableTwoFactor, getTwoFactorStatus } from 'services/userService';
|
||||
import { PAGES } from 'types';
|
||||
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { Label, Value } from './Container';
|
||||
import { Label, Value, Row } from './Container';
|
||||
import MessageDialog, { SetDialogMessage } from './MessageDialog';
|
||||
|
||||
interface Props {
|
||||
|
@ -86,7 +87,7 @@ function TwoFactorModal(props: Props) {
|
|||
});
|
||||
};
|
||||
const reconfigureTwoFactor = async () => {
|
||||
router.push('/two-factor/setup');
|
||||
router.push(PAGES.TWO_FACTOR_SETUP);
|
||||
};
|
||||
return (
|
||||
<MessageDialog
|
||||
|
@ -107,7 +108,8 @@ function TwoFactorModal(props: Props) {
|
|||
<Value>
|
||||
<Button
|
||||
variant={'outline-success'}
|
||||
onClick={warnTwoFactorReconfigure}>
|
||||
onClick={warnTwoFactorReconfigure}
|
||||
style={{ width: '100%' }}>
|
||||
{constants.RECONFIGURE}
|
||||
</Button>
|
||||
</Value>
|
||||
|
@ -117,7 +119,8 @@ function TwoFactorModal(props: Props) {
|
|||
<Value>
|
||||
<Button
|
||||
variant={'outline-danger'}
|
||||
onClick={warnTwoFactorDisable}>
|
||||
onClick={warnTwoFactorDisable}
|
||||
style={{ width: '100%' }}>
|
||||
{constants.DISABLE}
|
||||
</Button>
|
||||
</Value>
|
||||
|
@ -142,7 +145,7 @@ function TwoFactorModal(props: Props) {
|
|||
<div style={{ height: '10px' }} />
|
||||
<Button
|
||||
variant="outline-success"
|
||||
onClick={() => router.push('/two-factor/setup')}>
|
||||
onClick={() => router.push(PAGES.TWO_FACTOR_SETUP)}>
|
||||
{constants.ENABLE_TWO_FACTOR}
|
||||
</Button>
|
||||
</DeadCenter>
|
||||
|
|
31
src/components/icons/WarningIcon.tsx
Normal file
31
src/components/icons/WarningIcon.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import {
|
||||
ButtonVariant,
|
||||
getVariantColor,
|
||||
} from 'components/pages/gallery/LinkButton';
|
||||
import React from 'react';
|
||||
|
||||
export default function WarningIcon(props) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
color: getVariantColor(ButtonVariant.danger),
|
||||
display: 'inline-block',
|
||||
padding: '0 10px',
|
||||
}}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height={props.height}
|
||||
viewBox={props.viewBox}
|
||||
width={props.width}
|
||||
fill="currentColor">
|
||||
<path d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm-1 6h2v8h-2v-8zm1 12.25c-.69 0-1.25-.56-1.25-1.25s.56-1.25 1.25-1.25 1.25.56 1.25 1.25-.56 1.25-1.25 1.25z" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
WarningIcon.defaultProps = {
|
||||
height: 24,
|
||||
width: 24,
|
||||
viewBox: '0 0 24 24',
|
||||
};
|
|
@ -15,9 +15,15 @@ const ImageContainer = styled.div`
|
|||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export default function AddCollectionButton({ showNextModal }) {
|
||||
export default function AddCollectionButton({
|
||||
showNextModal,
|
||||
}: {
|
||||
showNextModal: () => void;
|
||||
}) {
|
||||
return (
|
||||
<CollectionIcon style={{ margin: '10px' }} onClick={showNextModal}>
|
||||
<CollectionIcon
|
||||
style={{ margin: '10px' }}
|
||||
onClick={() => showNextModal()}>
|
||||
<Card>
|
||||
<ImageContainer>+</ImageContainer>
|
||||
<Card.Text style={{ textAlign: 'center' }}>
|
||||
|
|
|
@ -1,31 +1,29 @@
|
|||
import { FlexWrapper } from 'components/Container';
|
||||
import WarningIcon from 'components/icons/WarningIcon';
|
||||
import React from 'react';
|
||||
import Alert from 'react-bootstrap/Alert';
|
||||
import { getVariantColor } from './LinkButton';
|
||||
import styled from 'styled-components';
|
||||
import { ButtonVariant, getVariantColor } from './LinkButton';
|
||||
|
||||
interface Props {
|
||||
bannerMessage?: any;
|
||||
variant?: string;
|
||||
children?: any;
|
||||
}
|
||||
const Banner = styled.div`
|
||||
border: 1px solid ${getVariantColor(ButtonVariant.warning)};
|
||||
border-radius: 8px;
|
||||
padding: 16px 28px;
|
||||
color: #eee;
|
||||
margin-top: 10px;
|
||||
`;
|
||||
export default function AlertBanner(props: Props) {
|
||||
return (
|
||||
<Alert
|
||||
variant={props.variant ?? 'danger'}
|
||||
style={{
|
||||
display:
|
||||
props.bannerMessage || props.children ? 'block' : 'none',
|
||||
textAlign: 'center',
|
||||
|
||||
border: 'none',
|
||||
borderBottom: '1px solid',
|
||||
background: 'none',
|
||||
borderRadius: '0px',
|
||||
color: getVariantColor(props.variant),
|
||||
padding: 0,
|
||||
margin: '0 25px',
|
||||
marginBottom: '10px',
|
||||
}}>
|
||||
{props.bannerMessage ? props.bannerMessage : props.children}
|
||||
</Alert>
|
||||
return props.bannerMessage ? (
|
||||
<FlexWrapper>
|
||||
<Banner>
|
||||
<WarningIcon />
|
||||
{props.bannerMessage && props.bannerMessage}
|
||||
</Banner>
|
||||
</FlexWrapper>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,14 +17,23 @@ function ChoiceModal({
|
|||
}: Props) {
|
||||
return (
|
||||
<MessageDialog
|
||||
size="lg"
|
||||
{...props}
|
||||
attributes={{ title: constants.MULTI_FOLDER_UPLOAD }}>
|
||||
<p>{constants.UPLOAD_STRATEGY_CHOICE}</p>
|
||||
<p
|
||||
style={{
|
||||
fontSize: '18px',
|
||||
textAlign: 'center',
|
||||
marginBottom: '24px',
|
||||
marginTop: '4px',
|
||||
}}>
|
||||
{constants.UPLOAD_STRATEGY_CHOICE}
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
paddingBottom: '12px',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<Button
|
||||
variant="outline-success"
|
||||
|
@ -34,17 +43,16 @@ function ChoiceModal({
|
|||
}}
|
||||
style={{
|
||||
padding: '12px 24px',
|
||||
flex: 2,
|
||||
whiteSpace: 'nowrap',
|
||||
fontWeight: 900,
|
||||
}}>
|
||||
{constants.UPLOAD_STRATEGY_SINGLE_COLLECTION}
|
||||
</Button>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
textAlign: 'center',
|
||||
minWidth: '100px',
|
||||
margin: '2% auto',
|
||||
margin: '2%',
|
||||
}}>
|
||||
<strong>{constants.OR}</strong>
|
||||
</div>
|
||||
|
@ -56,8 +64,8 @@ function ChoiceModal({
|
|||
}}
|
||||
style={{
|
||||
padding: '12px 24px',
|
||||
flex: 2,
|
||||
whiteSpace: 'nowrap',
|
||||
fontWeight: 900,
|
||||
}}>
|
||||
{constants.UPLOAD_STRATEGY_COLLECTION_PER_FOLDER}
|
||||
</Button>
|
||||
|
|
|
@ -18,7 +18,7 @@ export const CollectionIcon = styled.div`
|
|||
|
||||
export interface CollectionSelectorAttributes {
|
||||
callback: (collection) => void;
|
||||
showNextModal: () => void;
|
||||
showNextModal: (firstAlbum?: boolean) => void;
|
||||
title: string;
|
||||
}
|
||||
export type SetCollectionSelectorAttributes = React.Dispatch<
|
||||
|
@ -47,7 +47,7 @@ function CollectionSelector({
|
|||
useEffect(() => {
|
||||
if (directlyShowNextModal && attributes) {
|
||||
props.onHide();
|
||||
attributes.showNextModal();
|
||||
attributes.showNextModal(true);
|
||||
}
|
||||
}, [attributes]);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
enum ButtonVariant {
|
||||
export enum ButtonVariant {
|
||||
success = 'success',
|
||||
danger = 'danger',
|
||||
secondary = 'secondary',
|
||||
|
@ -15,7 +15,7 @@ type Props = React.PropsWithChildren<{
|
|||
export function getVariantColor(variant: string) {
|
||||
switch (variant) {
|
||||
case ButtonVariant.success:
|
||||
return '#2dc262';
|
||||
return '#51cd7c';
|
||||
case ButtonVariant.danger:
|
||||
return '#c93f3f';
|
||||
case ButtonVariant.secondary:
|
||||
|
|
|
@ -143,15 +143,14 @@ function PlanSelector(props: Props) {
|
|||
} else {
|
||||
try {
|
||||
props.setLoading(true);
|
||||
await billingService.buyPaidSubscription(plan.stripeID);
|
||||
await billingService.buySubscription(plan.stripeID);
|
||||
} catch (e) {
|
||||
props.setLoading(false);
|
||||
props.setDialogMessage({
|
||||
title: constants.ERROR,
|
||||
content: constants.SUBSCRIPTION_PURCHASE_FAILED,
|
||||
close: { variant: 'danger' },
|
||||
});
|
||||
} finally {
|
||||
props.setLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,8 +60,8 @@ const Check = styled.input`
|
|||
/** checked */
|
||||
&:checked::before {
|
||||
content: '';
|
||||
background-color: #2dc262;
|
||||
border-color: #2dc262;
|
||||
background-color: #51cd7c;
|
||||
border-color: #51cd7c;
|
||||
color: #fff;
|
||||
}
|
||||
&:checked::after {
|
||||
|
@ -90,7 +90,7 @@ const Cont = styled.div<{ disabled: boolean; selected: boolean }>`
|
|||
max-width: 100%;
|
||||
min-height: 100%;
|
||||
flex: 1;
|
||||
${(props) => props.selected && 'border: 5px solid #2dc262;'}
|
||||
${(props) => props.selected && 'border: 5px solid #51cd7c;'}
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import UploadService, {
|
||||
FileWithCollection,
|
||||
UPLOAD_STAGES,
|
||||
} from 'services/uploadService';
|
||||
import { createAlbum } from 'services/collectionService';
|
||||
import { getLocalFiles } from 'services/fileService';
|
||||
|
||||
import {
|
||||
Collection,
|
||||
syncCollections,
|
||||
createAlbum,
|
||||
} from 'services/collectionService';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { SetDialogMessage } from 'components/MessageDialog';
|
||||
import UploadProgress from './UploadProgress';
|
||||
|
@ -12,11 +12,19 @@ import UploadProgress from './UploadProgress';
|
|||
import ChoiceModal from './ChoiceModal';
|
||||
import { SetCollectionNamerAttributes } from './CollectionNamer';
|
||||
import { SetCollectionSelectorAttributes } from './CollectionSelector';
|
||||
import { SetFiles, SetLoading } from 'pages/gallery';
|
||||
import { GalleryContext, SetFiles, SetLoading } from 'pages/gallery';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { logError } from 'utils/sentry';
|
||||
import { FileRejection } from 'react-dropzone';
|
||||
import UploadManager, {
|
||||
FileWithCollection,
|
||||
UPLOAD_STAGES,
|
||||
} from 'services/upload/uploadManager';
|
||||
import uploadManager from 'services/upload/uploadManager';
|
||||
import { METADATA_FOLDER_NAME } from 'services/exportService';
|
||||
import { getUserFacingErrorMessage } from 'utils/common/errorUtil';
|
||||
|
||||
const FIRST_ALBUM_NAME = 'My First Album';
|
||||
|
||||
interface Props {
|
||||
syncWithRemote: (force?: boolean, silent?: boolean) => Promise<void>;
|
||||
|
@ -42,13 +50,25 @@ interface AnalysisResult {
|
|||
suggestedCollectionName: string;
|
||||
multipleFolders: boolean;
|
||||
}
|
||||
export interface ProgressUpdater {
|
||||
setPercentComplete: React.Dispatch<React.SetStateAction<number>>;
|
||||
setFileCounter: React.Dispatch<
|
||||
React.SetStateAction<{
|
||||
finished: number;
|
||||
total: number;
|
||||
}>
|
||||
>;
|
||||
setUploadStage: React.Dispatch<React.SetStateAction<UPLOAD_STAGES>>;
|
||||
setFileProgress: React.Dispatch<React.SetStateAction<Map<string, number>>>;
|
||||
setUploadResult: React.Dispatch<React.SetStateAction<Map<string, number>>>;
|
||||
}
|
||||
|
||||
export default function Upload(props: Props) {
|
||||
const [progressView, setProgressView] = useState(false);
|
||||
const [uploadStage, setUploadStage] = useState<UPLOAD_STAGES>(
|
||||
UPLOAD_STAGES.START
|
||||
);
|
||||
const [fileCounter, setFileCounter] = useState({ current: 0, total: 0 });
|
||||
const [fileCounter, setFileCounter] = useState({ finished: 0, total: 0 });
|
||||
const [fileProgress, setFileProgress] = useState(new Map<string, number>());
|
||||
const [uploadResult, setUploadResult] = useState(new Map<string, number>());
|
||||
const [percentComplete, setPercentComplete] = useState(0);
|
||||
|
@ -56,7 +76,20 @@ export default function Upload(props: Props) {
|
|||
const [fileAnalysisResult, setFileAnalysisResult] =
|
||||
useState<AnalysisResult>(null);
|
||||
const appContext = useContext(AppContext);
|
||||
const galleryContext = useContext(GalleryContext);
|
||||
|
||||
useEffect(() => {
|
||||
UploadManager.initUploader(
|
||||
{
|
||||
setPercentComplete,
|
||||
setFileCounter,
|
||||
setFileProgress,
|
||||
setUploadResult,
|
||||
setUploadStage,
|
||||
},
|
||||
props.setFiles
|
||||
);
|
||||
});
|
||||
useEffect(() => {
|
||||
if (
|
||||
props.acceptedFiles?.length > 0 ||
|
||||
|
@ -64,7 +97,7 @@ export default function Upload(props: Props) {
|
|||
) {
|
||||
props.setLoading(true);
|
||||
|
||||
let fileAnalysisResult;
|
||||
let fileAnalysisResult: AnalysisResult;
|
||||
if (props.acceptedFiles?.length > 0) {
|
||||
// File selection by drag and drop or selection of file.
|
||||
fileAnalysisResult = analyseUploadFiles();
|
||||
|
@ -77,7 +110,7 @@ export default function Upload(props: Props) {
|
|||
props.setCollectionSelectorAttributes({
|
||||
callback: uploadFilesToExistingCollection,
|
||||
showNextModal: nextModal.bind(null, fileAnalysisResult),
|
||||
title: 'upload to collection',
|
||||
title: constants.UPLOAD_TO_COLLECTION,
|
||||
});
|
||||
props.setLoading(false);
|
||||
}
|
||||
|
@ -85,31 +118,44 @@ export default function Upload(props: Props) {
|
|||
|
||||
const uploadInit = function () {
|
||||
setUploadStage(UPLOAD_STAGES.START);
|
||||
setFileCounter({ current: 0, total: 0 });
|
||||
setFileCounter({ finished: 0, total: 0 });
|
||||
setFileProgress(new Map<string, number>());
|
||||
setUploadResult(new Map<string, number>());
|
||||
setPercentComplete(0);
|
||||
setProgressView(true);
|
||||
};
|
||||
const showCreateCollectionModal = (fileAnalysisResult?: AnalysisResult) => {
|
||||
props.setCollectionNamerAttributes({
|
||||
title: constants.CREATE_COLLECTION,
|
||||
buttonText: constants.CREATE,
|
||||
autoFilledName: fileAnalysisResult?.suggestedCollectionName,
|
||||
callback: async (collectionName) => {
|
||||
const showCreateCollectionModal = (
|
||||
fileAnalysisResult: AnalysisResult,
|
||||
isFirstAlbum?: boolean
|
||||
) => {
|
||||
const uploadToNewCollection = async (collectionName: string) => {
|
||||
props.closeCollectionSelector();
|
||||
await uploadFilesToNewCollections(
|
||||
UPLOAD_STRATEGY.SINGLE_COLLECTION,
|
||||
collectionName
|
||||
);
|
||||
},
|
||||
};
|
||||
if (fileAnalysisResult.suggestedCollectionName) {
|
||||
uploadToNewCollection(fileAnalysisResult.suggestedCollectionName);
|
||||
} else if (isFirstAlbum) {
|
||||
uploadToNewCollection(FIRST_ALBUM_NAME);
|
||||
} else {
|
||||
props.setCollectionNamerAttributes({
|
||||
title: constants.CREATE_COLLECTION,
|
||||
buttonText: constants.CREATE,
|
||||
autoFilledName: fileAnalysisResult?.suggestedCollectionName,
|
||||
callback: uploadToNewCollection,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const nextModal = (fileAnalysisResult: AnalysisResult) => {
|
||||
const nextModal = (
|
||||
fileAnalysisResult: AnalysisResult,
|
||||
isFirstAlbum: boolean
|
||||
) => {
|
||||
fileAnalysisResult?.multipleFolders
|
||||
? setChoiceModalView(true)
|
||||
: showCreateCollectionModal(fileAnalysisResult);
|
||||
: showCreateCollectionModal(fileAnalysisResult, isFirstAlbum);
|
||||
};
|
||||
|
||||
function analyseUploadFiles(): AnalysisResult {
|
||||
|
@ -131,6 +177,11 @@ export default function Upload(props: Props) {
|
|||
1,
|
||||
commonPathPrefix.lastIndexOf('/') - 1
|
||||
);
|
||||
if (commonPathPrefix) {
|
||||
commonPathPrefix = commonPathPrefix.substr(
|
||||
commonPathPrefix.lastIndexOf('/') + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
return {
|
||||
suggestedCollectionName: commonPathPrefix,
|
||||
|
@ -163,7 +214,7 @@ export default function Upload(props: Props) {
|
|||
const filesWithCollectionToUpload: FileWithCollection[] =
|
||||
props.acceptedFiles.map((file) => ({
|
||||
file,
|
||||
collection,
|
||||
collectionID: collection.id,
|
||||
}));
|
||||
await uploadFiles(filesWithCollectionToUpload);
|
||||
} catch (e) {
|
||||
|
@ -178,7 +229,8 @@ export default function Upload(props: Props) {
|
|||
try {
|
||||
uploadInit();
|
||||
|
||||
const filesWithCollectionToUpload = [];
|
||||
const filesWithCollectionToUpload: FileWithCollection[] = [];
|
||||
const collections: Collection[] = [];
|
||||
let collectionWiseFiles = new Map<string, globalThis.File[]>();
|
||||
if (strategy === UPLOAD_STRATEGY.SINGLE_COLLECTION) {
|
||||
collectionWiseFiles.set(collectionName, props.acceptedFiles);
|
||||
|
@ -186,10 +238,18 @@ export default function Upload(props: Props) {
|
|||
collectionWiseFiles = getCollectionWiseFiles();
|
||||
}
|
||||
try {
|
||||
const existingCollection = await syncCollections();
|
||||
for (const [collectionName, files] of collectionWiseFiles) {
|
||||
const collection = await createAlbum(collectionName);
|
||||
const collection = await createAlbum(
|
||||
collectionName,
|
||||
existingCollection
|
||||
);
|
||||
collections.push(collection);
|
||||
for (const file of files) {
|
||||
filesWithCollectionToUpload.push({ collection, file });
|
||||
filesWithCollectionToUpload.push({
|
||||
collectionID: collection.id,
|
||||
file,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -203,34 +263,30 @@ export default function Upload(props: Props) {
|
|||
});
|
||||
throw e;
|
||||
}
|
||||
await uploadFiles(filesWithCollectionToUpload);
|
||||
await uploadFiles(filesWithCollectionToUpload, collections);
|
||||
} catch (e) {
|
||||
logError(e, 'Failed to upload files to new collections');
|
||||
}
|
||||
};
|
||||
|
||||
const uploadFiles = async (
|
||||
filesWithCollectionToUpload: FileWithCollection[]
|
||||
filesWithCollectionToUpload: FileWithCollection[],
|
||||
collections?: Collection[]
|
||||
) => {
|
||||
try {
|
||||
props.setUploadInProgress(true);
|
||||
props.closeCollectionSelector();
|
||||
await props.syncWithRemote(true, true);
|
||||
const localFiles = await getLocalFiles();
|
||||
await UploadService.uploadFiles(
|
||||
await uploadManager.queueFilesForUpload(
|
||||
filesWithCollectionToUpload,
|
||||
localFiles,
|
||||
{
|
||||
setPercentComplete,
|
||||
setFileCounter,
|
||||
setUploadStage,
|
||||
setFileProgress,
|
||||
setUploadResult,
|
||||
},
|
||||
props.setFiles
|
||||
collections
|
||||
);
|
||||
} catch (err) {
|
||||
props.setBannerMessage(err.message);
|
||||
const message = getUserFacingErrorMessage(
|
||||
err.message,
|
||||
galleryContext.showPlanSelectorModal
|
||||
);
|
||||
props.setBannerMessage(message);
|
||||
setProgressView(false);
|
||||
throw err;
|
||||
} finally {
|
||||
|
@ -244,12 +300,15 @@ export default function Upload(props: Props) {
|
|||
props.setUploadInProgress(true);
|
||||
uploadInit();
|
||||
await props.syncWithRemote(true, true);
|
||||
const localFiles = await getLocalFiles();
|
||||
await UploadService.retryFailedFiles(localFiles);
|
||||
await uploadManager.retryFailedFiles();
|
||||
} catch (err) {
|
||||
props.setBannerMessage(err.message);
|
||||
const message = getUserFacingErrorMessage(
|
||||
err.message,
|
||||
galleryContext.showPlanSelectorModal
|
||||
);
|
||||
appContext.resetSharedFiles();
|
||||
props.setBannerMessage(message);
|
||||
setProgressView(false);
|
||||
throw err;
|
||||
} finally {
|
||||
props.setUploadInProgress(false);
|
||||
props.syncWithRemote();
|
||||
|
|
|
@ -11,6 +11,7 @@ const Wrapper = styled.div<{ isDisabled: boolean }>`
|
|||
min-height: 64px;
|
||||
right: 32px;
|
||||
transition: opacity 1s ease;
|
||||
cursor: pointer;
|
||||
opacity: ${(props) => (props.isDisabled ? 0 : 1)};
|
||||
`;
|
||||
function UploadButton({ openFileUploader, isFirstFetch }) {
|
||||
|
@ -24,7 +25,7 @@ function UploadButton({ openFileUploader, isFirstFetch }) {
|
|||
height="32px">
|
||||
<path fill="none" d="M0 0h24v24H0z" />
|
||||
<path
|
||||
fill="#2dc262"
|
||||
fill="#51cd7c"
|
||||
d="M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V8h16v10zM8 13.01l1.41 1.41L11 12.84V17h2v-4.16l1.59 1.59L16 13.01 12.01 9 8 13.01z"
|
||||
/>
|
||||
</svg>
|
||||
|
|
|
@ -3,11 +3,15 @@ import ExpandMore from 'components/icons/ExpandMore';
|
|||
import React, { useState } from 'react';
|
||||
import { Button, Modal, ProgressBar } from 'react-bootstrap';
|
||||
import { FileRejection } from 'react-dropzone';
|
||||
import { FileUploadResults, UPLOAD_STAGES } from 'services/uploadService';
|
||||
import {
|
||||
FileUploadResults,
|
||||
UPLOAD_STAGES,
|
||||
} from 'services/upload/uploadManager';
|
||||
import styled from 'styled-components';
|
||||
import { DESKTOP_APP_DOWNLOAD_URL } from 'utils/common';
|
||||
import constants from 'utils/strings/constants';
|
||||
import AlertBanner from './AlertBanner';
|
||||
import { Collapse } from 'react-collapse';
|
||||
import { ButtonVariant, getVariantColor } from './LinkButton';
|
||||
|
||||
interface Props {
|
||||
fileCounter;
|
||||
|
@ -25,25 +29,12 @@ interface FileProgresses {
|
|||
progress: number;
|
||||
}
|
||||
|
||||
const Content = styled.div<{
|
||||
collapsed: boolean;
|
||||
sm?: boolean;
|
||||
height?: number;
|
||||
}>`
|
||||
overflow: hidden;
|
||||
height: ${(props) => (props.collapsed ? '0px' : props.height + 'px')};
|
||||
transition: ${(props) => 'height ' + 0.001 * props.height + 's ease-out'};
|
||||
margin-bottom: 20px;
|
||||
& > p {
|
||||
padding-left: 35px;
|
||||
margin: 0;
|
||||
}
|
||||
`;
|
||||
const FileList = styled.ul`
|
||||
padding-left: 50px;
|
||||
padding-left: 30px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 0px;
|
||||
& > li {
|
||||
padding-left: 10px;
|
||||
padding-left: 5px;
|
||||
margin-bottom: 10px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
@ -52,18 +43,41 @@ const FileList = styled.ul`
|
|||
const SectionTitle = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px;
|
||||
color: #eee;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const Section = styled.div`
|
||||
margin: 20px 0;
|
||||
& > .ReactCollapse--collapse {
|
||||
transition: height 200ms;
|
||||
}
|
||||
word-break: break-word;
|
||||
padding: 0 20px;
|
||||
`;
|
||||
const SectionInfo = styled.div`
|
||||
margin: 4px 0;
|
||||
padding-left: 15px;
|
||||
`;
|
||||
|
||||
const Content = styled.div`
|
||||
padding-right: 30px;
|
||||
`;
|
||||
|
||||
const NotUploadSectionHeader = styled.div`
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
color: ${getVariantColor(ButtonVariant.warning)};
|
||||
border-bottom: 1px solid ${getVariantColor(ButtonVariant.warning)};
|
||||
margin: 0 20px;
|
||||
`;
|
||||
|
||||
interface ResultSectionProps {
|
||||
fileUploadResultMap: Map<FileUploadResults, string[]>;
|
||||
fileUploadResult: FileUploadResults;
|
||||
sectionTitle;
|
||||
sectionInfo;
|
||||
infoHeight: number;
|
||||
sectionTitle: any;
|
||||
sectionInfo?: any;
|
||||
}
|
||||
const ResultSection = (props: ResultSectionProps) => {
|
||||
const [listView, setListView] = useState(false);
|
||||
|
@ -72,23 +86,61 @@ const ResultSection = (props: ResultSectionProps) => {
|
|||
return <></>;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Section>
|
||||
<SectionTitle onClick={() => setListView(!listView)}>
|
||||
{' '}
|
||||
{props.sectionTitle}{' '}
|
||||
{props.sectionTitle}
|
||||
{listView ? <ExpandLess /> : <ExpandMore />}
|
||||
</SectionTitle>
|
||||
<Content
|
||||
collapsed={!listView}
|
||||
height={fileList.length * 33 + props.infoHeight}>
|
||||
<p>{props.sectionInfo}</p>
|
||||
<Collapse isOpened={listView}>
|
||||
<Content>
|
||||
{props.sectionInfo && (
|
||||
<SectionInfo>{props.sectionInfo}</SectionInfo>
|
||||
)}
|
||||
<FileList>
|
||||
{fileList.map((fileName) => (
|
||||
<li key={fileName}>{fileName}</li>
|
||||
))}
|
||||
</FileList>
|
||||
</Content>
|
||||
</>
|
||||
</Collapse>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
interface InProgressProps {
|
||||
sectionTitle: string;
|
||||
fileProgressStatuses: FileProgresses[];
|
||||
}
|
||||
const InProgressSection = (props: InProgressProps) => {
|
||||
const [listView, setListView] = useState(true);
|
||||
const fileList = props.fileProgressStatuses;
|
||||
if (!fileList?.length) {
|
||||
return <></>;
|
||||
}
|
||||
if (!fileList?.length) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<Section>
|
||||
<SectionTitle onClick={() => setListView(!listView)}>
|
||||
{props.sectionTitle}
|
||||
{listView ? <ExpandLess /> : <ExpandMore />}
|
||||
</SectionTitle>
|
||||
<Collapse isOpened={listView}>
|
||||
<Content>
|
||||
<FileList>
|
||||
{fileList.map(({ fileName, progress }) => (
|
||||
<li key={fileName}>
|
||||
{constants.FILE_UPLOAD_PROGRESS(
|
||||
fileName,
|
||||
progress
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</FileList>
|
||||
</Content>
|
||||
</Collapse>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -152,35 +204,24 @@ export default function UploadProgress(props: Props) {
|
|||
variant="upload-progress-bar"
|
||||
/>
|
||||
)}
|
||||
{fileProgressStatuses.length > 0 && (
|
||||
<FileList>
|
||||
{fileProgressStatuses.map(({ fileName, progress }) => (
|
||||
<li key={fileName} style={{ marginTop: '12px' }}>
|
||||
{props.uploadStage === UPLOAD_STAGES.FINISH
|
||||
? fileName
|
||||
: constants.FILE_UPLOAD_PROGRESS(
|
||||
fileName,
|
||||
progress
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</FileList>
|
||||
)}
|
||||
<InProgressSection
|
||||
fileProgressStatuses={fileProgressStatuses}
|
||||
sectionTitle={constants.INPROGRESS_UPLOADS}
|
||||
/>
|
||||
|
||||
<ResultSection
|
||||
fileUploadResultMap={fileUploadResultMap}
|
||||
fileUploadResult={FileUploadResults.UPLOADED}
|
||||
sectionTitle={constants.SUCCESSFUL_UPLOADS}
|
||||
sectionInfo={constants.SUCCESS_INFO}
|
||||
infoHeight={32}
|
||||
/>
|
||||
|
||||
{props.uploadStage === UPLOAD_STAGES.FINISH &&
|
||||
filesNotUploaded && (
|
||||
<AlertBanner variant="warning">
|
||||
<NotUploadSectionHeader>
|
||||
{constants.FILE_NOT_UPLOADED_LIST}
|
||||
</AlertBanner>
|
||||
</NotUploadSectionHeader>
|
||||
)}
|
||||
|
||||
<ResultSection
|
||||
fileUploadResultMap={fileUploadResultMap}
|
||||
fileUploadResult={FileUploadResults.BLOCKED}
|
||||
|
@ -188,30 +229,31 @@ export default function UploadProgress(props: Props) {
|
|||
sectionInfo={constants.ETAGS_BLOCKED(
|
||||
DESKTOP_APP_DOWNLOAD_URL
|
||||
)}
|
||||
infoHeight={140}
|
||||
/>
|
||||
<ResultSection
|
||||
fileUploadResultMap={fileUploadResultMap}
|
||||
fileUploadResult={FileUploadResults.FAILED}
|
||||
sectionTitle={constants.FAILED_UPLOADS}
|
||||
sectionInfo={constants.FAILED_INFO}
|
||||
infoHeight={48}
|
||||
/>
|
||||
<ResultSection
|
||||
fileUploadResultMap={fileUploadResultMap}
|
||||
fileUploadResult={FileUploadResults.SKIPPED}
|
||||
sectionTitle={constants.SKIPPED_FILES}
|
||||
sectionInfo={constants.SKIPPED_INFO}
|
||||
infoHeight={32}
|
||||
/>
|
||||
<ResultSection
|
||||
fileUploadResultMap={fileUploadResultMap}
|
||||
fileUploadResult={FileUploadResults.UNSUPPORTED}
|
||||
sectionTitle={constants.UNSUPPORTED_FILES}
|
||||
sectionInfo={constants.UNSUPPORTED_INFO}
|
||||
infoHeight={32}
|
||||
/>
|
||||
|
||||
<ResultSection
|
||||
fileUploadResultMap={fileUploadResultMap}
|
||||
fileUploadResult={FileUploadResults.TOO_LARGE}
|
||||
sectionTitle={constants.TOO_LARGE_UPLOADS}
|
||||
sectionInfo={constants.TOO_LARGE_INFO}
|
||||
/>
|
||||
</Modal.Body>
|
||||
{props.uploadStage === UPLOAD_STAGES.FINISH && (
|
||||
<Modal.Footer style={{ border: 'none' }}>
|
||||
{props.uploadStage === UPLOAD_STAGES.FINISH &&
|
||||
|
@ -235,7 +277,6 @@ export default function UploadProgress(props: Props) {
|
|||
))}
|
||||
</Modal.Footer>
|
||||
)}
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,16 +4,16 @@ import Navbar from 'components/Navbar';
|
|||
import constants from 'utils/strings/constants';
|
||||
import { useRouter } from 'next/router';
|
||||
import Container from 'components/Container';
|
||||
import Head from 'next/head';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import 'photoswipe/dist/photoswipe.css';
|
||||
import EnteSpinner from 'components/EnteSpinner';
|
||||
import { logError } from '../utils/sentry';
|
||||
import { Workbox } from 'workbox-window';
|
||||
// import { Workbox } from 'workbox-window';
|
||||
import { getEndpoint } from 'utils/common/apiUtil';
|
||||
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||
import HTTPService from 'services/HTTPService';
|
||||
import FlashMessageBar from 'components/FlashMessageBar';
|
||||
import Head from 'next/head';
|
||||
|
||||
const GlobalStyles = createGlobalStyle`
|
||||
/* ubuntu-regular - latin */
|
||||
|
@ -130,12 +130,12 @@ const GlobalStyles = createGlobalStyle`
|
|||
background-color:#202020 !important;
|
||||
}
|
||||
.modal-dialog{
|
||||
margin:5% auto;
|
||||
margin:5vh auto;
|
||||
width:90%;
|
||||
}
|
||||
.modal-body{
|
||||
max-height:80vh;
|
||||
overflow:auto;
|
||||
max-height:74vh;
|
||||
overflow-y:auto;
|
||||
}
|
||||
.modal-xl{
|
||||
max-width:90% !important;
|
||||
|
@ -176,28 +176,42 @@ const GlobalStyles = createGlobalStyle`
|
|||
box-shadow: none;
|
||||
}
|
||||
.btn-success {
|
||||
background: #2dc262;
|
||||
border-color: #29a354;
|
||||
background: #51cd7c;
|
||||
color: #fff;
|
||||
border-color: #51cd7c;
|
||||
}
|
||||
.btn-success:hover .btn-success:focus .btn-success:active {
|
||||
background-color: #29a354;
|
||||
border-color: #2dc262;
|
||||
border-color: #51cd7c;
|
||||
color: #242424;
|
||||
}
|
||||
.btn-success:disabled {
|
||||
background-color: #69b383;
|
||||
}
|
||||
.btn-outline-success {
|
||||
color: #2dc262;
|
||||
border-color: #2dc262;
|
||||
border-width: 2px;
|
||||
background: #51cd7c;
|
||||
color: #fff;
|
||||
border-color: #51cd7c;
|
||||
}
|
||||
.btn-outline-success:hover:enabled {
|
||||
background: #2dc262;
|
||||
color: white;
|
||||
background: #4db76c;
|
||||
color: #fff;
|
||||
}
|
||||
.btn-outline-danger, .btn-outline-secondary, .btn-outline-primary{
|
||||
border-width: 2px;
|
||||
}
|
||||
a {
|
||||
color: #fff;
|
||||
}
|
||||
a:hover {
|
||||
color: #51cd7c;
|
||||
}
|
||||
.btn-link {
|
||||
color: #fff;
|
||||
}
|
||||
.btn-link:hover {
|
||||
color: #51cd7c;
|
||||
}
|
||||
.btn-link-danger {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
@ -262,7 +276,7 @@ const GlobalStyles = createGlobalStyle`
|
|||
background: rgba(0, 0, 0, 0.8) !important;
|
||||
}
|
||||
.bg-upload-progress-bar {
|
||||
background-color: #2dc262;
|
||||
background-color: #51cd7c;
|
||||
}
|
||||
.custom-switch.custom-switch-md .custom-control-label {
|
||||
padding-left: 2rem;
|
||||
|
@ -308,9 +322,6 @@ const GlobalStyles = createGlobalStyle`
|
|||
text-decoration: none;
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
.submitButton:hover > .spinner-border{
|
||||
color:white;
|
||||
}
|
||||
hr{
|
||||
border-top: 1rem solid #444 !important;
|
||||
}
|
||||
|
@ -336,7 +347,7 @@ const GlobalStyles = createGlobalStyle`
|
|||
margin-right: 12px;
|
||||
}
|
||||
.carousel-indicators .active {
|
||||
background-color: #2dc262;
|
||||
background-color: #51cd7c;
|
||||
}
|
||||
div.otp-input input {
|
||||
width: 36px !important;
|
||||
|
@ -349,7 +360,7 @@ const GlobalStyles = createGlobalStyle`
|
|||
}
|
||||
|
||||
div.otp-input input:not(:placeholder-shown) , div.otp-input input:focus{
|
||||
border: 2px solid #2dc262;
|
||||
border: 2px solid #51cd7c;
|
||||
border-radius:1px;
|
||||
-webkit-transition: 0.5s;
|
||||
transition: 0.5s;
|
||||
|
@ -431,7 +442,6 @@ export default function App({ Component, err }) {
|
|||
const [sharedFiles, setSharedFiles] = useState<File[]>(null);
|
||||
const [redirectName, setRedirectName] = useState<string>(null);
|
||||
const [flashMessage, setFlashMessage] = useState<FlashMessage>(null);
|
||||
const [pageRootURL, setPageRootURL] = useState<URL>(null);
|
||||
useEffect(() => {
|
||||
if (
|
||||
!('serviceWorker' in navigator) ||
|
||||
|
@ -440,8 +450,8 @@ export default function App({ Component, err }) {
|
|||
console.warn('Progressive Web App support is disabled');
|
||||
return;
|
||||
}
|
||||
const wb = new Workbox('sw.js', { scope: '/' });
|
||||
wb.register();
|
||||
// const wb = new Workbox('sw.js', { scope: '/' });
|
||||
// wb.register();
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.onmessage = (event) => {
|
||||
|
@ -450,12 +460,19 @@ export default function App({ Component, err }) {
|
|||
setSharedFiles(files);
|
||||
}
|
||||
};
|
||||
navigator.serviceWorker
|
||||
.getRegistrations()
|
||||
.then(function (registrations) {
|
||||
for (const registration of registrations) {
|
||||
registration.unregister();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
HTTPService.getInterceptors().response.use(
|
||||
(resp) => resp,
|
||||
(error) => {
|
||||
logError(error);
|
||||
logError(error, 'Network Error');
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
@ -482,7 +499,6 @@ export default function App({ Component, err }) {
|
|||
setRedirectName(redirect);
|
||||
}
|
||||
}
|
||||
setPageRootURL(new URL(window.location.href));
|
||||
|
||||
router.events.on('routeChangeStart', (url: string) => {
|
||||
if (window.location.pathname !== url.split('?')[0]) {
|
||||
|
@ -516,28 +532,11 @@ export default function App({ Component, err }) {
|
|||
setFlashMessage(flashMessages);
|
||||
setTimeout(() => setFlashMessage(null), 5000);
|
||||
};
|
||||
// ho ja yaar
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{constants.TITLE}</title>
|
||||
{/* Cloudflare Web Analytics */}
|
||||
{pageRootURL?.hostname &&
|
||||
(pageRootURL.hostname === 'photos.ente.io' ? (
|
||||
<script
|
||||
defer
|
||||
src="https://static.cloudflareinsights.com/beacon.min.js"
|
||||
data-cf-beacon='{"token": "6a388287b59c439cb2070f78cc89dde1"}'
|
||||
/>
|
||||
) : pageRootURL.hostname === 'web.ente.io' ? (
|
||||
<script
|
||||
defer
|
||||
src="https://static.cloudflareinsights.com/beacon.min.js"
|
||||
data-cf-beacon='{"token": "dfde128b7bb34a618ad34a08f1ba7609"}'
|
||||
/>
|
||||
) : (
|
||||
console.warn('Web analytics is disabled')
|
||||
))}
|
||||
{/* End Cloudflare Web Analytics */}
|
||||
</Head>
|
||||
<GlobalStyles />
|
||||
{showNavbar && (
|
||||
|
|
|
@ -8,6 +8,7 @@ import { getToken } from 'utils/common/key';
|
|||
import EnteSpinner from 'components/EnteSpinner';
|
||||
import ChangeEmailForm from 'components/ChangeEmail';
|
||||
import EnteCard from 'components/EnteCard';
|
||||
import { PAGES } from 'types';
|
||||
|
||||
function ChangeEmailPage() {
|
||||
const [email, setEmail] = useState('');
|
||||
|
@ -18,7 +19,7 @@ function ChangeEmailPage() {
|
|||
useEffect(() => {
|
||||
const token = getToken();
|
||||
if (!token) {
|
||||
router.push('/');
|
||||
router.push(PAGES.ROOT);
|
||||
return;
|
||||
}
|
||||
setWaiting(false);
|
||||
|
|
|
@ -12,6 +12,7 @@ import { setKeys, UpdatedKey } from 'services/userService';
|
|||
import SetPasswordForm from 'components/SetPasswordForm';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { SESSION_KEYS } from 'utils/storage/sessionStorage';
|
||||
import { PAGES } from 'types';
|
||||
|
||||
export interface KEK {
|
||||
key: string;
|
||||
|
@ -27,7 +28,7 @@ export default function Generate() {
|
|||
useEffect(() => {
|
||||
const user = getData(LS_KEYS.USER);
|
||||
if (!user?.token) {
|
||||
router.push('/');
|
||||
router.push(PAGES.ROOT);
|
||||
} else {
|
||||
setToken(user.token);
|
||||
}
|
||||
|
@ -69,8 +70,8 @@ export default function Generate() {
|
|||
redirectToGallery();
|
||||
};
|
||||
const redirectToGallery = () => {
|
||||
setData(LS_KEYS.SHOW_BACK_BUTTON, { value: false });
|
||||
router.push('/gallery');
|
||||
setData(LS_KEYS.SHOW_BACK_BUTTON, { value: true });
|
||||
router.push(PAGES.GALLERY);
|
||||
};
|
||||
return (
|
||||
<SetPasswordForm
|
||||
|
|
|
@ -3,7 +3,7 @@ import React, { useContext, useEffect, useState } from 'react';
|
|||
import constants from 'utils/strings/constants';
|
||||
import { clearData, getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||
import { useRouter } from 'next/router';
|
||||
import { KeyAttributes } from 'types';
|
||||
import { KeyAttributes, PAGES } from 'types';
|
||||
import { SESSION_KEYS, getKey } from 'utils/storage/sessionStorage';
|
||||
import CryptoWorker, {
|
||||
decryptAndStoreToken,
|
||||
|
@ -25,20 +25,20 @@ export default function Credentials() {
|
|||
const appContext = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
router.prefetch('/gallery');
|
||||
router.prefetch(PAGES.GALLERY);
|
||||
const user = getData(LS_KEYS.USER);
|
||||
const keyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES);
|
||||
const key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
|
||||
if (
|
||||
(!user?.token && !user?.encryptedToken) ||
|
||||
!keyAttributes?.memLimit
|
||||
(keyAttributes && !keyAttributes.memLimit)
|
||||
) {
|
||||
clearData();
|
||||
router.push('/');
|
||||
router.push(PAGES.ROOT);
|
||||
} else if (!keyAttributes) {
|
||||
router.push('/generate');
|
||||
router.push(PAGES.GENERATE);
|
||||
} else if (key) {
|
||||
router.push('/gallery');
|
||||
router.push(PAGES.GALLERY);
|
||||
} else {
|
||||
setKeyAttributes(keyAttributes);
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ export default function Credentials() {
|
|||
keyAttributes.memLimit
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('failed to deriveKey ', e.message);
|
||||
logError(e, 'failed to derive key');
|
||||
throw e;
|
||||
}
|
||||
try {
|
||||
|
@ -76,9 +76,9 @@ export default function Credentials() {
|
|||
await SaveKeyInSessionStore(SESSION_KEYS.ENCRYPTION_KEY, key);
|
||||
await decryptAndStoreToken(key);
|
||||
|
||||
router.push('/gallery');
|
||||
router.push(PAGES.GALLERY);
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
logError(e, 'user entered a wrong password');
|
||||
setFieldError('passphrase', constants.INCORRECT_PASSPHRASE);
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -86,7 +86,6 @@ export default function Credentials() {
|
|||
'passphrase',
|
||||
`${constants.UNKNOWN_ERROR} ${e.message}`
|
||||
);
|
||||
console.error('failed to verifyPassphrase ', e.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -113,7 +112,7 @@ export default function Credentials() {
|
|||
}}>
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={() => router.push('/recover')}>
|
||||
onClick={() => router.push(PAGES.RECOVER)}>
|
||||
{constants.FORGOT_PASSWORD}
|
||||
</Button>
|
||||
<Button variant="link" onClick={logoutUser}>
|
||||
|
|
|
@ -61,6 +61,7 @@ import Upload from 'components/pages/gallery/Upload';
|
|||
import Collections from 'components/pages/gallery/Collections';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { CustomError, ServerErrorCodes } from 'utils/common/errorUtil';
|
||||
import { PAGES } from 'types';
|
||||
|
||||
export const DeadCenter = styled.div`
|
||||
flex: 1;
|
||||
|
@ -98,11 +99,13 @@ export interface SearchStats {
|
|||
type GalleryContextType = {
|
||||
thumbs: Map<number, string>;
|
||||
files: Map<number, string>;
|
||||
showPlanSelectorModal: () => void;
|
||||
};
|
||||
|
||||
const defaultGalleryContext: GalleryContextType = {
|
||||
thumbs: new Map(),
|
||||
files: new Map(),
|
||||
showPlanSelectorModal: () => null,
|
||||
};
|
||||
|
||||
export const GalleryContext = createContext<GalleryContextType>(
|
||||
|
@ -144,7 +147,6 @@ export default function Gallery() {
|
|||
} = useDropzone({
|
||||
noClick: true,
|
||||
noKeyboard: true,
|
||||
accept: ['image/*', 'video/*', '.json'],
|
||||
disabled: uploadInProgress,
|
||||
});
|
||||
|
||||
|
@ -161,7 +163,7 @@ export default function Gallery() {
|
|||
useEffect(() => {
|
||||
const key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
|
||||
if (!key) {
|
||||
router.push('/');
|
||||
router.push(PAGES.ROOT);
|
||||
return;
|
||||
}
|
||||
const main = async () => {
|
||||
|
@ -175,8 +177,12 @@ export default function Gallery() {
|
|||
const collections = await getLocalCollections();
|
||||
setFiles(files);
|
||||
setCollections(collections);
|
||||
await initDerivativeState(collections, files);
|
||||
await checkSubscriptionPurchase(setDialogMessage, router);
|
||||
await setDerivativeState(collections, files);
|
||||
await checkSubscriptionPurchase(
|
||||
setDialogMessage,
|
||||
router,
|
||||
setLoading
|
||||
);
|
||||
await syncWithRemote(true);
|
||||
setIsFirstLoad(false);
|
||||
setJustSignedUp(false);
|
||||
|
@ -208,8 +214,9 @@ export default function Gallery() {
|
|||
!silent && loadingBar.current?.continuousStart();
|
||||
await billingService.syncSubscription();
|
||||
const collections = await syncCollections();
|
||||
setCollections(collections);
|
||||
const { files } = await syncFiles(collections, setFiles);
|
||||
await initDerivativeState(collections, files);
|
||||
await setDerivativeState(collections, files);
|
||||
} catch (e) {
|
||||
switch (e.message) {
|
||||
case ServerErrorCodes.SESSION_EXPIRED:
|
||||
|
@ -228,7 +235,7 @@ export default function Gallery() {
|
|||
break;
|
||||
case CustomError.KEY_MISSING:
|
||||
clearKeys();
|
||||
router.push('/credentials');
|
||||
router.push(PAGES.CREDENTIALS);
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
|
@ -241,7 +248,7 @@ export default function Gallery() {
|
|||
}
|
||||
};
|
||||
|
||||
const initDerivativeState = async (collections, files) => {
|
||||
const setDerivativeState = async (collections, files) => {
|
||||
const nonEmptyCollections = getNonEmptyCollections(collections, files);
|
||||
const collectionsAndTheirLatestFile =
|
||||
await getCollectionsAndTheirLatestFile(nonEmptyCollections, files);
|
||||
|
@ -262,7 +269,7 @@ export default function Gallery() {
|
|||
};
|
||||
|
||||
const selectCollection = (id?: number) => {
|
||||
const href = `/gallery?collection=${id || ''}`;
|
||||
const href = `/gallery${id ? `?collection=${id.toString()}` : ''}`;
|
||||
router.push(href, undefined, { shallow: true });
|
||||
};
|
||||
|
||||
|
@ -349,7 +356,7 @@ export default function Gallery() {
|
|||
<EnteSpinner />
|
||||
</LoadingOverlay>
|
||||
)}
|
||||
<LoadingBar color="#2dc262" ref={loadingBar} />
|
||||
<LoadingBar color="#51cd7c" ref={loadingBar} />
|
||||
{isFirstLoad && (
|
||||
<AlertContainer>
|
||||
{constants.INITIAL_LOAD_DELAY_WARNING}
|
||||
|
|
|
@ -10,9 +10,9 @@ import {
|
|||
generateKeyAttributes,
|
||||
} from 'utils/crypto';
|
||||
import SetPasswordForm from 'components/SetPasswordForm';
|
||||
import { setJustSignedUp } from 'utils/storage';
|
||||
import { justSignedUp, setJustSignedUp } from 'utils/storage';
|
||||
import RecoveryKeyModal from 'components/RecoveryKeyModal';
|
||||
import { KeyAttributes } from 'types';
|
||||
import { KeyAttributes, PAGES } from 'types';
|
||||
import Container from 'components/Container';
|
||||
import EnteSpinner from 'components/EnteSpinner';
|
||||
import { AppContext } from 'pages/_app';
|
||||
|
@ -28,24 +28,28 @@ export default function Generate() {
|
|||
const [token, setToken] = useState<string>();
|
||||
const router = useRouter();
|
||||
const [recoverModalView, setRecoveryModalView] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const appContext = useContext(AppContext);
|
||||
useEffect(() => {
|
||||
const main = async () => {
|
||||
setLoading(true);
|
||||
const key: string = getKey(SESSION_KEYS.ENCRYPTION_KEY);
|
||||
const keyAttributes: KeyAttributes = getData(
|
||||
LS_KEYS.ORIGINAL_KEY_ATTRIBUTES
|
||||
);
|
||||
router.prefetch('/gallery');
|
||||
router.prefetch('/credentials');
|
||||
router.prefetch(PAGES.GALLERY);
|
||||
router.prefetch(PAGES.CREDENTIALS);
|
||||
const user: User = getData(LS_KEYS.USER);
|
||||
if (!user?.token) {
|
||||
router.push('/');
|
||||
} else if (keyAttributes?.encryptedKey) {
|
||||
router.push('/credentials');
|
||||
router.push(PAGES.ROOT);
|
||||
} else if (key) {
|
||||
router.push('/gallery');
|
||||
if (justSignedUp()) {
|
||||
setRecoveryModalView(true);
|
||||
setLoading(false);
|
||||
} else {
|
||||
router.push(PAGES.GALLERY);
|
||||
}
|
||||
} else if (keyAttributes?.encryptedKey) {
|
||||
router.push(PAGES.CREDENTIALS);
|
||||
} else {
|
||||
setToken(user.token);
|
||||
setLoading(false);
|
||||
|
@ -89,7 +93,7 @@ export default function Generate() {
|
|||
show={recoverModalView}
|
||||
onHide={() => {
|
||||
setRecoveryModalView(false);
|
||||
router.push('/gallery');
|
||||
router.push(PAGES.GALLERY);
|
||||
}}
|
||||
somethingWentWrong={() => null}
|
||||
/>
|
||||
|
|
|
@ -12,6 +12,7 @@ import constants from 'utils/strings/constants';
|
|||
import localForage from 'utils/storage/localForage';
|
||||
import IncognitoWarning from 'components/IncognitoWarning';
|
||||
import { logError } from 'utils/sentry';
|
||||
import { PAGES } from 'types';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
|
@ -81,7 +82,7 @@ const UpperText = styled(TextContainer)`
|
|||
`;
|
||||
|
||||
const FeatureText = styled.div`
|
||||
color: #2dc262;
|
||||
color: #51cd7c;
|
||||
font-weight: bold;
|
||||
padding-top: 20px;
|
||||
font-size: 24px;
|
||||
|
@ -106,7 +107,7 @@ export default function LandingPage() {
|
|||
const main = async () => {
|
||||
const user = getData(LS_KEYS.USER);
|
||||
if (user?.email) {
|
||||
await router.push('/verify');
|
||||
await router.push(PAGES.VERIFY);
|
||||
}
|
||||
try {
|
||||
await localForage.ready();
|
||||
|
@ -165,8 +166,8 @@ export default function LandingPage() {
|
|||
<Button
|
||||
variant="outline-success"
|
||||
size="lg"
|
||||
style={{ color: '#fff', padding: '10px 50px' }}
|
||||
onClick={() => router.push('signup')}>
|
||||
style={{ padding: '10px 50px' }}
|
||||
onClick={() => router.push(PAGES.SIGNUP)}>
|
||||
{constants.SIGN_UP}
|
||||
</Button>
|
||||
<br />
|
||||
|
@ -174,8 +175,8 @@ export default function LandingPage() {
|
|||
variant="link"
|
||||
size="lg"
|
||||
style={{ color: '#fff', padding: '10px 50px' }}
|
||||
onClick={() => router.push('login')}>
|
||||
{constants.SIGN_IN}
|
||||
onClick={() => router.push(PAGES.LOGIN)}>
|
||||
{constants.LOGIN}
|
||||
</Button>
|
||||
</MobileBox>
|
||||
<DesktopBox>
|
||||
|
|
|
@ -6,6 +6,7 @@ import Login from 'components/Login';
|
|||
import Container from 'components/Container';
|
||||
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||
import Card from 'react-bootstrap/Card';
|
||||
import { PAGES } from 'types';
|
||||
|
||||
export default function Home() {
|
||||
const router = useRouter();
|
||||
|
@ -13,18 +14,18 @@ export default function Home() {
|
|||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
router.prefetch('/verify');
|
||||
router.prefetch('/signup');
|
||||
router.prefetch(PAGES.VERIFY);
|
||||
router.prefetch(PAGES.SIGNUP);
|
||||
const user = getData(LS_KEYS.USER);
|
||||
if (user?.email) {
|
||||
router.push('/verify');
|
||||
router.push(PAGES.VERIFY);
|
||||
}
|
||||
setLoading(false);
|
||||
appContext.showNavBar(false);
|
||||
}, []);
|
||||
|
||||
const register = () => {
|
||||
router.push('/signup');
|
||||
router.push(PAGES.SIGNUP);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { clearData, getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||
import {
|
||||
clearData,
|
||||
getData,
|
||||
LS_KEYS,
|
||||
setData,
|
||||
} from 'utils/storage/localStorage';
|
||||
import { useRouter } from 'next/router';
|
||||
import { KeyAttributes } from 'types';
|
||||
import CryptoWorker, { SaveKeyInSessionStore } from 'utils/crypto';
|
||||
import { KeyAttributes, PAGES } from 'types';
|
||||
import CryptoWorker, {
|
||||
decryptAndStoreToken,
|
||||
SaveKeyInSessionStore,
|
||||
} from 'utils/crypto';
|
||||
import SingleInputForm from 'components/SingleInputForm';
|
||||
import MessageDialog from 'components/MessageDialog';
|
||||
import Container from 'components/Container';
|
||||
|
@ -21,7 +29,7 @@ export default function Recover() {
|
|||
const appContext = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
router.prefetch('/gallery');
|
||||
router.prefetch(PAGES.GALLERY);
|
||||
const user: User = getData(LS_KEYS.USER);
|
||||
const keyAttributes: KeyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES);
|
||||
const key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
|
||||
|
@ -30,11 +38,11 @@ export default function Recover() {
|
|||
!keyAttributes?.memLimit
|
||||
) {
|
||||
clearData();
|
||||
router.push('/');
|
||||
router.push(PAGES.ROOT);
|
||||
} else if (!keyAttributes) {
|
||||
router.push('/generate');
|
||||
router.push(PAGES.GENERATE);
|
||||
} else if (key) {
|
||||
router.push('/gallery');
|
||||
router.push(PAGES.GALLERY);
|
||||
} else {
|
||||
setKeyAttributes(keyAttributes);
|
||||
}
|
||||
|
@ -50,10 +58,12 @@ export default function Recover() {
|
|||
await cryptoWorker.fromHex(recoveryKey)
|
||||
);
|
||||
await SaveKeyInSessionStore(SESSION_KEYS.ENCRYPTION_KEY, masterKey);
|
||||
await decryptAndStoreToken(masterKey);
|
||||
|
||||
router.push('/changePassword');
|
||||
setData(LS_KEYS.SHOW_BACK_BUTTON, { value: false });
|
||||
router.push(PAGES.CHANGE_PASSWORD);
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
logError(e, 'password recovery failed');
|
||||
setFieldError('passphrase', constants.INCORRECT_RECOVERY_KEY);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -6,6 +6,7 @@ import Container from 'components/Container';
|
|||
import EnteSpinner from 'components/EnteSpinner';
|
||||
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||
import SignUp from 'components/SignUp';
|
||||
import { PAGES } from 'types';
|
||||
|
||||
export default function SignUpPage() {
|
||||
const router = useRouter();
|
||||
|
@ -13,18 +14,18 @@ export default function SignUpPage() {
|
|||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
router.prefetch('/verify');
|
||||
router.prefetch('/login');
|
||||
router.prefetch(PAGES.VERIFY);
|
||||
router.prefetch(PAGES.LOGIN);
|
||||
const user = getData(LS_KEYS.USER);
|
||||
if (user?.email) {
|
||||
router.push('/verify');
|
||||
router.push(PAGES.VERIFY);
|
||||
}
|
||||
setLoading(false);
|
||||
appContext.showNavBar(false);
|
||||
}, []);
|
||||
|
||||
const login = () => {
|
||||
router.push('/login');
|
||||
router.push(PAGES.LOGIN);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
|
||||
import { useRouter } from 'next/router';
|
||||
|
@ -10,6 +10,8 @@ import { Card, Button } from 'react-bootstrap';
|
|||
import LogoImg from 'components/LogoImg';
|
||||
import { logError } from 'utils/sentry';
|
||||
import { recoverTwoFactor, removeTwoFactor } from 'services/userService';
|
||||
import { AppContext, FLASH_MESSAGE_TYPE } from 'pages/_app';
|
||||
import { PAGES } from 'types';
|
||||
|
||||
export default function Recover() {
|
||||
const router = useRouter();
|
||||
|
@ -17,13 +19,14 @@ export default function Recover() {
|
|||
const [encryptedTwoFactorSecret, setEncryptedTwoFactorSecret] =
|
||||
useState<B64EncryptionResult>(null);
|
||||
const [sessionID, setSessionID] = useState(null);
|
||||
const appContext = useContext(AppContext);
|
||||
useEffect(() => {
|
||||
router.prefetch('/gallery');
|
||||
router.prefetch(PAGES.GALLERY);
|
||||
const user = getData(LS_KEYS.USER);
|
||||
if (!user.isTwoFactorEnabled && (user.encryptedToken || user.token)) {
|
||||
router.push('/credential');
|
||||
router.push(PAGES.GENERATE);
|
||||
} else if (!user.email || !user.twoFactorSessionID) {
|
||||
router.push('/');
|
||||
router.push(PAGES.ROOT);
|
||||
} else {
|
||||
setSessionID(user.twoFactorSessionID);
|
||||
}
|
||||
|
@ -56,9 +59,13 @@ export default function Recover() {
|
|||
isTwoFactorEnabled: false,
|
||||
});
|
||||
setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes);
|
||||
router.push('/credentials');
|
||||
appContext.setDisappearingFlashMessage({
|
||||
message: constants.TWO_FACTOR_DISABLE_SUCCESS,
|
||||
type: FLASH_MESSAGE_TYPE.INFO,
|
||||
});
|
||||
router.push(PAGES.CREDENTIALS);
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
logError(e, 'two factor recovery failed');
|
||||
setFieldError('passphrase', constants.INCORRECT_RECOVERY_KEY);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -18,6 +18,7 @@ import { B64EncryptionResult } from 'utils/crypto';
|
|||
import { encryptWithRecoveryKey } from 'utils/crypto';
|
||||
import { setData, LS_KEYS, getData } from 'utils/storage/localStorage';
|
||||
import { AppContext, FLASH_MESSAGE_TYPE } from 'pages/_app';
|
||||
import { PAGES } from 'types';
|
||||
|
||||
enum SetupMode {
|
||||
QR_CODE,
|
||||
|
@ -58,7 +59,7 @@ export default function SetupTwoFactor() {
|
|||
message: constants.TWO_FACTOR_SETUP_FAILED,
|
||||
type: FLASH_MESSAGE_TYPE.DANGER,
|
||||
});
|
||||
router.push('/gallery');
|
||||
router.push(PAGES.GALLERY);
|
||||
}
|
||||
};
|
||||
main();
|
||||
|
@ -73,7 +74,7 @@ export default function SetupTwoFactor() {
|
|||
message: constants.TWO_FACTOR_SETUP_SUCCESS,
|
||||
type: FLASH_MESSAGE_TYPE.SUCCESS,
|
||||
});
|
||||
router.push('/gallery');
|
||||
router.push(PAGES.GALLERY);
|
||||
};
|
||||
return (
|
||||
<Container>
|
||||
|
|
|
@ -5,6 +5,7 @@ import router from 'next/router';
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Card } from 'react-bootstrap';
|
||||
import { logoutUser, User, verifyTwoFactor } from 'services/userService';
|
||||
import { PAGES } from 'types';
|
||||
import { setData, LS_KEYS, getData } from 'utils/storage/localStorage';
|
||||
import constants from 'utils/strings/constants';
|
||||
|
||||
|
@ -13,15 +14,15 @@ export default function Home() {
|
|||
|
||||
useEffect(() => {
|
||||
const main = async () => {
|
||||
router.prefetch('/credentials');
|
||||
router.prefetch(PAGES.CREDENTIALS);
|
||||
const user: User = getData(LS_KEYS.USER);
|
||||
if (
|
||||
!user.isTwoFactorEnabled &&
|
||||
(user.encryptedToken || user.token)
|
||||
) {
|
||||
router.push('/credential');
|
||||
router.push(PAGES.CREDENTIALS);
|
||||
} else if (!user?.email || !user.twoFactorSessionID) {
|
||||
router.push('/');
|
||||
router.push(PAGES.ROOT);
|
||||
} else {
|
||||
setSessionID(user.twoFactorSessionID);
|
||||
}
|
||||
|
@ -40,7 +41,7 @@ export default function Home() {
|
|||
id,
|
||||
});
|
||||
setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes);
|
||||
router.push('/credentials');
|
||||
router.push(PAGES.CREDENTIALS);
|
||||
} catch (e) {
|
||||
if (e.status === 404) {
|
||||
logoutUser();
|
||||
|
@ -70,7 +71,9 @@ export default function Home() {
|
|||
}}>
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={() => router.push('/two-factor/recover')}>
|
||||
onClick={() =>
|
||||
router.push(PAGES.TWO_FACTOR_RECOVER)
|
||||
}>
|
||||
{constants.LOST_DEVICE}
|
||||
</Button>
|
||||
<Button variant="link" onClick={logoutUser}>
|
||||
|
|
|
@ -13,12 +13,15 @@ import {
|
|||
logoutUser,
|
||||
clearFiles,
|
||||
EmailVerificationResponse,
|
||||
User,
|
||||
putAttributes,
|
||||
} from 'services/userService';
|
||||
import { setIsFirstLogin } from 'utils/storage';
|
||||
import SubmitButton from 'components/SubmitButton';
|
||||
import { clearKeys } from 'utils/storage/sessionStorage';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import LogoImg from 'components/LogoImg';
|
||||
import { KeyAttributes, PAGES } from 'types';
|
||||
|
||||
interface formValues {
|
||||
ott: string;
|
||||
|
@ -33,12 +36,20 @@ export default function Verify() {
|
|||
|
||||
useEffect(() => {
|
||||
const main = async () => {
|
||||
router.prefetch('/twoFactor/verify');
|
||||
router.prefetch('/credentials');
|
||||
router.prefetch('/generate');
|
||||
const user = getData(LS_KEYS.USER);
|
||||
router.prefetch(PAGES.TWO_FACTOR_VERIFY);
|
||||
router.prefetch(PAGES.CREDENTIALS);
|
||||
router.prefetch(PAGES.GENERATE);
|
||||
const user: User = getData(LS_KEYS.USER);
|
||||
const keyAttributes: KeyAttributes = getData(
|
||||
LS_KEYS.KEY_ATTRIBUTES
|
||||
);
|
||||
if (!user?.email) {
|
||||
router.push('/');
|
||||
router.push(PAGES.ROOT);
|
||||
} else if (
|
||||
keyAttributes?.encryptedKey &&
|
||||
(user.token || user.encryptedToken)
|
||||
) {
|
||||
router.push(PAGES.CREDENTIALS);
|
||||
} else {
|
||||
setEmail(user.email);
|
||||
}
|
||||
|
@ -68,29 +79,38 @@ export default function Verify() {
|
|||
isTwoFactorEnabled: true,
|
||||
});
|
||||
setIsFirstLogin(true);
|
||||
router.push('/two-factor/verify');
|
||||
router.push(PAGES.TWO_FACTOR_VERIFY);
|
||||
} else {
|
||||
setData(LS_KEYS.USER, {
|
||||
...getData(LS_KEYS.USER),
|
||||
email,
|
||||
token,
|
||||
encryptedToken,
|
||||
id,
|
||||
isTwoFactorEnabled: false,
|
||||
});
|
||||
keyAttributes && setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes);
|
||||
if (keyAttributes) {
|
||||
setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes);
|
||||
setData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES, keyAttributes);
|
||||
} else if (getData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES)) {
|
||||
await putAttributes(
|
||||
token,
|
||||
getData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES)
|
||||
);
|
||||
}
|
||||
clearFiles();
|
||||
setIsFirstLogin(true);
|
||||
if (keyAttributes?.encryptedKey) {
|
||||
clearKeys();
|
||||
router.push('/credentials');
|
||||
router.push(PAGES.CREDENTIALS);
|
||||
} else {
|
||||
router.push('/generate');
|
||||
router.push(PAGES.GENERATE);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (e?.status === 401) {
|
||||
setFieldError('ott', constants.INVALID_CODE);
|
||||
} else if (e?.status === 410) {
|
||||
setFieldError('ott', constants.EXPIRED_CODE);
|
||||
} else {
|
||||
setFieldError('ott', `${constants.UNKNOWN_ERROR} ${e.message}`);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
import { getEndpoint } from 'utils/common/apiUtil';
|
||||
import { getStripePublishableKey, getToken } from 'utils/common/key';
|
||||
import { checkConnectivity, runningInBrowser } from 'utils/common/';
|
||||
import { getEndpoint, getPaymentsUrl } from 'utils/common/apiUtil';
|
||||
import { getToken } from 'utils/common/key';
|
||||
import { setData, LS_KEYS } from 'utils/storage/localStorage';
|
||||
import { convertToHumanReadable } from 'utils/billingUtil';
|
||||
import { loadStripe, Stripe } from '@stripe/stripe-js';
|
||||
import { CustomError } from 'utils/common/errorUtil';
|
||||
import HTTPService from './HTTPService';
|
||||
import { logError } from 'utils/sentry';
|
||||
import { getPaymentToken } from './userService';
|
||||
|
||||
const ENDPOINT = getEndpoint();
|
||||
|
||||
export enum PAYMENT_INTENT_STATUS {
|
||||
SUCCESS = 'success',
|
||||
REQUIRE_ACTION = 'requires_action',
|
||||
REQUIRE_PAYMENT_METHOD = 'requires_payment_method',
|
||||
enum PaymentActionType {
|
||||
Buy = 'buy',
|
||||
Update = 'update',
|
||||
}
|
||||
export interface Subscription {
|
||||
id: number;
|
||||
|
@ -39,31 +36,8 @@ export interface Plan {
|
|||
stripeID: string;
|
||||
}
|
||||
|
||||
export interface SubscriptionUpdateResponse {
|
||||
subscription: Subscription;
|
||||
status: PAYMENT_INTENT_STATUS;
|
||||
clientSecret: string;
|
||||
}
|
||||
export const FREE_PLAN = 'free';
|
||||
class billingService {
|
||||
private stripe: Stripe;
|
||||
|
||||
constructor() {
|
||||
try {
|
||||
const publishableKey = getStripePublishableKey();
|
||||
const main = async () => {
|
||||
try {
|
||||
this.stripe = await loadStripe(publishableKey);
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
}
|
||||
};
|
||||
runningInBrowser() && checkConnectivity() && main();
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public async getPlans(): Promise<Plan[]> {
|
||||
try {
|
||||
const response = await HTTPService.get(
|
||||
|
@ -92,60 +66,32 @@ class billingService {
|
|||
}
|
||||
}
|
||||
|
||||
public async buyPaidSubscription(productID) {
|
||||
public async buySubscription(productID: string) {
|
||||
try {
|
||||
const response = await this.createCheckoutSession(productID);
|
||||
await this.stripe.redirectToCheckout({
|
||||
sessionId: response.data.sessionID,
|
||||
});
|
||||
const paymentToken = await getPaymentToken();
|
||||
await this.redirectToPayments(
|
||||
paymentToken,
|
||||
productID,
|
||||
PaymentActionType.Buy
|
||||
);
|
||||
} catch (e) {
|
||||
logError(e, 'unable to buy subscription');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async updateSubscription(productID) {
|
||||
public async updateSubscription(productID: string) {
|
||||
try {
|
||||
const response = await HTTPService.post(
|
||||
`${ENDPOINT}/billing/stripe/update-subscription`,
|
||||
{
|
||||
const paymentToken = await getPaymentToken();
|
||||
await this.redirectToPayments(
|
||||
paymentToken,
|
||||
productID,
|
||||
},
|
||||
null,
|
||||
{
|
||||
'X-Auth-Token': getToken(),
|
||||
}
|
||||
PaymentActionType.Update
|
||||
);
|
||||
const { result } = response.data;
|
||||
switch (result.status) {
|
||||
case PAYMENT_INTENT_STATUS.SUCCESS:
|
||||
// subscription updated successfully
|
||||
// no-op required
|
||||
break;
|
||||
case PAYMENT_INTENT_STATUS.REQUIRE_PAYMENT_METHOD:
|
||||
throw new Error(
|
||||
PAYMENT_INTENT_STATUS.REQUIRE_PAYMENT_METHOD
|
||||
);
|
||||
case PAYMENT_INTENT_STATUS.REQUIRE_ACTION:
|
||||
{
|
||||
const { error } = await this.stripe.confirmCardPayment(
|
||||
result.clientSecret
|
||||
);
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
logError(e, 'subscription update failed');
|
||||
throw e;
|
||||
}
|
||||
try {
|
||||
await this.verifySubscription();
|
||||
} catch (e) {
|
||||
throw new Error(CustomError.SUBSCRIPTION_VERIFICATION_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public async cancelSubscription() {
|
||||
|
@ -161,7 +107,7 @@ class billingService {
|
|||
const { subscription } = response.data;
|
||||
setData(LS_KEYS.SUBSCRIPTION, subscription);
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
logError(e, 'subscription cancel failed');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
@ -179,23 +125,11 @@ class billingService {
|
|||
const { subscription } = response.data;
|
||||
setData(LS_KEYS.SUBSCRIPTION, subscription);
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
logError(e, 'failed to activate subscription');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private async createCheckoutSession(productID) {
|
||||
return HTTPService.get(
|
||||
`${ENDPOINT}/billing/stripe/checkout-session`,
|
||||
{
|
||||
productID,
|
||||
},
|
||||
{
|
||||
'X-Auth-Token': getToken(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public async verifySubscription(
|
||||
sessionID: string = null
|
||||
): Promise<Subscription> {
|
||||
|
@ -221,11 +155,26 @@ class billingService {
|
|||
}
|
||||
}
|
||||
|
||||
public async redirectToPayments(
|
||||
paymentToken: string,
|
||||
productID: string,
|
||||
action: string
|
||||
) {
|
||||
try {
|
||||
window.location.href = `${getPaymentsUrl()}?productID=${productID}&paymentToken=${paymentToken}&action=${action}&redirectURL=${
|
||||
window.location.origin
|
||||
}/gallery`;
|
||||
} catch (e) {
|
||||
logError(e, 'unable to get payments url');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async redirectToCustomerPortal() {
|
||||
try {
|
||||
const response = await HTTPService.get(
|
||||
`${ENDPOINT}/billing/stripe/customer-portal`,
|
||||
null,
|
||||
{ redirectURL: `${window.location.origin}/gallery` },
|
||||
{
|
||||
'X-Auth-Token': getToken(),
|
||||
}
|
||||
|
|
|
@ -113,16 +113,19 @@ const getCollections = async (
|
|||
collection,
|
||||
key
|
||||
);
|
||||
return collectionWithSecrets;
|
||||
} catch (e) {
|
||||
logError(
|
||||
e,
|
||||
`decryption failed for collection with id=${collection.id}`
|
||||
);
|
||||
logError(e, `decryption failed for collection`, {
|
||||
collectionID: collection.id,
|
||||
});
|
||||
}
|
||||
return collectionWithSecrets;
|
||||
}
|
||||
);
|
||||
return await Promise.all(promises);
|
||||
// only allow deleted or collection with key, filtering out collection whose decryption failed
|
||||
const collections = (await Promise.all(promises)).filter(
|
||||
(collection) => collection.isDeleted || collection.key
|
||||
);
|
||||
return collections;
|
||||
} catch (e) {
|
||||
logError(e, 'getCollections failed');
|
||||
throw e;
|
||||
|
@ -221,15 +224,20 @@ export const getFavItemIds = async (files: File[]): Promise<Set<number>> => {
|
|||
);
|
||||
};
|
||||
|
||||
export const createAlbum = async (albumName: string) =>
|
||||
createCollection(albumName, CollectionType.album);
|
||||
export const createAlbum = async (
|
||||
albumName: string,
|
||||
existingCollection?: Collection[]
|
||||
) => createCollection(albumName, CollectionType.album, existingCollection);
|
||||
|
||||
export const createCollection = async (
|
||||
collectionName: string,
|
||||
type: CollectionType
|
||||
type: CollectionType,
|
||||
existingCollections?: Collection[]
|
||||
): Promise<Collection> => {
|
||||
try {
|
||||
const existingCollections = await syncCollections();
|
||||
if (!existingCollections) {
|
||||
existingCollections = await syncCollections();
|
||||
}
|
||||
for (const collection of existingCollections) {
|
||||
if (collection.name === collectionName) {
|
||||
return collection;
|
||||
|
|
|
@ -1,21 +1,14 @@
|
|||
import { getToken } from 'utils/common/key';
|
||||
import { getFileUrl, getThumbnailUrl } from 'utils/common/apiUtil';
|
||||
import CryptoWorker from 'utils/crypto';
|
||||
import {
|
||||
fileIsHEIC,
|
||||
convertHEIC2JPEG,
|
||||
fileNameWithoutExtension,
|
||||
generateStreamFromArrayBuffer,
|
||||
} from 'utils/file';
|
||||
import { generateStreamFromArrayBuffer, convertForPreview } from 'utils/file';
|
||||
import HTTPService from './HTTPService';
|
||||
import { File, FILE_TYPE } from './fileService';
|
||||
import { logError } from 'utils/sentry';
|
||||
import { decodeMotionPhoto } from './motionPhotoService';
|
||||
|
||||
class DownloadManager {
|
||||
private fileDownloads = new Map<string, string>();
|
||||
|
||||
private thumbnailDownloads = new Map<number, string>();
|
||||
private fileObjectUrlPromise = new Map<string, Promise<string>>();
|
||||
private thumbnailObjectUrlPromise = new Map<number, Promise<string>>();
|
||||
|
||||
public async getPreview(file: File) {
|
||||
try {
|
||||
|
@ -23,21 +16,33 @@ class DownloadManager {
|
|||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
const cache = await caches.open('thumbs');
|
||||
const cacheResp: Response = await cache.match(file.id.toString());
|
||||
const thumbnailCache = await caches.open('thumbs');
|
||||
const cacheResp: Response = await thumbnailCache.match(
|
||||
file.id.toString()
|
||||
);
|
||||
if (cacheResp) {
|
||||
return URL.createObjectURL(await cacheResp.blob());
|
||||
}
|
||||
if (!this.thumbnailDownloads.get(file.id)) {
|
||||
const download = await this.downloadThumb(token, cache, file);
|
||||
this.thumbnailDownloads.set(file.id, download);
|
||||
if (!this.thumbnailObjectUrlPromise.get(file.id)) {
|
||||
const downloadPromise = this._downloadThumb(
|
||||
token,
|
||||
thumbnailCache,
|
||||
file
|
||||
);
|
||||
this.thumbnailObjectUrlPromise.set(file.id, downloadPromise);
|
||||
}
|
||||
return await this.thumbnailDownloads.get(file.id);
|
||||
return await this.thumbnailObjectUrlPromise.get(file.id);
|
||||
} catch (e) {
|
||||
this.thumbnailObjectUrlPromise.delete(file.id);
|
||||
logError(e, 'get preview Failed');
|
||||
}
|
||||
}
|
||||
downloadThumb = async (token: string, cache: Cache, file: File) => {
|
||||
|
||||
_downloadThumb = async (
|
||||
token: string,
|
||||
thumbnailCache: Cache,
|
||||
file: File
|
||||
) => {
|
||||
const resp = await HTTPService.get(
|
||||
getThumbnailUrl(file.id),
|
||||
null,
|
||||
|
@ -51,7 +56,7 @@ class DownloadManager {
|
|||
file.key
|
||||
);
|
||||
try {
|
||||
await cache.put(
|
||||
await thumbnailCache.put(
|
||||
file.id.toString(),
|
||||
new Response(new Blob([decrypted]))
|
||||
);
|
||||
|
@ -63,31 +68,23 @@ class DownloadManager {
|
|||
|
||||
getFile = async (file: File, forPreview = false) => {
|
||||
try {
|
||||
if (!this.fileDownloads.get(`${file.id}_${forPreview}`)) {
|
||||
// unzip motion photo and return fileBlob of the image for preview
|
||||
const getFilePromise = (async () => {
|
||||
const fileStream = await this.downloadFile(file);
|
||||
let fileBlob = await new Response(fileStream).blob();
|
||||
if (forPreview) {
|
||||
if (file.metadata.fileType === FILE_TYPE.LIVE_PHOTO) {
|
||||
const originalName = fileNameWithoutExtension(
|
||||
file.metadata.title
|
||||
);
|
||||
const motionPhoto = await decodeMotionPhoto(
|
||||
fileBlob,
|
||||
originalName
|
||||
);
|
||||
fileBlob = new Blob([motionPhoto.image]);
|
||||
fileBlob = await convertForPreview(file, fileBlob);
|
||||
}
|
||||
if (fileIsHEIC(file.metadata.title)) {
|
||||
fileBlob = await convertHEIC2JPEG(fileBlob);
|
||||
}
|
||||
}
|
||||
this.fileDownloads.set(
|
||||
return URL.createObjectURL(fileBlob);
|
||||
})();
|
||||
if (!this.fileObjectUrlPromise.get(`${file.id}_${forPreview}`)) {
|
||||
this.fileObjectUrlPromise.set(
|
||||
`${file.id}_${forPreview}`,
|
||||
URL.createObjectURL(fileBlob)
|
||||
getFilePromise
|
||||
);
|
||||
}
|
||||
return this.fileDownloads.get(`${file.id}_${forPreview}`);
|
||||
return await this.fileObjectUrlPromise.get(
|
||||
`${file.id}_${forPreview}`
|
||||
);
|
||||
} catch (e) {
|
||||
logError(e, 'Failed to get File');
|
||||
}
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
import { retryAsyncFunction, runningInBrowser } from 'utils/common';
|
||||
import { runningInBrowser } from 'utils/common';
|
||||
import {
|
||||
getExportPendingFiles,
|
||||
getExportFailedFiles,
|
||||
getFilesUploadedAfterLastExport,
|
||||
getExportRecordFileUID,
|
||||
dedupe,
|
||||
getGoogleLikeMetadataFile,
|
||||
getExportRecordFileUID,
|
||||
} from 'utils/export';
|
||||
import {
|
||||
fileNameWithoutExtension,
|
||||
generateStreamFromArrayBuffer,
|
||||
} from 'utils/file';
|
||||
import { retryAsyncFunction } from 'utils/network';
|
||||
import { logError } from 'utils/sentry';
|
||||
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||
import {
|
||||
|
@ -21,6 +18,10 @@ import {
|
|||
import downloadManager from './downloadManager';
|
||||
import { File, FILE_TYPE, getLocalFiles } from './fileService';
|
||||
import { decodeMotionPhoto } from './motionPhotoService';
|
||||
import {
|
||||
fileNameWithoutExtension,
|
||||
generateStreamFromArrayBuffer,
|
||||
} from 'utils/file';
|
||||
|
||||
export interface ExportProgress {
|
||||
current: number;
|
||||
|
@ -226,7 +227,7 @@ class ExportService {
|
|||
}
|
||||
return { paused: false };
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
logError(e, 'export failed ');
|
||||
}
|
||||
}
|
||||
async addFilesQueuedRecord(folder: string, files: File[]) {
|
||||
|
|
82
src/services/ffmpegService.ts
Normal file
82
src/services/ffmpegService.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
import { createFFmpeg, FFmpeg } from '@ffmpeg/ffmpeg';
|
||||
import { CustomError } from 'utils/common/errorUtil';
|
||||
import { logError } from 'utils/sentry';
|
||||
import QueueProcessor from './upload/queueProcessor';
|
||||
import { getUint8ArrayView } from './upload/readFileService';
|
||||
|
||||
class FFmpegService {
|
||||
private ffmpeg: FFmpeg = null;
|
||||
private isLoading = null;
|
||||
|
||||
private generateThumbnailProcessor = new QueueProcessor<Uint8Array>(1);
|
||||
async init() {
|
||||
try {
|
||||
this.ffmpeg = createFFmpeg({
|
||||
corePath: '/js/ffmpeg/ffmpeg-core.js',
|
||||
});
|
||||
this.isLoading = this.ffmpeg.load();
|
||||
await this.isLoading;
|
||||
this.isLoading = null;
|
||||
} catch (e) {
|
||||
logError(e, 'ffmpeg load failed');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async generateThumbnail(file: File) {
|
||||
if (!this.ffmpeg) {
|
||||
await this.init();
|
||||
}
|
||||
if (this.isLoading) {
|
||||
await this.isLoading;
|
||||
}
|
||||
const response = this.generateThumbnailProcessor.queueUpRequest(
|
||||
generateThumbnailHelper.bind(null, this.ffmpeg, file)
|
||||
);
|
||||
|
||||
const thumbnail = await response.promise;
|
||||
if (!thumbnail) {
|
||||
throw Error(CustomError.THUMBNAIL_GENERATION_FAILED);
|
||||
}
|
||||
return thumbnail;
|
||||
}
|
||||
}
|
||||
|
||||
async function generateThumbnailHelper(ffmpeg: FFmpeg, file: File) {
|
||||
try {
|
||||
const inputFileName = `${Date.now().toString}-${file.name}`;
|
||||
const thumbFileName = `${Date.now().toString}-thumb.png`;
|
||||
ffmpeg.FS(
|
||||
'writeFile',
|
||||
inputFileName,
|
||||
await getUint8ArrayView(new FileReader(), file)
|
||||
);
|
||||
let seekTime = 1.0;
|
||||
let thumb = null;
|
||||
while (seekTime > 0) {
|
||||
try {
|
||||
await ffmpeg.run(
|
||||
'-i',
|
||||
inputFileName,
|
||||
'-ss',
|
||||
`00:00:0${seekTime.toFixed(3)}`,
|
||||
'-vframes',
|
||||
'1',
|
||||
thumbFileName
|
||||
);
|
||||
thumb = ffmpeg.FS('readFile', thumbFileName);
|
||||
ffmpeg.FS('unlink', thumbFileName);
|
||||
break;
|
||||
} catch (e) {
|
||||
seekTime = Number((seekTime / 10).toFixed(3));
|
||||
}
|
||||
}
|
||||
ffmpeg.FS('unlink', inputFileName);
|
||||
return thumb;
|
||||
} catch (e) {
|
||||
logError(e, 'ffmpeg thumbnail generation failed');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export default new FFmpegService();
|
|
@ -2,7 +2,7 @@ import { getEndpoint } from 'utils/common/apiUtil';
|
|||
import localForage from 'utils/storage/localForage';
|
||||
|
||||
import { getToken } from 'utils/common/key';
|
||||
import { DataStream, MetadataObject } from './uploadService';
|
||||
import { DataStream, MetadataObject } from './upload/uploadService';
|
||||
import { Collection } from './collectionService';
|
||||
import HTTPService from './HTTPService';
|
||||
import { logError } from 'utils/sentry';
|
||||
|
@ -26,6 +26,17 @@ export enum FILE_TYPE {
|
|||
OTHERS,
|
||||
}
|
||||
|
||||
/* Build error occurred
|
||||
ReferenceError: Cannot access 'FILE_TYPE' before initialization
|
||||
when it was placed in readFileService
|
||||
*/
|
||||
// list of format that were missed by type-detection for some files.
|
||||
export const FORMAT_MISSED_BY_FILE_TYPE_LIB = [
|
||||
{ fileType: FILE_TYPE.IMAGE, exactType: 'jpeg' },
|
||||
{ fileType: FILE_TYPE.IMAGE, exactType: 'jpg' },
|
||||
{ fileType: FILE_TYPE.VIDEO, exactType: 'webm' },
|
||||
];
|
||||
|
||||
export interface File {
|
||||
id: number;
|
||||
collectionID: number;
|
||||
|
|
40
src/services/upload/encryptionService.ts
Normal file
40
src/services/upload/encryptionService.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { DataStream, EncryptionResult, isDataStream } from './uploadService';
|
||||
|
||||
async function encryptFileStream(worker, fileData: DataStream) {
|
||||
const { stream, chunkCount } = fileData;
|
||||
const fileStreamReader = stream.getReader();
|
||||
const { key, decryptionHeader, pushState } =
|
||||
await worker.initChunkEncryption();
|
||||
const ref = { pullCount: 1 };
|
||||
const encryptedFileStream = new ReadableStream({
|
||||
async pull(controller) {
|
||||
const { value } = await fileStreamReader.read();
|
||||
const encryptedFileChunk = await worker.encryptFileChunk(
|
||||
value,
|
||||
pushState,
|
||||
ref.pullCount === chunkCount
|
||||
);
|
||||
controller.enqueue(encryptedFileChunk);
|
||||
if (ref.pullCount === chunkCount) {
|
||||
controller.close();
|
||||
}
|
||||
ref.pullCount++;
|
||||
},
|
||||
});
|
||||
return {
|
||||
key,
|
||||
file: {
|
||||
decryptionHeader,
|
||||
encryptedData: { stream: encryptedFileStream, chunkCount },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function encryptFiledata(
|
||||
worker,
|
||||
filedata: Uint8Array | DataStream
|
||||
): Promise<EncryptionResult> {
|
||||
return isDataStream(filedata)
|
||||
? await encryptFileStream(worker, filedata)
|
||||
: await worker.encryptFile(filedata);
|
||||
}
|
90
src/services/upload/exifService.ts
Normal file
90
src/services/upload/exifService.ts
Normal file
|
@ -0,0 +1,90 @@
|
|||
import EXIF from 'exif-js';
|
||||
|
||||
import { logError } from 'utils/sentry';
|
||||
import { NULL_LOCATION, Location } from './metadataService';
|
||||
|
||||
const SOUTH_DIRECTION = 'S';
|
||||
const WEST_DIRECTION = 'W';
|
||||
const CHUNK_SIZE_FOR_EXIF_READING = 4 * 1024 * 1024;
|
||||
interface ParsedEXIFData {
|
||||
location: Location;
|
||||
creationTime: number;
|
||||
}
|
||||
|
||||
export async function getExifData(
|
||||
worker,
|
||||
receivedFile: globalThis.File
|
||||
): Promise<ParsedEXIFData> {
|
||||
try {
|
||||
const fileChunk = await worker.getUint8ArrayView(
|
||||
receivedFile.slice(0, CHUNK_SIZE_FOR_EXIF_READING)
|
||||
);
|
||||
const exifData = EXIF.readFromBinaryFile(fileChunk.buffer);
|
||||
if (!exifData) {
|
||||
return { location: NULL_LOCATION, creationTime: null };
|
||||
}
|
||||
return {
|
||||
location: getEXIFLocation(exifData),
|
||||
creationTime: getUNIXTime(exifData),
|
||||
};
|
||||
} catch (e) {
|
||||
logError(e, 'error reading exif data');
|
||||
// ignore exif parsing errors
|
||||
}
|
||||
}
|
||||
|
||||
function getUNIXTime(exifData: any) {
|
||||
const dateString: string = exifData.DateTimeOriginal || exifData.DateTime;
|
||||
if (!dateString || dateString === '0000:00:00 00:00:00') {
|
||||
return null;
|
||||
}
|
||||
const parts = dateString.split(' ')[0].split(':');
|
||||
const date = new Date(
|
||||
Number(parts[0]),
|
||||
Number(parts[1]) - 1,
|
||||
Number(parts[2])
|
||||
);
|
||||
return date.getTime() * 1000;
|
||||
}
|
||||
|
||||
function getEXIFLocation(exifData): Location {
|
||||
if (!exifData.GPSLatitude) {
|
||||
return NULL_LOCATION;
|
||||
}
|
||||
|
||||
const latDegree = exifData.GPSLatitude[0];
|
||||
const latMinute = exifData.GPSLatitude[1];
|
||||
const latSecond = exifData.GPSLatitude[2];
|
||||
|
||||
const lonDegree = exifData.GPSLongitude[0];
|
||||
const lonMinute = exifData.GPSLongitude[1];
|
||||
const lonSecond = exifData.GPSLongitude[2];
|
||||
|
||||
const latDirection = exifData.GPSLatitudeRef;
|
||||
const lonDirection = exifData.GPSLongitudeRef;
|
||||
|
||||
const latFinal = convertDMSToDD(
|
||||
latDegree,
|
||||
latMinute,
|
||||
latSecond,
|
||||
latDirection
|
||||
);
|
||||
|
||||
const lonFinal = convertDMSToDD(
|
||||
lonDegree,
|
||||
lonMinute,
|
||||
lonSecond,
|
||||
lonDirection
|
||||
);
|
||||
return { latitude: latFinal * 1.0, longitude: lonFinal * 1.0 };
|
||||
}
|
||||
|
||||
function convertDMSToDD(degrees, minutes, seconds, direction) {
|
||||
let dd = degrees + minutes / 60 + seconds / 3600;
|
||||
|
||||
if (direction === SOUTH_DIRECTION || direction === WEST_DIRECTION) {
|
||||
dd = dd * -1;
|
||||
}
|
||||
|
||||
return dd;
|
||||
}
|
117
src/services/upload/metadataService.ts
Normal file
117
src/services/upload/metadataService.ts
Normal file
|
@ -0,0 +1,117 @@
|
|||
import { logError } from 'utils/sentry';
|
||||
import { getExifData } from './exifService';
|
||||
import { FileTypeInfo } from './readFileService';
|
||||
import { MetadataObject } from './uploadService';
|
||||
|
||||
export interface Location {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
}
|
||||
|
||||
export interface ParsedMetaDataJSON {
|
||||
creationTime: number;
|
||||
modificationTime: number;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
}
|
||||
interface ParsedMetaDataJSONWithTitle {
|
||||
title: string;
|
||||
parsedMetaDataJSON: ParsedMetaDataJSON;
|
||||
}
|
||||
|
||||
export const NULL_LOCATION: Location = { latitude: null, longitude: null };
|
||||
|
||||
const NULL_PARSED_METADATA_JSON: ParsedMetaDataJSON = {
|
||||
creationTime: null,
|
||||
modificationTime: null,
|
||||
...NULL_LOCATION,
|
||||
};
|
||||
|
||||
export async function extractMetadata(
|
||||
worker,
|
||||
receivedFile: globalThis.File,
|
||||
fileTypeInfo: FileTypeInfo
|
||||
) {
|
||||
const { location, creationTime } = await getExifData(worker, receivedFile);
|
||||
|
||||
const extractedMetadata: MetadataObject = {
|
||||
title: receivedFile.name,
|
||||
creationTime: creationTime || receivedFile.lastModified * 1000,
|
||||
modificationTime: receivedFile.lastModified * 1000,
|
||||
latitude: location?.latitude,
|
||||
longitude: location?.longitude,
|
||||
fileType: fileTypeInfo.fileType,
|
||||
};
|
||||
return extractedMetadata;
|
||||
}
|
||||
|
||||
export const getMetadataMapKey = (collectionID: number, title: string) =>
|
||||
`${collectionID}_${title}`;
|
||||
|
||||
export async function parseMetadataJSON(receivedFile: globalThis.File) {
|
||||
try {
|
||||
const metadataJSON: object = await new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onabort = () => reject(Error('file reading was aborted'));
|
||||
reader.onerror = () => reject(Error('file reading has failed'));
|
||||
reader.onload = () => {
|
||||
const result =
|
||||
typeof reader.result !== 'string'
|
||||
? new TextDecoder().decode(reader.result)
|
||||
: reader.result;
|
||||
resolve(JSON.parse(result));
|
||||
};
|
||||
reader.readAsText(receivedFile);
|
||||
});
|
||||
|
||||
const parsedMetaDataJSON: ParsedMetaDataJSON =
|
||||
NULL_PARSED_METADATA_JSON;
|
||||
if (!metadataJSON || !metadataJSON['title']) {
|
||||
return;
|
||||
}
|
||||
|
||||
const title = metadataJSON['title'];
|
||||
if (
|
||||
metadataJSON['photoTakenTime'] &&
|
||||
metadataJSON['photoTakenTime']['timestamp']
|
||||
) {
|
||||
parsedMetaDataJSON.creationTime =
|
||||
metadataJSON['photoTakenTime']['timestamp'] * 1000000;
|
||||
} else if (
|
||||
metadataJSON['creationTime'] &&
|
||||
metadataJSON['creationTime']['timestamp']
|
||||
) {
|
||||
parsedMetaDataJSON.creationTime =
|
||||
metadataJSON['creationTime']['timestamp'] * 1000000;
|
||||
}
|
||||
if (
|
||||
metadataJSON['modificationTime'] &&
|
||||
metadataJSON['modificationTime']['timestamp']
|
||||
) {
|
||||
parsedMetaDataJSON.modificationTime =
|
||||
metadataJSON['modificationTime']['timestamp'] * 1000000;
|
||||
}
|
||||
let locationData: Location = NULL_LOCATION;
|
||||
if (
|
||||
metadataJSON['geoData'] &&
|
||||
(metadataJSON['geoData']['latitude'] !== 0.0 ||
|
||||
metadataJSON['geoData']['longitude'] !== 0.0)
|
||||
) {
|
||||
locationData = metadataJSON['geoData'];
|
||||
} else if (
|
||||
metadataJSON['geoDataExif'] &&
|
||||
(metadataJSON['geoDataExif']['latitude'] !== 0.0 ||
|
||||
metadataJSON['geoDataExif']['longitude'] !== 0.0)
|
||||
) {
|
||||
locationData = metadataJSON['geoDataExif'];
|
||||
}
|
||||
if (locationData !== null) {
|
||||
parsedMetaDataJSON.latitude = locationData.latitude;
|
||||
parsedMetaDataJSON.longitude = locationData.longitude;
|
||||
}
|
||||
return { title, parsedMetaDataJSON } as ParsedMetaDataJSONWithTitle;
|
||||
} catch (e) {
|
||||
logError(e, 'parseMetadataJSON failed');
|
||||
// ignore
|
||||
}
|
||||
}
|
112
src/services/upload/multiPartUploadService.ts
Normal file
112
src/services/upload/multiPartUploadService.ts
Normal file
|
@ -0,0 +1,112 @@
|
|||
import {
|
||||
FILE_CHUNKS_COMBINED_FOR_A_UPLOAD_PART,
|
||||
DataStream,
|
||||
} from './uploadService';
|
||||
import UploadHttpClient from './uploadHttpClient';
|
||||
import * as convert from 'xml-js';
|
||||
import UIService, { RANDOM_PERCENTAGE_PROGRESS_FOR_PUT } from './uiService';
|
||||
import { CustomError } from 'utils/common/errorUtil';
|
||||
|
||||
interface PartEtag {
|
||||
PartNumber: number;
|
||||
ETag: string;
|
||||
}
|
||||
|
||||
export interface MultipartUploadURLs {
|
||||
objectKey: string;
|
||||
partURLs: string[];
|
||||
completeURL: string;
|
||||
}
|
||||
|
||||
function calculatePartCount(chunkCount: number) {
|
||||
const partCount = Math.ceil(
|
||||
chunkCount / FILE_CHUNKS_COMBINED_FOR_A_UPLOAD_PART
|
||||
);
|
||||
return partCount;
|
||||
}
|
||||
export async function uploadStreamUsingMultipart(
|
||||
filename: string,
|
||||
dataStream: DataStream
|
||||
) {
|
||||
const uploadPartCount = calculatePartCount(dataStream.chunkCount);
|
||||
const multipartUploadURLs = await UploadHttpClient.fetchMultipartUploadURLs(
|
||||
uploadPartCount
|
||||
);
|
||||
const fileObjectKey = await uploadStreamInParts(
|
||||
multipartUploadURLs,
|
||||
dataStream.stream,
|
||||
filename,
|
||||
uploadPartCount
|
||||
);
|
||||
return fileObjectKey;
|
||||
}
|
||||
|
||||
export async function uploadStreamInParts(
|
||||
multipartUploadURLs: MultipartUploadURLs,
|
||||
dataStream: ReadableStream<Uint8Array>,
|
||||
filename: string,
|
||||
uploadPartCount: number
|
||||
) {
|
||||
const streamReader = dataStream.getReader();
|
||||
const percentPerPart = getRandomProgressPerPartUpload(uploadPartCount);
|
||||
|
||||
const partEtags: PartEtag[] = [];
|
||||
for (const [
|
||||
index,
|
||||
fileUploadURL,
|
||||
] of multipartUploadURLs.partURLs.entries()) {
|
||||
const uploadChunk = await combineChunksToFormUploadPart(streamReader);
|
||||
const progressTracker = UIService.trackUploadProgress(
|
||||
filename,
|
||||
percentPerPart,
|
||||
index
|
||||
);
|
||||
|
||||
const eTag = await UploadHttpClient.putFilePart(
|
||||
fileUploadURL,
|
||||
uploadChunk,
|
||||
progressTracker
|
||||
);
|
||||
partEtags.push({ PartNumber: index + 1, ETag: eTag });
|
||||
}
|
||||
const { done } = await streamReader.read();
|
||||
if (!done) {
|
||||
throw Error(CustomError.CHUNK_MORE_THAN_EXPECTED);
|
||||
}
|
||||
await completeMultipartUpload(partEtags, multipartUploadURLs.completeURL);
|
||||
return multipartUploadURLs.objectKey;
|
||||
}
|
||||
|
||||
function getRandomProgressPerPartUpload(uploadPartCount: number) {
|
||||
const percentPerPart =
|
||||
RANDOM_PERCENTAGE_PROGRESS_FOR_PUT() / uploadPartCount;
|
||||
return percentPerPart;
|
||||
}
|
||||
|
||||
async function combineChunksToFormUploadPart(
|
||||
streamReader: ReadableStreamDefaultReader<Uint8Array>
|
||||
) {
|
||||
const combinedChunks = [];
|
||||
for (let i = 0; i < FILE_CHUNKS_COMBINED_FOR_A_UPLOAD_PART; i++) {
|
||||
const { done, value: chunk } = await streamReader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
for (let index = 0; index < chunk.length; index++) {
|
||||
combinedChunks.push(chunk[index]);
|
||||
}
|
||||
}
|
||||
return Uint8Array.from(combinedChunks);
|
||||
}
|
||||
|
||||
async function completeMultipartUpload(
|
||||
partEtags: PartEtag[],
|
||||
completeURL: string
|
||||
) {
|
||||
const options = { compact: true, ignoreComment: true, spaces: 4 };
|
||||
const body = convert.js2xml(
|
||||
{ CompleteMultipartUpload: { Part: partEtags } },
|
||||
options
|
||||
);
|
||||
await UploadHttpClient.completeMultipartUpload(completeURL, body);
|
||||
}
|
66
src/services/upload/queueProcessor.ts
Normal file
66
src/services/upload/queueProcessor.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
interface RequestQueueItem {
|
||||
request: (canceller?: RequestCanceller) => Promise<any>;
|
||||
callback: (response) => void;
|
||||
isCanceled: { status: boolean };
|
||||
canceller: { exec: () => void };
|
||||
}
|
||||
|
||||
interface RequestCanceller {
|
||||
exec: () => void;
|
||||
}
|
||||
|
||||
export default class QueueProcessor<T> {
|
||||
private requestQueue: RequestQueueItem[] = [];
|
||||
|
||||
private requestInProcessing = 0;
|
||||
|
||||
constructor(private maxParallelProcesses: number) {}
|
||||
|
||||
public queueUpRequest(request: () => Promise<T>) {
|
||||
const isCanceled = { status: false };
|
||||
const canceller: RequestCanceller = {
|
||||
exec: () => {
|
||||
isCanceled.status = true;
|
||||
},
|
||||
};
|
||||
|
||||
const promise = new Promise<T>((resolve) => {
|
||||
this.requestQueue.push({
|
||||
request,
|
||||
callback: resolve,
|
||||
isCanceled,
|
||||
canceller,
|
||||
});
|
||||
this.pollQueue();
|
||||
});
|
||||
|
||||
return { promise, canceller };
|
||||
}
|
||||
|
||||
async pollQueue() {
|
||||
if (this.requestInProcessing < this.maxParallelProcesses) {
|
||||
this.requestInProcessing++;
|
||||
await this.processQueue();
|
||||
this.requestInProcessing--;
|
||||
}
|
||||
}
|
||||
|
||||
public async processQueue() {
|
||||
while (this.requestQueue.length > 0) {
|
||||
const queueItem = this.requestQueue.pop();
|
||||
let response: string;
|
||||
|
||||
if (queueItem.isCanceled.status) {
|
||||
response = null;
|
||||
} else {
|
||||
try {
|
||||
response = await queueItem.request(queueItem.canceller);
|
||||
} catch (e) {
|
||||
response = null;
|
||||
}
|
||||
}
|
||||
queueItem.callback(response);
|
||||
await this.processQueue();
|
||||
}
|
||||
}
|
||||
}
|
153
src/services/upload/readFileService.ts
Normal file
153
src/services/upload/readFileService.ts
Normal file
|
@ -0,0 +1,153 @@
|
|||
import {
|
||||
FILE_TYPE,
|
||||
FORMAT_MISSED_BY_FILE_TYPE_LIB,
|
||||
} from 'services/fileService';
|
||||
import { logError } from 'utils/sentry';
|
||||
import { FILE_READER_CHUNK_SIZE, MULTIPART_PART_SIZE } from './uploadService';
|
||||
import FileType from 'file-type/browser';
|
||||
import { CustomError } from 'utils/common/errorUtil';
|
||||
|
||||
const TYPE_VIDEO = 'video';
|
||||
const TYPE_IMAGE = 'image';
|
||||
const EDITED_FILE_SUFFIX = '-edited';
|
||||
const CHUNK_SIZE_FOR_TYPE_DETECTION = 4100;
|
||||
|
||||
export async function getFileData(worker, file: globalThis.File) {
|
||||
if (file.size > MULTIPART_PART_SIZE) {
|
||||
return getFileStream(worker, file, FILE_READER_CHUNK_SIZE);
|
||||
} else {
|
||||
return await worker.getUint8ArrayView(file);
|
||||
}
|
||||
}
|
||||
|
||||
export interface FileTypeInfo {
|
||||
fileType: FILE_TYPE;
|
||||
exactType: string;
|
||||
}
|
||||
|
||||
export async function getFileType(
|
||||
worker,
|
||||
receivedFile: globalThis.File
|
||||
): Promise<FileTypeInfo> {
|
||||
try {
|
||||
let fileType: FILE_TYPE;
|
||||
const mimeType = await getMimeType(worker, receivedFile);
|
||||
const typeParts = mimeType?.split('/');
|
||||
if (typeParts?.length !== 2) {
|
||||
throw Error(CustomError.TYPE_DETECTION_FAILED);
|
||||
}
|
||||
switch (typeParts[0]) {
|
||||
case TYPE_IMAGE:
|
||||
fileType = FILE_TYPE.IMAGE;
|
||||
break;
|
||||
case TYPE_VIDEO:
|
||||
fileType = FILE_TYPE.VIDEO;
|
||||
break;
|
||||
default:
|
||||
fileType = FILE_TYPE.OTHERS;
|
||||
}
|
||||
return { fileType, exactType: typeParts[1] };
|
||||
} catch (e) {
|
||||
const fileFormat = receivedFile.name.split('.').pop();
|
||||
const formatMissedByTypeDetection = FORMAT_MISSED_BY_FILE_TYPE_LIB.find(
|
||||
(a) => a.exactType === fileFormat
|
||||
);
|
||||
if (formatMissedByTypeDetection) {
|
||||
return formatMissedByTypeDetection;
|
||||
}
|
||||
logError(e, CustomError.TYPE_DETECTION_FAILED, {
|
||||
fileFormat,
|
||||
});
|
||||
return { fileType: FILE_TYPE.OTHERS, exactType: null };
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Get the original file name for edited file to associate it to original file's metadataJSON file
|
||||
as edited file doesn't have their own metadata file
|
||||
*/
|
||||
export function getFileOriginalName(file: globalThis.File) {
|
||||
let originalName: string = null;
|
||||
|
||||
const isEditedFile = file.name.endsWith(EDITED_FILE_SUFFIX);
|
||||
if (isEditedFile) {
|
||||
originalName = file.name.slice(0, -1 * EDITED_FILE_SUFFIX.length);
|
||||
} else {
|
||||
originalName = file.name;
|
||||
}
|
||||
return originalName;
|
||||
}
|
||||
|
||||
async function getMimeType(worker, file: globalThis.File) {
|
||||
const fileChunkBlob = file.slice(0, CHUNK_SIZE_FOR_TYPE_DETECTION);
|
||||
return getMimeTypeFromBlob(worker, fileChunkBlob);
|
||||
}
|
||||
|
||||
export async function getMimeTypeFromBlob(worker, fileBlob: Blob) {
|
||||
try {
|
||||
const initialFiledata = await worker.getUint8ArrayView(fileBlob);
|
||||
const result = await FileType.fromBuffer(initialFiledata);
|
||||
return result.mime;
|
||||
} catch (e) {
|
||||
throw Error(CustomError.TYPE_DETECTION_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
function getFileStream(worker, file: globalThis.File, chunkSize: number) {
|
||||
const fileChunkReader = fileChunkReaderMaker(worker, file, chunkSize);
|
||||
|
||||
const stream = new ReadableStream<Uint8Array>({
|
||||
async pull(controller: ReadableStreamDefaultController) {
|
||||
const chunk = await fileChunkReader.next();
|
||||
if (chunk.done) {
|
||||
controller.close();
|
||||
} else {
|
||||
controller.enqueue(chunk.value);
|
||||
}
|
||||
},
|
||||
});
|
||||
const chunkCount = Math.ceil(file.size / chunkSize);
|
||||
return {
|
||||
stream,
|
||||
chunkCount,
|
||||
};
|
||||
}
|
||||
|
||||
async function* fileChunkReaderMaker(
|
||||
worker,
|
||||
file: globalThis.File,
|
||||
chunkSize: number
|
||||
) {
|
||||
let offset = 0;
|
||||
while (offset < file.size) {
|
||||
const blob = file.slice(offset, chunkSize + offset);
|
||||
const fileChunk = await worker.getUint8ArrayView(blob);
|
||||
yield fileChunk;
|
||||
offset += chunkSize;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function getUint8ArrayView(
|
||||
reader: FileReader,
|
||||
file: Blob
|
||||
): Promise<Uint8Array> {
|
||||
try {
|
||||
return await new Promise((resolve, reject) => {
|
||||
reader.onabort = () => reject(Error('file reading was aborted'));
|
||||
reader.onerror = () => reject(Error('file reading has failed'));
|
||||
reader.onload = () => {
|
||||
// Do whatever you want with the file contents
|
||||
const result =
|
||||
typeof reader.result === 'string'
|
||||
? new TextEncoder().encode(reader.result)
|
||||
: new Uint8Array(reader.result);
|
||||
resolve(result);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
} catch (e) {
|
||||
logError(e, 'error reading file to byte-array');
|
||||
throw e;
|
||||
}
|
||||
}
|
188
src/services/upload/thumbnailService.ts
Normal file
188
src/services/upload/thumbnailService.ts
Normal file
|
@ -0,0 +1,188 @@
|
|||
import { FILE_TYPE } from 'services/fileService';
|
||||
import { CustomError, errorWithContext } from 'utils/common/errorUtil';
|
||||
import { convertHEIC2JPEG } from 'utils/file';
|
||||
import { logError } from 'utils/sentry';
|
||||
import { BLACK_THUMBNAIL_BASE64 } from '../../../public/images/black-thumbnail-b64';
|
||||
import FFmpegService from 'services/ffmpegService';
|
||||
|
||||
const THUMBNAIL_HEIGHT = 720;
|
||||
const MAX_ATTEMPTS = 3;
|
||||
const MIN_THUMBNAIL_SIZE = 50000;
|
||||
|
||||
const WAIT_TIME_THUMBNAIL_GENERATION = 10 * 1000;
|
||||
|
||||
export async function generateThumbnail(
|
||||
worker,
|
||||
file: globalThis.File,
|
||||
fileType: FILE_TYPE,
|
||||
isHEIC: boolean
|
||||
): Promise<{ thumbnail: Uint8Array; hasStaticThumbnail: boolean }> {
|
||||
try {
|
||||
let hasStaticThumbnail = false;
|
||||
let canvas = document.createElement('canvas');
|
||||
let thumbnail: Uint8Array;
|
||||
try {
|
||||
if (fileType === FILE_TYPE.IMAGE) {
|
||||
canvas = await generateImageThumbnail(file, isHEIC);
|
||||
} else {
|
||||
try {
|
||||
const thumb = await FFmpegService.generateThumbnail(file);
|
||||
return { thumbnail: thumb, hasStaticThumbnail: false };
|
||||
} catch (e) {
|
||||
canvas = await generateVideoThumbnail(file);
|
||||
}
|
||||
}
|
||||
const thumbnailBlob = await thumbnailCanvasToBlob(canvas);
|
||||
thumbnail = await worker.getUint8ArrayView(thumbnailBlob);
|
||||
if (thumbnail.length === 0) {
|
||||
throw Error('EMPTY THUMBNAIL');
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e, 'uploading static thumbnail');
|
||||
thumbnail = Uint8Array.from(atob(BLACK_THUMBNAIL_BASE64), (c) =>
|
||||
c.charCodeAt(0)
|
||||
);
|
||||
hasStaticThumbnail = true;
|
||||
}
|
||||
return { thumbnail, hasStaticThumbnail };
|
||||
} catch (e) {
|
||||
logError(e, 'Error generating static thumbnail');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateImageThumbnail(
|
||||
file: globalThis.File,
|
||||
isHEIC: boolean
|
||||
) {
|
||||
const canvas = document.createElement('canvas');
|
||||
const canvasCTX = canvas.getContext('2d');
|
||||
|
||||
let imageURL = null;
|
||||
let timeout = null;
|
||||
|
||||
if (isHEIC) {
|
||||
file = new globalThis.File([await convertHEIC2JPEG(file)], null, null);
|
||||
}
|
||||
let image = new Image();
|
||||
imageURL = URL.createObjectURL(file);
|
||||
image.setAttribute('src', imageURL);
|
||||
await new Promise((resolve, reject) => {
|
||||
image.onload = () => {
|
||||
try {
|
||||
const thumbnailWidth =
|
||||
(image.width * THUMBNAIL_HEIGHT) / image.height;
|
||||
canvas.width = thumbnailWidth;
|
||||
canvas.height = THUMBNAIL_HEIGHT;
|
||||
canvasCTX.drawImage(
|
||||
image,
|
||||
0,
|
||||
0,
|
||||
thumbnailWidth,
|
||||
THUMBNAIL_HEIGHT
|
||||
);
|
||||
image = null;
|
||||
clearTimeout(timeout);
|
||||
resolve(null);
|
||||
} catch (e) {
|
||||
const err = errorWithContext(
|
||||
e,
|
||||
`${CustomError.THUMBNAIL_GENERATION_FAILED} err: ${e}`
|
||||
);
|
||||
reject(err);
|
||||
}
|
||||
};
|
||||
timeout = setTimeout(
|
||||
() =>
|
||||
reject(
|
||||
Error(
|
||||
`wait time exceeded for format ${
|
||||
file.name.split('.').slice(-1)[0]
|
||||
}`
|
||||
)
|
||||
),
|
||||
WAIT_TIME_THUMBNAIL_GENERATION
|
||||
);
|
||||
});
|
||||
return canvas;
|
||||
}
|
||||
|
||||
export async function generateVideoThumbnail(file: globalThis.File) {
|
||||
const canvas = document.createElement('canvas');
|
||||
const canvasCTX = canvas.getContext('2d');
|
||||
|
||||
let videoURL = null;
|
||||
let timeout = null;
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
let video = document.createElement('video');
|
||||
videoURL = URL.createObjectURL(file);
|
||||
video.addEventListener('loadeddata', function () {
|
||||
try {
|
||||
if (!video) {
|
||||
throw Error('video load failed');
|
||||
}
|
||||
const thumbnailWidth =
|
||||
(video.videoWidth * THUMBNAIL_HEIGHT) / video.videoHeight;
|
||||
canvas.width = thumbnailWidth;
|
||||
canvas.height = THUMBNAIL_HEIGHT;
|
||||
canvasCTX.drawImage(
|
||||
video,
|
||||
0,
|
||||
0,
|
||||
thumbnailWidth,
|
||||
THUMBNAIL_HEIGHT
|
||||
);
|
||||
video = null;
|
||||
clearTimeout(timeout);
|
||||
resolve(null);
|
||||
} catch (e) {
|
||||
const err = Error(
|
||||
`${CustomError.THUMBNAIL_GENERATION_FAILED} err: ${e}`
|
||||
);
|
||||
logError(e, CustomError.THUMBNAIL_GENERATION_FAILED);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
video.preload = 'metadata';
|
||||
video.src = videoURL;
|
||||
timeout = setTimeout(
|
||||
() =>
|
||||
reject(
|
||||
Error(
|
||||
`wait time exceeded for format ${
|
||||
file.name.split('.').slice(-1)[0]
|
||||
}`
|
||||
)
|
||||
),
|
||||
WAIT_TIME_THUMBNAIL_GENERATION
|
||||
);
|
||||
});
|
||||
return canvas;
|
||||
}
|
||||
|
||||
async function thumbnailCanvasToBlob(canvas: HTMLCanvasElement) {
|
||||
let thumbnailBlob = null;
|
||||
let attempts = 0;
|
||||
let quality = 1;
|
||||
|
||||
do {
|
||||
thumbnailBlob = await new Promise((resolve) => {
|
||||
canvas.toBlob(
|
||||
function (blob) {
|
||||
resolve(blob);
|
||||
},
|
||||
'image/jpeg',
|
||||
quality
|
||||
);
|
||||
});
|
||||
thumbnailBlob = thumbnailBlob ?? new Blob([]);
|
||||
attempts++;
|
||||
quality /= 2;
|
||||
} while (
|
||||
thumbnailBlob.size > MIN_THUMBNAIL_SIZE &&
|
||||
attempts <= MAX_ATTEMPTS
|
||||
);
|
||||
|
||||
return thumbnailBlob;
|
||||
}
|
121
src/services/upload/uiService.ts
Normal file
121
src/services/upload/uiService.ts
Normal file
|
@ -0,0 +1,121 @@
|
|||
import { ProgressUpdater } from 'components/pages/gallery/Upload';
|
||||
import { UPLOAD_STAGES } from './uploadManager';
|
||||
|
||||
export const RANDOM_PERCENTAGE_PROGRESS_FOR_PUT = () => 90 + 10 * Math.random();
|
||||
|
||||
class UIService {
|
||||
private perFileProgress: number;
|
||||
private filesUploaded: number;
|
||||
private totalFileCount: number;
|
||||
private fileProgress: Map<string, number>;
|
||||
private uploadResult: Map<string, number>;
|
||||
private progressUpdater: ProgressUpdater;
|
||||
|
||||
init(progressUpdater: ProgressUpdater) {
|
||||
this.progressUpdater = progressUpdater;
|
||||
}
|
||||
|
||||
reset(count: number) {
|
||||
this.setTotalFileCount(count);
|
||||
this.filesUploaded = 0;
|
||||
this.fileProgress = new Map<string, number>();
|
||||
this.uploadResult = new Map<string, number>();
|
||||
this.updateProgressBarUI();
|
||||
}
|
||||
|
||||
setTotalFileCount(count: number) {
|
||||
this.totalFileCount = count;
|
||||
this.perFileProgress = 100 / this.totalFileCount;
|
||||
}
|
||||
|
||||
setFileProgress(filename: string, progress: number) {
|
||||
this.fileProgress.set(filename, progress);
|
||||
this.updateProgressBarUI();
|
||||
}
|
||||
|
||||
setUploadStage(stage: UPLOAD_STAGES) {
|
||||
this.progressUpdater.setUploadStage(stage);
|
||||
}
|
||||
|
||||
setPercentComplete(percent: number) {
|
||||
this.progressUpdater.setPercentComplete(percent);
|
||||
}
|
||||
|
||||
increaseFileUploaded() {
|
||||
this.filesUploaded++;
|
||||
this.updateProgressBarUI();
|
||||
}
|
||||
|
||||
moveFileToResultList(filename: string) {
|
||||
this.uploadResult.set(filename, this.fileProgress.get(filename));
|
||||
this.fileProgress.delete(filename);
|
||||
this.updateProgressBarUI();
|
||||
}
|
||||
|
||||
updateProgressBarUI() {
|
||||
const {
|
||||
setPercentComplete,
|
||||
setFileCounter,
|
||||
setFileProgress,
|
||||
setUploadResult,
|
||||
} = this.progressUpdater;
|
||||
setFileCounter({
|
||||
finished: this.filesUploaded,
|
||||
total: this.totalFileCount,
|
||||
});
|
||||
let percentComplete = this.perFileProgress * this.uploadResult.size;
|
||||
if (this.fileProgress) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
for (const [_, progress] of this.fileProgress) {
|
||||
// filter negative indicator values during percentComplete calculation
|
||||
if (progress < 0) {
|
||||
continue;
|
||||
}
|
||||
percentComplete += (this.perFileProgress * progress) / 100;
|
||||
}
|
||||
}
|
||||
setPercentComplete(percentComplete);
|
||||
setFileProgress(this.fileProgress);
|
||||
setUploadResult(this.uploadResult);
|
||||
}
|
||||
|
||||
trackUploadProgress(
|
||||
filename: string,
|
||||
percentPerPart = RANDOM_PERCENTAGE_PROGRESS_FOR_PUT(),
|
||||
index = 0
|
||||
) {
|
||||
const cancel = { exec: null };
|
||||
let timeout = null;
|
||||
const resetTimeout = () => {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
timeout = setTimeout(() => cancel.exec(), 30 * 1000);
|
||||
};
|
||||
return {
|
||||
cancel,
|
||||
onUploadProgress: (event) => {
|
||||
filename &&
|
||||
this.fileProgress.set(
|
||||
filename,
|
||||
Math.min(
|
||||
Math.round(
|
||||
percentPerPart * index +
|
||||
(percentPerPart * event.loaded) /
|
||||
event.total
|
||||
),
|
||||
98
|
||||
)
|
||||
);
|
||||
this.updateProgressBarUI();
|
||||
if (event.loaded === event.total) {
|
||||
clearTimeout(timeout);
|
||||
} else {
|
||||
resetTimeout();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default new UIService();
|
152
src/services/upload/uploadHttpClient.ts
Normal file
152
src/services/upload/uploadHttpClient.ts
Normal file
|
@ -0,0 +1,152 @@
|
|||
import HTTPService from 'services/HTTPService';
|
||||
import { getEndpoint } from 'utils/common/apiUtil';
|
||||
import { getToken } from 'utils/common/key';
|
||||
import { logError } from 'utils/sentry';
|
||||
import { UploadFile, UploadURL } from './uploadService';
|
||||
import { File } from '../fileService';
|
||||
import { CustomError, handleUploadError } from 'utils/common/errorUtil';
|
||||
import { retryAsyncFunction } from 'utils/network';
|
||||
import { MultipartUploadURLs } from './multiPartUploadService';
|
||||
|
||||
const ENDPOINT = getEndpoint();
|
||||
const MAX_URL_REQUESTS = 50;
|
||||
|
||||
class UploadHttpClient {
|
||||
private uploadURLFetchInProgress = null;
|
||||
|
||||
async uploadFile(uploadFile: UploadFile): Promise<File> {
|
||||
try {
|
||||
const token = getToken();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
const response = await retryAsyncFunction(
|
||||
() =>
|
||||
HTTPService.post(`${ENDPOINT}/files`, uploadFile, null, {
|
||||
'X-Auth-Token': token,
|
||||
}),
|
||||
handleUploadError
|
||||
);
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
logError(e, 'upload Files Failed');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchUploadURLs(count: number, urlStore: UploadURL[]): Promise<void> {
|
||||
try {
|
||||
if (!this.uploadURLFetchInProgress) {
|
||||
try {
|
||||
const token = getToken();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
this.uploadURLFetchInProgress = HTTPService.get(
|
||||
`${ENDPOINT}/files/upload-urls`,
|
||||
{
|
||||
count: Math.min(MAX_URL_REQUESTS, count * 2),
|
||||
},
|
||||
{ 'X-Auth-Token': token }
|
||||
);
|
||||
const response = await this.uploadURLFetchInProgress;
|
||||
urlStore.push(...response.data['urls']);
|
||||
} finally {
|
||||
this.uploadURLFetchInProgress = null;
|
||||
}
|
||||
}
|
||||
return this.uploadURLFetchInProgress;
|
||||
} catch (e) {
|
||||
logError(e, 'fetch upload-url failed ');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchMultipartUploadURLs(
|
||||
count: number
|
||||
): Promise<MultipartUploadURLs> {
|
||||
try {
|
||||
const token = getToken();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
const response = await HTTPService.get(
|
||||
`${ENDPOINT}/files/multipart-upload-urls`,
|
||||
{
|
||||
count,
|
||||
},
|
||||
{ 'X-Auth-Token': token }
|
||||
);
|
||||
|
||||
return response.data['urls'];
|
||||
} catch (e) {
|
||||
logError(e, 'fetch multipart-upload-url failed');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async putFile(
|
||||
fileUploadURL: UploadURL,
|
||||
file: Uint8Array,
|
||||
progressTracker
|
||||
): Promise<string> {
|
||||
try {
|
||||
await retryAsyncFunction(() =>
|
||||
HTTPService.put(
|
||||
fileUploadURL.url,
|
||||
file,
|
||||
null,
|
||||
null,
|
||||
progressTracker
|
||||
)
|
||||
);
|
||||
return fileUploadURL.objectKey;
|
||||
} catch (e) {
|
||||
logError(e, 'putFile to dataStore failed ');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async putFilePart(
|
||||
partUploadURL: string,
|
||||
filePart: Uint8Array,
|
||||
progressTracker
|
||||
) {
|
||||
try {
|
||||
const response = await retryAsyncFunction(async () => {
|
||||
const resp = await HTTPService.put(
|
||||
partUploadURL,
|
||||
filePart,
|
||||
null,
|
||||
null,
|
||||
progressTracker
|
||||
);
|
||||
if (!resp?.headers?.etag) {
|
||||
const err = Error(CustomError.ETAG_MISSING);
|
||||
logError(err, 'putFile in parts failed');
|
||||
throw err;
|
||||
}
|
||||
return resp;
|
||||
});
|
||||
return response.headers.etag as string;
|
||||
} catch (e) {
|
||||
logError(e, 'put filePart failed');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async completeMultipartUpload(completeURL: string, reqBody: any) {
|
||||
try {
|
||||
await retryAsyncFunction(() =>
|
||||
HTTPService.post(completeURL, reqBody, null, {
|
||||
'content-type': 'text/xml',
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
logError(e, 'put file in parts failed');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new UploadHttpClient();
|
216
src/services/upload/uploadManager.ts
Normal file
216
src/services/upload/uploadManager.ts
Normal file
|
@ -0,0 +1,216 @@
|
|||
import { File, getLocalFiles } from '../fileService';
|
||||
import { Collection, getLocalCollections } from '../collectionService';
|
||||
import { SetFiles } from 'pages/gallery';
|
||||
import { ComlinkWorker, getDedicatedCryptoWorker } from 'utils/crypto';
|
||||
import {
|
||||
sortFilesIntoCollections,
|
||||
sortFiles,
|
||||
removeUnnecessaryFileProps,
|
||||
} from 'utils/file';
|
||||
import { logError } from 'utils/sentry';
|
||||
import localForage from 'utils/storage/localForage';
|
||||
import {
|
||||
getMetadataMapKey,
|
||||
ParsedMetaDataJSON,
|
||||
parseMetadataJSON,
|
||||
} from './metadataService';
|
||||
import { segregateFiles } from 'utils/upload';
|
||||
import { ProgressUpdater } from 'components/pages/gallery/Upload';
|
||||
import uploader from './uploader';
|
||||
import UIService from './uiService';
|
||||
import UploadService from './uploadService';
|
||||
import { CustomError } from 'utils/common/errorUtil';
|
||||
|
||||
const MAX_CONCURRENT_UPLOADS = 4;
|
||||
const FILE_UPLOAD_COMPLETED = 100;
|
||||
|
||||
export enum FileUploadResults {
|
||||
FAILED = -1,
|
||||
SKIPPED = -2,
|
||||
UNSUPPORTED = -3,
|
||||
BLOCKED = -4,
|
||||
TOO_LARGE = -5,
|
||||
UPLOADED = 100,
|
||||
}
|
||||
|
||||
export interface FileWithCollection {
|
||||
file: globalThis.File;
|
||||
collectionID?: number;
|
||||
collection?: Collection;
|
||||
}
|
||||
|
||||
export enum UPLOAD_STAGES {
|
||||
START,
|
||||
READING_GOOGLE_METADATA_FILES,
|
||||
UPLOADING,
|
||||
FINISH,
|
||||
}
|
||||
|
||||
export type MetadataMap = Map<string, ParsedMetaDataJSON>;
|
||||
|
||||
class UploadManager {
|
||||
private cryptoWorkers = new Array<ComlinkWorker>(MAX_CONCURRENT_UPLOADS);
|
||||
private metadataMap: MetadataMap;
|
||||
private filesToBeUploaded: FileWithCollection[];
|
||||
private failedFiles: FileWithCollection[];
|
||||
private existingFilesCollectionWise: Map<number, File[]>;
|
||||
private existingFiles: File[];
|
||||
private setFiles: SetFiles;
|
||||
private collections: Map<number, Collection>;
|
||||
public initUploader(progressUpdater: ProgressUpdater, setFiles: SetFiles) {
|
||||
UIService.init(progressUpdater);
|
||||
this.setFiles = setFiles;
|
||||
}
|
||||
|
||||
private async init(newCollections?: Collection[]) {
|
||||
this.filesToBeUploaded = [];
|
||||
this.failedFiles = [];
|
||||
this.metadataMap = new Map<string, ParsedMetaDataJSON>();
|
||||
this.existingFiles = await getLocalFiles();
|
||||
this.existingFilesCollectionWise = sortFilesIntoCollections(
|
||||
this.existingFiles
|
||||
);
|
||||
const collections = await getLocalCollections();
|
||||
if (newCollections) {
|
||||
collections.push(...newCollections);
|
||||
}
|
||||
this.collections = new Map(
|
||||
collections.map((collection) => [collection.id, collection])
|
||||
);
|
||||
}
|
||||
|
||||
public async queueFilesForUpload(
|
||||
fileWithCollectionToBeUploaded: FileWithCollection[],
|
||||
newCreatedCollections?: Collection[]
|
||||
) {
|
||||
try {
|
||||
await this.init(newCreatedCollections);
|
||||
const { metadataFiles, mediaFiles } = segregateFiles(
|
||||
fileWithCollectionToBeUploaded
|
||||
);
|
||||
if (metadataFiles.length) {
|
||||
UIService.setUploadStage(
|
||||
UPLOAD_STAGES.READING_GOOGLE_METADATA_FILES
|
||||
);
|
||||
await this.seedMetadataMap(metadataFiles);
|
||||
}
|
||||
if (mediaFiles.length) {
|
||||
UIService.setUploadStage(UPLOAD_STAGES.START);
|
||||
await this.uploadMediaFiles(mediaFiles);
|
||||
}
|
||||
UIService.setUploadStage(UPLOAD_STAGES.FINISH);
|
||||
UIService.setPercentComplete(FILE_UPLOAD_COMPLETED);
|
||||
} catch (e) {
|
||||
logError(e, 'uploading failed with error');
|
||||
throw e;
|
||||
} finally {
|
||||
for (let i = 0; i < MAX_CONCURRENT_UPLOADS; i++) {
|
||||
this.cryptoWorkers[i]?.worker.terminate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async seedMetadataMap(metadataFiles: FileWithCollection[]) {
|
||||
try {
|
||||
UIService.reset(metadataFiles.length);
|
||||
|
||||
for (const fileWithCollection of metadataFiles) {
|
||||
const parsedMetaDataJSONWithTitle = await parseMetadataJSON(
|
||||
fileWithCollection.file
|
||||
);
|
||||
if (parsedMetaDataJSONWithTitle) {
|
||||
const { title, parsedMetaDataJSON } =
|
||||
parsedMetaDataJSONWithTitle;
|
||||
this.metadataMap.set(
|
||||
getMetadataMapKey(
|
||||
fileWithCollection.collectionID,
|
||||
title
|
||||
),
|
||||
parsedMetaDataJSON
|
||||
);
|
||||
UIService.increaseFileUploaded();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e, 'error seeding MetadataMap');
|
||||
// silently ignore the error
|
||||
}
|
||||
}
|
||||
|
||||
private async uploadMediaFiles(mediaFiles: FileWithCollection[]) {
|
||||
this.filesToBeUploaded.push(...mediaFiles);
|
||||
UIService.reset(mediaFiles.length);
|
||||
|
||||
await UploadService.init(mediaFiles.length, this.metadataMap);
|
||||
|
||||
UIService.setUploadStage(UPLOAD_STAGES.UPLOADING);
|
||||
|
||||
const uploadProcesses = [];
|
||||
for (
|
||||
let i = 0;
|
||||
i < MAX_CONCURRENT_UPLOADS && this.filesToBeUploaded.length > 0;
|
||||
i++
|
||||
) {
|
||||
const cryptoWorker = getDedicatedCryptoWorker();
|
||||
if (!cryptoWorker) {
|
||||
throw Error(CustomError.FAILED_TO_LOAD_WEB_WORKER);
|
||||
}
|
||||
this.cryptoWorkers[i] = cryptoWorker;
|
||||
uploadProcesses.push(
|
||||
this.uploadNextFileInQueue(
|
||||
await new this.cryptoWorkers[i].comlink()
|
||||
)
|
||||
);
|
||||
}
|
||||
await Promise.all(uploadProcesses);
|
||||
}
|
||||
|
||||
private async uploadNextFileInQueue(worker: any) {
|
||||
while (this.filesToBeUploaded.length > 0) {
|
||||
const fileWithCollection = this.filesToBeUploaded.pop();
|
||||
const existingFilesInCollection =
|
||||
this.existingFilesCollectionWise.get(
|
||||
fileWithCollection.collectionID
|
||||
) ?? [];
|
||||
const collection = this.collections.get(
|
||||
fileWithCollection.collectionID
|
||||
);
|
||||
fileWithCollection.collection = collection;
|
||||
const { fileUploadResult, file } = await uploader(
|
||||
worker,
|
||||
existingFilesInCollection,
|
||||
fileWithCollection
|
||||
);
|
||||
|
||||
if (fileUploadResult === FileUploadResults.UPLOADED) {
|
||||
this.existingFiles.push(file);
|
||||
this.existingFiles = sortFiles(this.existingFiles);
|
||||
await localForage.setItem(
|
||||
'files',
|
||||
removeUnnecessaryFileProps(this.existingFiles)
|
||||
);
|
||||
this.setFiles(this.existingFiles);
|
||||
if (!this.existingFilesCollectionWise.has(file.collectionID)) {
|
||||
this.existingFilesCollectionWise.set(file.collectionID, []);
|
||||
}
|
||||
this.existingFilesCollectionWise
|
||||
.get(file.collectionID)
|
||||
.push(file);
|
||||
}
|
||||
if (
|
||||
fileUploadResult === FileUploadResults.BLOCKED ||
|
||||
fileUploadResult === FileUploadResults.FAILED
|
||||
) {
|
||||
this.failedFiles.push(fileWithCollection);
|
||||
}
|
||||
|
||||
UIService.moveFileToResultList(fileWithCollection.file.name);
|
||||
}
|
||||
}
|
||||
|
||||
async retryFailedFiles() {
|
||||
await this.queueFilesForUpload(this.failedFiles);
|
||||
}
|
||||
}
|
||||
|
||||
export default new UploadManager();
|
274
src/services/upload/uploadService.ts
Normal file
274
src/services/upload/uploadService.ts
Normal file
|
@ -0,0 +1,274 @@
|
|||
import { fileAttribute, FILE_TYPE } from '../fileService';
|
||||
import { Collection } from '../collectionService';
|
||||
import { logError } from 'utils/sentry';
|
||||
import UploadHttpClient from './uploadHttpClient';
|
||||
import {
|
||||
extractMetadata,
|
||||
getMetadataMapKey,
|
||||
ParsedMetaDataJSON,
|
||||
} from './metadataService';
|
||||
import { generateThumbnail } from './thumbnailService';
|
||||
import {
|
||||
getFileOriginalName,
|
||||
getFileData,
|
||||
FileTypeInfo,
|
||||
} from './readFileService';
|
||||
import { encryptFiledata } from './encryptionService';
|
||||
import { ENCRYPTION_CHUNK_SIZE } from 'types';
|
||||
import { uploadStreamUsingMultipart } from './multiPartUploadService';
|
||||
import UIService from './uiService';
|
||||
import { handleUploadError } from 'utils/common/errorUtil';
|
||||
import { MetadataMap } from './uploadManager';
|
||||
import { fileIsHEIC } from 'utils/file';
|
||||
|
||||
// this is the chunk size of the un-encrypted file which is read and encrypted before uploading it as a single part.
|
||||
export const MULTIPART_PART_SIZE = 20 * 1024 * 1024;
|
||||
|
||||
export const FILE_READER_CHUNK_SIZE = ENCRYPTION_CHUNK_SIZE;
|
||||
|
||||
export const FILE_CHUNKS_COMBINED_FOR_A_UPLOAD_PART = Math.floor(
|
||||
MULTIPART_PART_SIZE / FILE_READER_CHUNK_SIZE
|
||||
);
|
||||
|
||||
export interface UploadURL {
|
||||
url: string;
|
||||
objectKey: string;
|
||||
}
|
||||
|
||||
export interface DataStream {
|
||||
stream: ReadableStream<Uint8Array>;
|
||||
chunkCount: number;
|
||||
}
|
||||
|
||||
export function isDataStream(object: any): object is DataStream {
|
||||
return 'stream' in object;
|
||||
}
|
||||
export interface EncryptionResult {
|
||||
file: fileAttribute;
|
||||
key: string;
|
||||
}
|
||||
export interface B64EncryptionResult {
|
||||
encryptedData: string;
|
||||
key: string;
|
||||
nonce: string;
|
||||
}
|
||||
|
||||
export interface MetadataObject {
|
||||
title: string;
|
||||
creationTime: number;
|
||||
modificationTime: number;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
fileType: FILE_TYPE;
|
||||
hasStaticThumbnail?: boolean;
|
||||
}
|
||||
|
||||
export interface FileInMemory {
|
||||
filedata: Uint8Array | DataStream;
|
||||
thumbnail: Uint8Array;
|
||||
hasStaticThumbnail: boolean;
|
||||
}
|
||||
|
||||
export interface FileWithMetadata
|
||||
extends Omit<FileInMemory, 'hasStaticThumbnail'> {
|
||||
metadata: MetadataObject;
|
||||
}
|
||||
|
||||
export interface EncryptedFile {
|
||||
file: ProcessedFile;
|
||||
fileKey: B64EncryptionResult;
|
||||
}
|
||||
export interface ProcessedFile {
|
||||
file: fileAttribute;
|
||||
thumbnail: fileAttribute;
|
||||
metadata: fileAttribute;
|
||||
filename: string;
|
||||
}
|
||||
export interface BackupedFile extends Omit<ProcessedFile, 'filename'> {}
|
||||
|
||||
export interface UploadFile extends BackupedFile {
|
||||
collectionID: number;
|
||||
encryptedKey: string;
|
||||
keyDecryptionNonce: string;
|
||||
}
|
||||
|
||||
class UploadService {
|
||||
private uploadURLs: UploadURL[] = [];
|
||||
private metadataMap: Map<string, ParsedMetaDataJSON>;
|
||||
private pendingUploadCount: number = 0;
|
||||
|
||||
async init(fileCount: number, metadataMap: MetadataMap) {
|
||||
this.pendingUploadCount = fileCount;
|
||||
this.metadataMap = metadataMap;
|
||||
await this.preFetchUploadURLs();
|
||||
}
|
||||
|
||||
async readFile(
|
||||
worker: any,
|
||||
rawFile: globalThis.File,
|
||||
fileTypeInfo: FileTypeInfo
|
||||
): Promise<FileInMemory> {
|
||||
const isHEIC = fileIsHEIC(fileTypeInfo.exactType);
|
||||
|
||||
const { thumbnail, hasStaticThumbnail } = await generateThumbnail(
|
||||
worker,
|
||||
rawFile,
|
||||
fileTypeInfo.fileType,
|
||||
isHEIC
|
||||
);
|
||||
|
||||
const filedata = await getFileData(worker, rawFile);
|
||||
|
||||
return {
|
||||
filedata,
|
||||
thumbnail,
|
||||
hasStaticThumbnail,
|
||||
};
|
||||
}
|
||||
|
||||
async getFileMetadata(
|
||||
worker: any,
|
||||
rawFile: File,
|
||||
collection: Collection,
|
||||
fileTypeInfo: FileTypeInfo
|
||||
): Promise<MetadataObject> {
|
||||
const originalName = getFileOriginalName(rawFile);
|
||||
const googleMetadata =
|
||||
this.metadataMap.get(
|
||||
getMetadataMapKey(collection.id, originalName)
|
||||
) ?? {};
|
||||
const extractedMetadata: MetadataObject = await extractMetadata(
|
||||
worker,
|
||||
rawFile,
|
||||
fileTypeInfo
|
||||
);
|
||||
|
||||
for (const [key, value] of Object.entries(googleMetadata)) {
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
extractedMetadata[key] = value;
|
||||
}
|
||||
return extractedMetadata;
|
||||
}
|
||||
|
||||
async encryptFile(
|
||||
worker: any,
|
||||
file: FileWithMetadata,
|
||||
encryptionKey: string
|
||||
): Promise<EncryptedFile> {
|
||||
try {
|
||||
const { key: fileKey, file: encryptedFiledata } =
|
||||
await encryptFiledata(worker, file.filedata);
|
||||
|
||||
const { file: encryptedThumbnail }: EncryptionResult =
|
||||
await worker.encryptThumbnail(file.thumbnail, fileKey);
|
||||
const { file: encryptedMetadata }: EncryptionResult =
|
||||
await worker.encryptMetadata(file.metadata, fileKey);
|
||||
|
||||
const encryptedKey: B64EncryptionResult = await worker.encryptToB64(
|
||||
fileKey,
|
||||
encryptionKey
|
||||
);
|
||||
|
||||
const result: EncryptedFile = {
|
||||
file: {
|
||||
file: encryptedFiledata,
|
||||
thumbnail: encryptedThumbnail,
|
||||
metadata: encryptedMetadata,
|
||||
filename: file.metadata.title,
|
||||
},
|
||||
fileKey: encryptedKey,
|
||||
};
|
||||
return result;
|
||||
} catch (e) {
|
||||
logError(e, 'Error encrypting files');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async uploadToBucket(file: ProcessedFile): Promise<BackupedFile> {
|
||||
try {
|
||||
let fileObjectKey: string = null;
|
||||
if (isDataStream(file.file.encryptedData)) {
|
||||
fileObjectKey = await uploadStreamUsingMultipart(
|
||||
file.filename,
|
||||
file.file.encryptedData
|
||||
);
|
||||
} else {
|
||||
const progressTracker = UIService.trackUploadProgress(
|
||||
file.filename
|
||||
);
|
||||
const fileUploadURL = await this.getUploadURL();
|
||||
fileObjectKey = await UploadHttpClient.putFile(
|
||||
fileUploadURL,
|
||||
file.file.encryptedData,
|
||||
progressTracker
|
||||
);
|
||||
}
|
||||
const thumbnailUploadURL = await this.getUploadURL();
|
||||
const thumbnailObjectKey = await UploadHttpClient.putFile(
|
||||
thumbnailUploadURL,
|
||||
file.thumbnail.encryptedData as Uint8Array,
|
||||
null
|
||||
);
|
||||
|
||||
const backupedFile: BackupedFile = {
|
||||
file: {
|
||||
decryptionHeader: file.file.decryptionHeader,
|
||||
objectKey: fileObjectKey,
|
||||
},
|
||||
thumbnail: {
|
||||
decryptionHeader: file.thumbnail.decryptionHeader,
|
||||
objectKey: thumbnailObjectKey,
|
||||
},
|
||||
metadata: file.metadata,
|
||||
};
|
||||
return backupedFile;
|
||||
} catch (e) {
|
||||
logError(e, 'error uploading to bucket');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
getUploadFile(
|
||||
collection: Collection,
|
||||
backupedFile: BackupedFile,
|
||||
fileKey: B64EncryptionResult
|
||||
): UploadFile {
|
||||
const uploadFile: UploadFile = {
|
||||
collectionID: collection.id,
|
||||
encryptedKey: fileKey.encryptedData,
|
||||
keyDecryptionNonce: fileKey.nonce,
|
||||
...backupedFile,
|
||||
};
|
||||
uploadFile;
|
||||
return uploadFile;
|
||||
}
|
||||
|
||||
private async getUploadURL() {
|
||||
if (this.uploadURLs.length === 0 && this.pendingUploadCount) {
|
||||
await this.fetchUploadURLs();
|
||||
}
|
||||
return this.uploadURLs.pop();
|
||||
}
|
||||
|
||||
public async preFetchUploadURLs() {
|
||||
try {
|
||||
await this.fetchUploadURLs();
|
||||
// checking for any subscription related errors
|
||||
} catch (e) {
|
||||
logError(e, 'prefetch uploadURL failed');
|
||||
handleUploadError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchUploadURLs() {
|
||||
await UploadHttpClient.fetchUploadURLs(
|
||||
this.pendingUploadCount,
|
||||
this.uploadURLs
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default new UploadService();
|
142
src/services/upload/uploader.ts
Normal file
142
src/services/upload/uploader.ts
Normal file
|
@ -0,0 +1,142 @@
|
|||
import { File, FILE_TYPE } from 'services/fileService';
|
||||
import { sleep } from 'utils/common';
|
||||
import { handleUploadError, CustomError } from 'utils/common/errorUtil';
|
||||
import { decryptFile } from 'utils/file';
|
||||
import { logError } from 'utils/sentry';
|
||||
import { fileAlreadyInCollection } from 'utils/upload';
|
||||
import UploadHttpClient from './uploadHttpClient';
|
||||
import UIService from './uiService';
|
||||
import { FileUploadResults, FileWithCollection } from './uploadManager';
|
||||
import UploadService, {
|
||||
BackupedFile,
|
||||
EncryptedFile,
|
||||
FileInMemory,
|
||||
FileWithMetadata,
|
||||
MetadataObject,
|
||||
UploadFile,
|
||||
} from './uploadService';
|
||||
import uploadService from './uploadService';
|
||||
import { FileTypeInfo, getFileType } from './readFileService';
|
||||
|
||||
const TwoSecondInMillSeconds = 2000;
|
||||
const FIVE_GB_IN_BYTES = 5 * 1024 * 1024 * 1024;
|
||||
interface UploadResponse {
|
||||
fileUploadResult: FileUploadResults;
|
||||
file?: File;
|
||||
}
|
||||
export default async function uploader(
|
||||
worker: any,
|
||||
existingFilesInCollection: File[],
|
||||
fileWithCollection: FileWithCollection
|
||||
): Promise<UploadResponse> {
|
||||
const { file: rawFile, collection } = fileWithCollection;
|
||||
|
||||
UIService.setFileProgress(rawFile.name, 0);
|
||||
|
||||
let file: FileInMemory = null;
|
||||
let encryptedFile: EncryptedFile = null;
|
||||
let metadata: MetadataObject = null;
|
||||
let fileTypeInfo: FileTypeInfo = null;
|
||||
let fileWithMetadata: FileWithMetadata = null;
|
||||
|
||||
try {
|
||||
if (rawFile.size >= FIVE_GB_IN_BYTES) {
|
||||
UIService.setFileProgress(
|
||||
rawFile.name,
|
||||
FileUploadResults.TOO_LARGE
|
||||
);
|
||||
// wait two second before removing the file from the progress in file section
|
||||
await sleep(TwoSecondInMillSeconds);
|
||||
return { fileUploadResult: FileUploadResults.TOO_LARGE };
|
||||
}
|
||||
fileTypeInfo = await getFileType(worker, rawFile);
|
||||
if (fileTypeInfo.fileType === FILE_TYPE.OTHERS) {
|
||||
throw Error(CustomError.UNSUPPORTED_FILE_FORMAT);
|
||||
}
|
||||
metadata = await uploadService.getFileMetadata(
|
||||
worker,
|
||||
rawFile,
|
||||
collection,
|
||||
fileTypeInfo
|
||||
);
|
||||
|
||||
if (fileAlreadyInCollection(existingFilesInCollection, metadata)) {
|
||||
UIService.setFileProgress(rawFile.name, FileUploadResults.SKIPPED);
|
||||
// wait two second before removing the file from the progress in file section
|
||||
await sleep(TwoSecondInMillSeconds);
|
||||
return { fileUploadResult: FileUploadResults.SKIPPED };
|
||||
}
|
||||
|
||||
file = await UploadService.readFile(worker, rawFile, fileTypeInfo);
|
||||
if (file.hasStaticThumbnail) {
|
||||
metadata.hasStaticThumbnail = true;
|
||||
}
|
||||
fileWithMetadata = {
|
||||
filedata: file.filedata,
|
||||
thumbnail: file.thumbnail,
|
||||
metadata,
|
||||
};
|
||||
|
||||
encryptedFile = await UploadService.encryptFile(
|
||||
worker,
|
||||
fileWithMetadata,
|
||||
collection.key
|
||||
);
|
||||
|
||||
const backupedFile: BackupedFile = await UploadService.uploadToBucket(
|
||||
encryptedFile.file
|
||||
);
|
||||
|
||||
const uploadFile: UploadFile = UploadService.getUploadFile(
|
||||
collection,
|
||||
backupedFile,
|
||||
encryptedFile.fileKey
|
||||
);
|
||||
|
||||
const uploadedFile = await UploadHttpClient.uploadFile(uploadFile);
|
||||
const decryptedFile = await decryptFile(uploadedFile, collection);
|
||||
|
||||
UIService.setFileProgress(rawFile.name, FileUploadResults.UPLOADED);
|
||||
UIService.increaseFileUploaded();
|
||||
return {
|
||||
fileUploadResult: FileUploadResults.UPLOADED,
|
||||
file: decryptedFile,
|
||||
};
|
||||
} catch (e) {
|
||||
const fileFormat =
|
||||
fileTypeInfo.exactType ?? rawFile.name.split('.').pop();
|
||||
logError(e, 'file upload failed', { fileFormat });
|
||||
const error = handleUploadError(e);
|
||||
switch (error.message) {
|
||||
case CustomError.ETAG_MISSING:
|
||||
UIService.setFileProgress(
|
||||
rawFile.name,
|
||||
FileUploadResults.BLOCKED
|
||||
);
|
||||
return { fileUploadResult: FileUploadResults.BLOCKED };
|
||||
case CustomError.UNSUPPORTED_FILE_FORMAT:
|
||||
UIService.setFileProgress(
|
||||
rawFile.name,
|
||||
FileUploadResults.UNSUPPORTED
|
||||
);
|
||||
return { fileUploadResult: FileUploadResults.UNSUPPORTED };
|
||||
|
||||
case CustomError.FILE_TOO_LARGE:
|
||||
UIService.setFileProgress(
|
||||
rawFile.name,
|
||||
FileUploadResults.TOO_LARGE
|
||||
);
|
||||
return { fileUploadResult: FileUploadResults.TOO_LARGE };
|
||||
default:
|
||||
UIService.setFileProgress(
|
||||
rawFile.name,
|
||||
FileUploadResults.FAILED
|
||||
);
|
||||
return { fileUploadResult: FileUploadResults.FAILED };
|
||||
}
|
||||
} finally {
|
||||
file = null;
|
||||
fileWithMetadata = null;
|
||||
encryptedFile = null;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,8 @@
|
|||
import { KeyAttributes } from 'types';
|
||||
import { KeyAttributes, PAGES } from 'types';
|
||||
import { getEndpoint } from 'utils/common/apiUtil';
|
||||
import { clearKeys } from 'utils/storage/sessionStorage';
|
||||
import router from 'next/router';
|
||||
import { clearData } from 'utils/storage/localStorage';
|
||||
import { clearData, getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||
import localForage from 'utils/storage/localForage';
|
||||
import { getToken } from 'utils/common/key';
|
||||
import HTTPService from './HTTPService';
|
||||
|
@ -86,6 +86,19 @@ export const getPublicKey = async (email: string) => {
|
|||
return resp.data.publicKey;
|
||||
};
|
||||
|
||||
export const getPaymentToken = async () => {
|
||||
const token = getToken();
|
||||
|
||||
const resp = await HTTPService.get(
|
||||
`${ENDPOINT}/users/payment-token`,
|
||||
null,
|
||||
{
|
||||
'X-Auth-Token': token,
|
||||
}
|
||||
);
|
||||
return resp.data['paymentToken'];
|
||||
};
|
||||
|
||||
export const verifyOtt = (email: string, ott: string) =>
|
||||
HTTPService.post(`${ENDPOINT}/users/verify-email`, { email, ott });
|
||||
|
||||
|
@ -111,7 +124,7 @@ export const logoutUser = async () => {
|
|||
clearData();
|
||||
await caches.delete('thumbs');
|
||||
await clearFiles();
|
||||
router.push('/');
|
||||
router.push(PAGES.ROOT);
|
||||
};
|
||||
|
||||
export const clearFiles = async () => {
|
||||
|
@ -120,9 +133,27 @@ export const clearFiles = async () => {
|
|||
|
||||
export const isTokenValid = async () => {
|
||||
try {
|
||||
await HTTPService.get(`${ENDPOINT}/users/session-validity`, null, {
|
||||
const resp = await HTTPService.get(
|
||||
`${ENDPOINT}/users/session-validity/v2`,
|
||||
null,
|
||||
{
|
||||
'X-Auth-Token': getToken(),
|
||||
});
|
||||
}
|
||||
);
|
||||
try {
|
||||
if (!resp.data['hasSetKeys']) {
|
||||
try {
|
||||
await putAttributes(
|
||||
getToken(),
|
||||
getData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES)
|
||||
);
|
||||
} catch (e) {
|
||||
logError(e, 'put attribute failed');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e, 'hasSetKeys not set in session validity response');
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
|
|
16
src/types.ts
16
src/types.ts
|
@ -21,3 +21,19 @@ export const IMAGE_CONTAINER_MAX_WIDTH =
|
|||
IMAGE_CONTAINER_MAX_HEIGHT - GAP_BTW_TILES;
|
||||
export const MIN_COLUMNS = 4;
|
||||
export const SPACE_BTW_DATES = 44;
|
||||
|
||||
export enum PAGES {
|
||||
CHANGE_EMAIL = '/change-email',
|
||||
CHANGE_PASSWORD = '/change-password',
|
||||
CREDENTIALS = '/credentials',
|
||||
GALLERY = '/gallery',
|
||||
GENERATE = '/generate',
|
||||
LOGIN = '/login',
|
||||
RECOVER = '/recover',
|
||||
SIGNUP = '/signup',
|
||||
TWO_FACTOR_SETUP = '/two-factor/setup',
|
||||
TWO_FACTOR_VERIFY = '/two-factor/verify',
|
||||
TWO_FACTOR_RECOVER = '/two-factor/recover',
|
||||
VERIFY = '/verify',
|
||||
ROOT = '/',
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import constants from 'utils/strings/constants';
|
||||
import billingService, {
|
||||
FREE_PLAN,
|
||||
PAYMENT_INTENT_STATUS,
|
||||
Plan,
|
||||
Subscription,
|
||||
} from 'services/billingService';
|
||||
|
@ -10,9 +9,23 @@ import { SetDialogMessage } from 'components/MessageDialog';
|
|||
import { SetLoading } from 'pages/gallery';
|
||||
import { getData, LS_KEYS } from './storage/localStorage';
|
||||
import { CustomError } from './common/errorUtil';
|
||||
import { logError } from './sentry';
|
||||
|
||||
const STRIPE = 'stripe';
|
||||
|
||||
enum FAILURE_REASON {
|
||||
AUTHENTICATION_FAILED = 'authentication_failed',
|
||||
REQUIRE_PAYMENT_METHOD = 'requires_payment_method',
|
||||
STRIPE_ERROR = 'stripe_error',
|
||||
CANCELED = 'canceled',
|
||||
SERVER_ERROR = 'server_error',
|
||||
}
|
||||
|
||||
enum RESPONSE_STATUS {
|
||||
success = 'success',
|
||||
fail = 'fail',
|
||||
}
|
||||
|
||||
export function convertBytesToGBs(bytes, precision?): string {
|
||||
return (bytes / (1024 * 1024 * 1024)).toFixed(precision ?? 2);
|
||||
}
|
||||
|
@ -94,49 +107,12 @@ export async function updateSubscription(
|
|||
try {
|
||||
setLoading(true);
|
||||
await billingService.updateSubscription(plan.stripeID);
|
||||
setLoading(false);
|
||||
await new Promise((resolve) => setTimeout(() => resolve(null), 400));
|
||||
setDialogMessage({
|
||||
title: constants.SUCCESS,
|
||||
content: constants.SUBSCRIPTION_PURCHASE_SUCCESS(
|
||||
getUserSubscription().expiryTime
|
||||
),
|
||||
close: { variant: 'success' },
|
||||
});
|
||||
} catch (err) {
|
||||
switch (err?.message) {
|
||||
case PAYMENT_INTENT_STATUS.REQUIRE_PAYMENT_METHOD:
|
||||
setDialogMessage({
|
||||
title: constants.UPDATE_PAYMENT_METHOD,
|
||||
content: constants.UPDATE_PAYMENT_METHOD_MESSAGE,
|
||||
staticBackdrop: true,
|
||||
proceed: {
|
||||
text: constants.UPDATE_PAYMENT_METHOD,
|
||||
variant: 'success',
|
||||
action: updatePaymentMethod.bind(
|
||||
null,
|
||||
|
||||
setDialogMessage,
|
||||
setLoading
|
||||
),
|
||||
},
|
||||
close: { text: constants.CANCEL },
|
||||
});
|
||||
break;
|
||||
case CustomError.SUBSCRIPTION_VERIFICATION_ERROR:
|
||||
setDialogMessage({
|
||||
title: constants.ERROR,
|
||||
content: constants.SUBSCRIPTION_VERIFICATION_FAILED,
|
||||
content: constants.SUBSCRIPTION_UPDATE_FAILED,
|
||||
close: { variant: 'danger' },
|
||||
});
|
||||
break;
|
||||
default:
|
||||
setDialogMessage({
|
||||
title: constants.ERROR,
|
||||
content: constants.SUBSCRIPTION_PURCHASE_FAILED,
|
||||
close: { variant: 'danger' },
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
closePlanSelectorModal();
|
||||
|
@ -201,30 +177,28 @@ export async function updatePaymentMethod(
|
|||
setLoading(true);
|
||||
await billingService.redirectToCustomerPortal();
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
setDialogMessage({
|
||||
title: constants.ERROR,
|
||||
content: constants.UNKNOWN_ERROR,
|
||||
close: { variant: 'danger' },
|
||||
});
|
||||
} finally {
|
||||
setLoading(true);
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkSubscriptionPurchase(
|
||||
setDialogMessage: SetDialogMessage,
|
||||
router: NextRouter
|
||||
router: NextRouter,
|
||||
setLoading: SetLoading
|
||||
) {
|
||||
try {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const sessionId = urlParams.get('session_id');
|
||||
if (sessionId === '-1') {
|
||||
setDialogMessage({
|
||||
title: constants.MESSAGE,
|
||||
content: constants.SUBSCRIPTION_PURCHASE_CANCELLED,
|
||||
close: { variant: 'danger' },
|
||||
});
|
||||
} else if (sessionId) {
|
||||
const status = urlParams.get('status');
|
||||
const reason = urlParams.get('reason');
|
||||
if (status === RESPONSE_STATUS.fail) {
|
||||
handleFailureReason(reason, setDialogMessage, setLoading);
|
||||
} else if (status === RESPONSE_STATUS.success) {
|
||||
try {
|
||||
const subscription = await billingService.verifySubscription(
|
||||
sessionId
|
||||
|
@ -251,6 +225,67 @@ export async function checkSubscriptionPurchase(
|
|||
}
|
||||
}
|
||||
|
||||
function handleFailureReason(
|
||||
reason: string,
|
||||
setDialogMessage: SetDialogMessage,
|
||||
setLoading: SetLoading
|
||||
): void {
|
||||
logError(Error(reason), 'subscription purchase failed');
|
||||
switch (reason) {
|
||||
case FAILURE_REASON.CANCELED:
|
||||
setDialogMessage({
|
||||
title: constants.MESSAGE,
|
||||
content: constants.SUBSCRIPTION_PURCHASE_CANCELLED,
|
||||
close: { variant: 'danger' },
|
||||
});
|
||||
break;
|
||||
case FAILURE_REASON.REQUIRE_PAYMENT_METHOD:
|
||||
setDialogMessage({
|
||||
title: constants.UPDATE_PAYMENT_METHOD,
|
||||
content: constants.UPDATE_PAYMENT_METHOD_MESSAGE,
|
||||
staticBackdrop: true,
|
||||
proceed: {
|
||||
text: constants.UPDATE_PAYMENT_METHOD,
|
||||
variant: 'success',
|
||||
action: updatePaymentMethod.bind(
|
||||
null,
|
||||
|
||||
setDialogMessage,
|
||||
setLoading
|
||||
),
|
||||
},
|
||||
close: { text: constants.CANCEL },
|
||||
});
|
||||
break;
|
||||
|
||||
case FAILURE_REASON.AUTHENTICATION_FAILED:
|
||||
setDialogMessage({
|
||||
title: constants.UPDATE_PAYMENT_METHOD,
|
||||
content: constants.STRIPE_AUTHENTICATION_FAILED,
|
||||
staticBackdrop: true,
|
||||
proceed: {
|
||||
text: constants.UPDATE_PAYMENT_METHOD,
|
||||
variant: 'success',
|
||||
action: updatePaymentMethod.bind(
|
||||
null,
|
||||
|
||||
setDialogMessage,
|
||||
setLoading
|
||||
),
|
||||
},
|
||||
close: { text: constants.CANCEL },
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
setDialogMessage({
|
||||
title: constants.ERROR,
|
||||
content: constants.SUBSCRIPTION_PURCHASE_FAILED,
|
||||
close: { variant: 'danger' },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function planForSubscription(subscription: Subscription) {
|
||||
if (!subscription) {
|
||||
return null;
|
||||
|
|
|
@ -27,3 +27,10 @@ export const getThumbnailUrl = (id: number) => {
|
|||
export const getSentryTunnelUrl = () => {
|
||||
return `https://sentry-reporter.ente.workers.dev`;
|
||||
};
|
||||
|
||||
export const getPaymentsUrl = () => {
|
||||
if (process.env.NEXT_PUBLIC_ENTE_ENDPOINT !== undefined) {
|
||||
return process.env.NEXT_PUBLIC_ENTE_PAYMENT_ENDPOINT;
|
||||
}
|
||||
return `https://payments.ente.io`;
|
||||
};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { AxiosResponse } from 'axios';
|
||||
import constants from 'utils/strings/constants';
|
||||
|
||||
export const ServerErrorCodes = {
|
||||
|
@ -5,47 +6,95 @@ export const ServerErrorCodes = {
|
|||
NO_ACTIVE_SUBSCRIPTION: '402',
|
||||
FORBIDDEN: '403',
|
||||
STORAGE_LIMIT_EXCEEDED: '426',
|
||||
FILE_TOO_LARGE: '413',
|
||||
};
|
||||
|
||||
export const CustomError = {
|
||||
SUBSCRIPTION_VERIFICATION_ERROR: 'Subscription verification failed',
|
||||
THUMBNAIL_GENERATION_FAILED: 'thumbnail generation failed',
|
||||
VIDEO_PLAYBACK_FAILED: 'video playback failed',
|
||||
ETAG_MISSING: 'no header/etag present in response body',
|
||||
KEY_MISSING: 'encrypted key missing from localStorage',
|
||||
};
|
||||
export enum CustomError {
|
||||
UNKNOWN_ERROR = 'unknown error',
|
||||
SUBSCRIPTION_VERIFICATION_ERROR = 'Subscription verification failed',
|
||||
THUMBNAIL_GENERATION_FAILED = 'thumbnail generation failed',
|
||||
VIDEO_PLAYBACK_FAILED = 'video playback failed',
|
||||
ETAG_MISSING = 'no header/etag present in response body',
|
||||
KEY_MISSING = 'encrypted key missing from localStorage',
|
||||
FAILED_TO_LOAD_WEB_WORKER = 'failed to load web worker',
|
||||
CHUNK_MORE_THAN_EXPECTED = 'chunks more than expected',
|
||||
UNSUPPORTED_FILE_FORMAT = 'unsupported file formats',
|
||||
FILE_TOO_LARGE = 'file too large',
|
||||
SUBSCRIPTION_EXPIRED = 'subscription expired',
|
||||
STORAGE_QUOTA_EXCEEDED = 'storage quota exceeded',
|
||||
SESSION_EXPIRED_MESSAGE = 'session expired',
|
||||
TYPE_DETECTION_FAILED = 'type detection failed',
|
||||
SIGNUP_FAILED = 'signup failed',
|
||||
}
|
||||
|
||||
export function parseError(error) {
|
||||
function parseUploadError(error: AxiosResponse) {
|
||||
let parsedMessage = null;
|
||||
if (error?.status) {
|
||||
const errorCode = error.status.toString();
|
||||
switch (errorCode) {
|
||||
case ServerErrorCodes.NO_ACTIVE_SUBSCRIPTION:
|
||||
parsedMessage = constants.SUBSCRIPTION_EXPIRED;
|
||||
parsedMessage = CustomError.SUBSCRIPTION_EXPIRED;
|
||||
break;
|
||||
case ServerErrorCodes.STORAGE_LIMIT_EXCEEDED:
|
||||
parsedMessage = constants.STORAGE_QUOTA_EXCEEDED;
|
||||
parsedMessage = CustomError.STORAGE_QUOTA_EXCEEDED;
|
||||
break;
|
||||
case ServerErrorCodes.SESSION_EXPIRED:
|
||||
parsedMessage = constants.SESSION_EXPIRED_MESSAGE;
|
||||
parsedMessage = CustomError.SESSION_EXPIRED_MESSAGE;
|
||||
break;
|
||||
case ServerErrorCodes.FILE_TOO_LARGE:
|
||||
parsedMessage = CustomError.FILE_TOO_LARGE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (parsedMessage) {
|
||||
return { parsedError: new Error(parsedMessage), parsed: true };
|
||||
return {
|
||||
parsedError: new Error(parsedMessage),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
parsedError: new Error(`${constants.UNKNOWN_ERROR} ${error}`),
|
||||
parsed: false,
|
||||
parsedError: new Error(CustomError.UNKNOWN_ERROR),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function handleError(error) {
|
||||
const { parsedError, parsed } = parseError(error);
|
||||
if (parsed) {
|
||||
throw parsedError;
|
||||
export function handleUploadError(error: AxiosResponse | Error): Error {
|
||||
let parsedError: Error = null;
|
||||
if ('status' in error) {
|
||||
parsedError = parseUploadError(error).parsedError;
|
||||
} else {
|
||||
// swallow error don't break the caller flow
|
||||
parsedError = error;
|
||||
}
|
||||
// breaking errors
|
||||
switch (parsedError.message) {
|
||||
case CustomError.SUBSCRIPTION_EXPIRED:
|
||||
case CustomError.STORAGE_QUOTA_EXCEEDED:
|
||||
case CustomError.SESSION_EXPIRED_MESSAGE:
|
||||
throw parsedError;
|
||||
}
|
||||
return parsedError;
|
||||
}
|
||||
|
||||
export function getUserFacingErrorMessage(
|
||||
err: CustomError,
|
||||
action: () => void
|
||||
) {
|
||||
switch (err) {
|
||||
case CustomError.SESSION_EXPIRED_MESSAGE:
|
||||
return constants.SESSION_EXPIRED_MESSAGE;
|
||||
case CustomError.SUBSCRIPTION_EXPIRED:
|
||||
return constants.SUBSCRIPTION_EXPIRED(action);
|
||||
case CustomError.STORAGE_QUOTA_EXCEEDED:
|
||||
return constants.STORAGE_QUOTA_EXCEEDED(action);
|
||||
default:
|
||||
return constants.UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
export function errorWithContext(originalError: Error, context: string) {
|
||||
const errorWithContext = new Error(context);
|
||||
errorWithContext.stack =
|
||||
errorWithContext.stack.split('\n').slice(2, 4).join('\n') +
|
||||
'\n' +
|
||||
originalError.stack;
|
||||
return errorWithContext;
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@ import constants from 'utils/strings/constants';
|
|||
export const DESKTOP_APP_DOWNLOAD_URL =
|
||||
'https://github.com/ente-io/bhari-frame/releases/latest';
|
||||
|
||||
const retrySleepTime = [2000, 5000, 10000];
|
||||
|
||||
export function checkConnectivity() {
|
||||
if (navigator.onLine) {
|
||||
return true;
|
||||
|
@ -32,20 +30,3 @@ export function reverseString(title: string) {
|
|||
?.split(' ')
|
||||
.reduce((reversedString, currWord) => `${currWord} ${reversedString}`);
|
||||
}
|
||||
|
||||
export async function retryAsyncFunction(
|
||||
func: () => Promise<any>,
|
||||
retryCount: number = 3
|
||||
) {
|
||||
try {
|
||||
const resp = await func();
|
||||
return resp;
|
||||
} catch (e) {
|
||||
if (retryCount > 0) {
|
||||
await sleep(retrySleepTime[3 - retryCount]);
|
||||
await retryAsyncFunction(func, retryCount - 1);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ExportRecord } from 'services/exportService';
|
||||
import { File } from 'services/fileService';
|
||||
import { MetadataObject } from 'services/uploadService';
|
||||
import { MetadataObject } from 'services/upload/uploadService';
|
||||
import { formatDate } from 'utils/file';
|
||||
|
||||
export const getExportRecordFileUID = (file: File) =>
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { Collection } from 'services/collectionService';
|
||||
import { File } from 'services/fileService';
|
||||
import { File, FILE_TYPE } from 'services/fileService';
|
||||
import { decodeMotionPhoto } from 'services/motionPhotoService';
|
||||
import { getMimeTypeFromBlob } from 'services/upload/readFileService';
|
||||
import { runningInBrowser } from 'utils/common';
|
||||
import CryptoWorker from 'utils/crypto';
|
||||
|
||||
const TYPE_HEIC = 'heic';
|
||||
export const TYPE_HEIC = 'heic';
|
||||
export const TYPE_HEIF = 'heif';
|
||||
const UNSUPPORTED_FORMATS = ['flv', 'mkv', '3gp', 'avi', 'wmv'];
|
||||
|
||||
export function downloadAsFile(filename: string, content: string) {
|
||||
|
@ -31,8 +34,11 @@ export async function convertHEIC2JPEG(fileBlob: Blob): Promise<Blob> {
|
|||
});
|
||||
}
|
||||
|
||||
export function fileIsHEIC(name: string) {
|
||||
return name.toLowerCase().endsWith(TYPE_HEIC);
|
||||
export function fileIsHEIC(mimeType: string) {
|
||||
return (
|
||||
mimeType.toLowerCase().endsWith(TYPE_HEIC) ||
|
||||
mimeType.toLowerCase().endsWith(TYPE_HEIF)
|
||||
);
|
||||
}
|
||||
|
||||
export function sortFilesIntoCollections(files: File[]) {
|
||||
|
@ -136,7 +142,7 @@ export async function decryptFile(file: File, collection: Collection) {
|
|||
return file;
|
||||
}
|
||||
|
||||
export function removeUnneccessaryFileProps(files: File[]): File[] {
|
||||
export function removeUnnecessaryFileProps(files: File[]): File[] {
|
||||
const stripedFiles = files.map((file) => {
|
||||
delete file.src;
|
||||
delete file.msrc;
|
||||
|
@ -171,3 +177,22 @@ export function generateStreamFromArrayBuffer(data: Uint8Array) {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function convertForPreview(file: File, fileBlob: Blob) {
|
||||
if (file.metadata.fileType === FILE_TYPE.LIVE_PHOTO) {
|
||||
const originalName = fileNameWithoutExtension(file.metadata.title);
|
||||
const motionPhoto = await decodeMotionPhoto(fileBlob, originalName);
|
||||
fileBlob = new Blob([motionPhoto.image]);
|
||||
}
|
||||
|
||||
const typeFromExtension = file.metadata.title.split('.')[-1];
|
||||
const worker = await new CryptoWorker();
|
||||
|
||||
const mimeType =
|
||||
(await getMimeTypeFromBlob(worker, fileBlob)) ?? typeFromExtension;
|
||||
|
||||
if (fileIsHEIC(mimeType)) {
|
||||
fileBlob = await convertHEIC2JPEG(fileBlob);
|
||||
}
|
||||
return fileBlob;
|
||||
}
|
||||
|
|
29
src/utils/network/index.ts
Normal file
29
src/utils/network/index.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { sleep } from 'utils/common';
|
||||
|
||||
const retrySleepTimeInMilliSeconds = [2000, 5000, 10000];
|
||||
|
||||
export async function retryAsyncFunction(
|
||||
func: () => Promise<any>,
|
||||
checkForBreakingError?: (error) => void
|
||||
) {
|
||||
const retrier = async (
|
||||
func: () => Promise<any>,
|
||||
attemptNumber: number = 0
|
||||
) => {
|
||||
try {
|
||||
const resp = await func();
|
||||
return resp;
|
||||
} catch (e) {
|
||||
if (checkForBreakingError) {
|
||||
checkForBreakingError(e);
|
||||
}
|
||||
if (attemptNumber < retrySleepTimeInMilliSeconds.length) {
|
||||
await sleep(retrySleepTimeInMilliSeconds[attemptNumber]);
|
||||
return await retrier(func, attemptNumber + 1);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
return await retrier(func);
|
||||
}
|
|
@ -1,14 +1,20 @@
|
|||
import * as Sentry from '@sentry/nextjs';
|
||||
import { errorWithContext } from 'utils/common/errorUtil';
|
||||
import { getUserAnonymizedID } from 'utils/user';
|
||||
|
||||
export const logError = (e: any, msg?: string) => {
|
||||
Sentry.captureException(e, {
|
||||
export const logError = (
|
||||
e: any,
|
||||
msg: string,
|
||||
info?: Record<string, unknown>
|
||||
) => {
|
||||
const err = errorWithContext(e, msg);
|
||||
Sentry.captureException(err, {
|
||||
level: Sentry.Severity.Info,
|
||||
user: { id: getUserAnonymizedID() },
|
||||
contexts: {
|
||||
context: {
|
||||
message: msg,
|
||||
},
|
||||
...(info && {
|
||||
info: info,
|
||||
}),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -23,6 +23,14 @@ const Logo = styled.img`
|
|||
margin-top: -3px;
|
||||
`;
|
||||
|
||||
const Trigger = styled.span`
|
||||
:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
color: #51cd7c;
|
||||
`;
|
||||
|
||||
const englishConstants = {
|
||||
HERO_HEADER: () => (
|
||||
<div>
|
||||
|
@ -40,9 +48,8 @@ const englishConstants = {
|
|||
HERO_SLIDE_3:
|
||||
'reliably replicated to a fallout shelter, designed to outlive',
|
||||
COMPANY_NAME: 'ente',
|
||||
LOGIN: 'login',
|
||||
LOGIN: 'log in',
|
||||
SIGN_UP: 'sign up',
|
||||
SIGN_IN: 'sign in',
|
||||
NAME: 'name',
|
||||
ENTER_NAME: 'your name',
|
||||
EMAIL: 'email',
|
||||
|
@ -63,6 +70,7 @@ const englishConstants = {
|
|||
VERIFY: 'verify',
|
||||
UNKNOWN_ERROR: 'something went wrong, please try again',
|
||||
INVALID_CODE: 'invalid verification code',
|
||||
EXPIRED_CODE: 'your verification code has expired',
|
||||
SENDING: 'sending...',
|
||||
SENT: 'sent!',
|
||||
PASSWORD: 'password',
|
||||
|
@ -87,7 +95,7 @@ const englishConstants = {
|
|||
CONSOLE_WARNING_DESC:
|
||||
"This is a browser feature intended for developers. Please don't copy-paste unverified code here.",
|
||||
SELECT_COLLECTION: 'select an album to upload to',
|
||||
CREATE_COLLECTION: 'create album',
|
||||
CREATE_COLLECTION: 'new album',
|
||||
ENTER_ALBUM_NAME: 'album name',
|
||||
CLOSE: 'close',
|
||||
NO: 'no',
|
||||
|
@ -103,8 +111,9 @@ const englishConstants = {
|
|||
FILE_NOT_UPLOADED_LIST: 'the following files were not uploaded',
|
||||
FILE_UPLOAD_PROGRESS: (name: string, progress: number) => (
|
||||
<div id={name}>
|
||||
<strong>{name}</strong>
|
||||
{name}
|
||||
{' - '}
|
||||
<span style={{ color: '#eee' }}>
|
||||
{(() => {
|
||||
switch (progress) {
|
||||
case -1:
|
||||
|
@ -112,17 +121,26 @@ const englishConstants = {
|
|||
case -2:
|
||||
return 'already uploaded, skipping...';
|
||||
case -3:
|
||||
return ',unsupported file format, skipping....';
|
||||
return 'unsupported file format, skipping....';
|
||||
default:
|
||||
return `${progress}%`;
|
||||
}
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
SUBSCRIPTION_EXPIRED: 'your subscription has expired, please renew it',
|
||||
|
||||
STORAGE_QUOTA_EXCEEDED:
|
||||
'you have exceeded your storage quota, please upgrade your plan',
|
||||
SUBSCRIPTION_EXPIRED: (action) => (
|
||||
<>
|
||||
your subscription has expired, please a{' '}
|
||||
<Trigger onClick={action}>renew</Trigger>
|
||||
</>
|
||||
),
|
||||
STORAGE_QUOTA_EXCEEDED: (action) => (
|
||||
<>
|
||||
you have exceeded your storage quota, please{' '}
|
||||
<Trigger onClick={action}>upgrade</Trigger> your plan
|
||||
</>
|
||||
),
|
||||
INITIAL_LOAD_DELAY_WARNING: 'the first load may take some time',
|
||||
USER_DOES_NOT_EXIST: 'sorry, could not find a user with that email',
|
||||
UPLOAD_BUTTON_TEXT: 'upload',
|
||||
|
@ -138,25 +156,24 @@ const englishConstants = {
|
|||
NO_INTERNET_CONNECTION:
|
||||
'please check your internet connection and try again',
|
||||
TITLE: 'ente.io | encrypted photo storage',
|
||||
UPLOAD_FIRST_PHOTO: 'backup your first photo',
|
||||
UPLOAD_FIRST_PHOTO_DESCRIPTION: 'preserve your first memory with ente',
|
||||
UPLOAD_FIRST_PHOTO: 'preserve',
|
||||
UPLOAD_DROPZONE_MESSAGE: 'drop to backup your files',
|
||||
CONFIRM_DELETE_FILE: 'confirm file deletion',
|
||||
DELETE_FILE_MESSAGE: 'sure you want to delete selected files?',
|
||||
DELETE_FILE: 'delete files',
|
||||
DELETE: 'delete',
|
||||
MULTI_FOLDER_UPLOAD: 'choose upload strategy',
|
||||
UPLOAD_STRATEGY_CHOICE:
|
||||
'you are uploading multiple folders, would you like us to create',
|
||||
UPLOAD_STRATEGY_SINGLE_COLLECTION: 'a single album for everything',
|
||||
MULTI_FOLDER_UPLOAD: 'multiple folders detected',
|
||||
UPLOAD_STRATEGY_CHOICE: 'would you like to upload them into',
|
||||
UPLOAD_STRATEGY_SINGLE_COLLECTION: 'a single album',
|
||||
OR: 'or',
|
||||
UPLOAD_STRATEGY_COLLECTION_PER_FOLDER: 'separate albums for every folder',
|
||||
UPLOAD_STRATEGY_COLLECTION_PER_FOLDER: 'separate albums',
|
||||
SESSION_EXPIRED_MESSAGE:
|
||||
'your session has expired, please login again to continue',
|
||||
SESSION_EXPIRED: 'session expired',
|
||||
SYNC_FAILED:
|
||||
'failed to sync with remote server, please refresh page to try again',
|
||||
SYNC_FAILED: 'failed to sync with server, please refresh this page',
|
||||
PASSWORD_GENERATION_FAILED:
|
||||
"your browser was unable to generate a strong enough password that meets ente's encryption standards, please try using the mobile app or another browser",
|
||||
"your browser was unable to generate a strong key that meets ente's encryption standards, please try using the mobile app or another browser",
|
||||
CHANGE_PASSWORD: 'change password',
|
||||
GO_BACK: 'go back',
|
||||
DOWNLOAD_RECOVERY_KEY: 'recovery key',
|
||||
|
@ -165,7 +182,7 @@ const englishConstants = {
|
|||
RECOVERY_KEY_DESCRIPTION:
|
||||
'if you forget your password, the only way you can recover your data is with this key',
|
||||
RECOVER_KEY_GENERATION_FAILED:
|
||||
'recovery code could be generated, please try again',
|
||||
'recovery code could not be generated, please try again',
|
||||
KEY_NOT_STORED_DISCLAIMER:
|
||||
"we don't store this key, so please save this in a safe place",
|
||||
RECOVERY_KEY_FILENAME: 'ente-recovery-key.txt',
|
||||
|
@ -182,7 +199,7 @@ const englishConstants = {
|
|||
<>
|
||||
please drop an email to{' '}
|
||||
<a href="mailto:support@ente.io">support@ente.io</a> from your
|
||||
registered email
|
||||
registered email address
|
||||
</>
|
||||
),
|
||||
CONTACT_SUPPORT: 'contact support',
|
||||
|
@ -192,9 +209,22 @@ const englishConstants = {
|
|||
SKIP: 'skip',
|
||||
CANCEL: 'cancel',
|
||||
LOGOUT: 'logout',
|
||||
DELETE_ACCOUNT: 'delete account',
|
||||
DELETE_MESSAGE: () => (
|
||||
<>
|
||||
<p>
|
||||
please send an email to{' '}
|
||||
<a href="mailto:account-deletion@ente.io">
|
||||
account-deletion@ente.io
|
||||
</a>{' '}
|
||||
from your registered email address.{' '}
|
||||
</p>
|
||||
your request will be processed within 72 hours.
|
||||
</>
|
||||
),
|
||||
LOGOUT_MESSAGE: 'sure you want to logout?',
|
||||
CHANGE: 'change',
|
||||
CHANGE_EMAIL: 'change email ?',
|
||||
CHANGE_EMAIL: 'change email?',
|
||||
OK: 'ok',
|
||||
SUCCESS: 'success',
|
||||
ERROR: 'error',
|
||||
|
@ -205,14 +235,14 @@ const englishConstants = {
|
|||
<a
|
||||
href="https://play.google.com/store/apps/details?id=io.ente.photos"
|
||||
target="_blank"
|
||||
style={{ color: '#2dc262' }}
|
||||
style={{ color: '#51cd7c' }}
|
||||
rel="noreferrer">
|
||||
android
|
||||
</a>{' '}
|
||||
or{' '}
|
||||
<a
|
||||
href="https://apps.apple.com/in/app/ente-photos/id1542026904"
|
||||
style={{ color: '#2dc262' }}
|
||||
style={{ color: '#51cd7c' }}
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
ios app{' '}
|
||||
|
@ -222,8 +252,10 @@ const englishConstants = {
|
|||
),
|
||||
DOWNLOAD_APP_MESSAGE: () => (
|
||||
<>
|
||||
<p>sorry, this operation is currently not supported on the web,</p>
|
||||
<p> do you want to download the desktop app</p>
|
||||
<p>
|
||||
sorry, this operation is currently only supported on our desktop
|
||||
app
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
DOWNLOAD_APP: 'download desktop app',
|
||||
|
@ -265,7 +297,7 @@ const englishConstants = {
|
|||
|
||||
USAGE_INFO: (usage, quota) => (
|
||||
<p>
|
||||
you have used {usage} out of your {quota} GB quota
|
||||
you have used {usage} out of your {quota} quota
|
||||
</p>
|
||||
),
|
||||
|
||||
|
@ -281,10 +313,13 @@ const englishConstants = {
|
|||
SUBSCRIPTION_VERIFICATION_FAILED:
|
||||
'we were not able to verify your purchase, verification can take few hours',
|
||||
SUBSCRIPTION_PURCHASE_FAILED:
|
||||
'subscription purchase failed , please try again later',
|
||||
|
||||
'subscription purchase failed , please try again',
|
||||
SUBSCRIPTION_UPDATE_FAILED:
|
||||
'subscription updated failed , please try again',
|
||||
UPDATE_PAYMENT_METHOD_MESSAGE:
|
||||
'we are sorry, payment failed when we tried to charge your card, please update your payment method and try again',
|
||||
STRIPE_AUTHENTICATION_FAILED:
|
||||
'we are unable to authenticate your payment method. please choose a different payment method and try again',
|
||||
UPDATE_PAYMENT_METHOD: 'update payment method',
|
||||
MONTHLY: 'monthly',
|
||||
YEARLY: 'yearly',
|
||||
|
@ -319,13 +354,13 @@ const englishConstants = {
|
|||
RENAME_COLLECTION: 'rename album',
|
||||
CONFIRM_DELETE_COLLECTION: 'confirm album deletion',
|
||||
DELETE_COLLECTION: 'delete album',
|
||||
DELETE_COLLECTION_FAILED: 'album deletion failed , please try again',
|
||||
DELETE_COLLECTION_FAILED: 'album deletion failed, please try again',
|
||||
DELETE_COLLECTION_MESSAGE: () => (
|
||||
<>
|
||||
<p>are you sure you want to delete this album?</p>
|
||||
<p>
|
||||
all files that are present only in this album will be
|
||||
permanently deleted
|
||||
all files that are unique to this album will be permanently
|
||||
deleted
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
|
@ -336,7 +371,11 @@ const englishConstants = {
|
|||
ZERO_SHAREES: () => (
|
||||
<>
|
||||
<h6>currently shared with no one 😔</h6>
|
||||
<em style={{ color: '#777' }}>"memories are fonder when shared"</em>
|
||||
<div style={{ marginTop: '16px' }}>
|
||||
<em style={{ color: '#3c3c3c' }}>
|
||||
memories are fonder when shared
|
||||
</em>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
SHARE_WITH_SELF: 'oops, you cannot share with yourself',
|
||||
|
@ -362,10 +401,10 @@ const englishConstants = {
|
|||
),
|
||||
CONFIRM_PASSWORD_NOT_SAVED: () => (
|
||||
<p>
|
||||
i understand that if i lose my password , i may lose my data since
|
||||
I understand that if I lose my password , I may lose my data since
|
||||
my data is{' '}
|
||||
<a
|
||||
href="https://ente.io/encryption"
|
||||
href="https://ente.io/architecture"
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
end-to-end encrypted
|
||||
|
@ -375,12 +414,12 @@ const englishConstants = {
|
|||
),
|
||||
SEARCH_STATS: ({ resultCount, timeTaken }) => (
|
||||
<span>
|
||||
found <span style={{ color: '#2dc262' }}>{resultCount}</span>{' '}
|
||||
memories ( <span style={{ color: '#2dc262' }}> {timeTaken}</span>{' '}
|
||||
found <span style={{ color: '#51cd7c' }}>{resultCount}</span>{' '}
|
||||
memories ( <span style={{ color: '#51cd7c' }}> {timeTaken}</span>{' '}
|
||||
seconds )
|
||||
</span>
|
||||
),
|
||||
NOT_FILE_OWNER: 'deleting shared collection files is not allowed',
|
||||
NOT_FILE_OWNER: 'you cannot delete files in a shared album',
|
||||
ADD_TO_COLLECTION: 'add to collection',
|
||||
SELECTED: 'selected',
|
||||
VIDEO_PLAYBACK_FAILED: 'video format not supported',
|
||||
|
@ -409,7 +448,7 @@ const englishConstants = {
|
|||
FILES_TO_BE_UPLOADED: (count: number) =>
|
||||
count === 1
|
||||
? `1 file received. uploading in a jiffy`
|
||||
: `${count} files received. Uploading in a jiffy`,
|
||||
: `${count} files received. uploading in a jiffy`,
|
||||
TWO_FACTOR: 'two-factor',
|
||||
TWO_FACTOR_AUTHENTICATION: 'two-factor authentication',
|
||||
TWO_FACTOR_QR_INSTRUCTION:
|
||||
|
@ -479,7 +518,7 @@ const englishConstants = {
|
|||
using <code>eTags</code> to upload large files, or use our{' '}
|
||||
<a
|
||||
href={url}
|
||||
style={{ color: '#2dc262', textDecoration: 'underline' }}
|
||||
style={{ color: '#51cd7c', textDecoration: 'underline' }}
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
desktop app
|
||||
|
@ -491,15 +530,18 @@ const englishConstants = {
|
|||
|
||||
RETRY_FAILED: 'retry failed uploads',
|
||||
FAILED_UPLOADS: 'failed uploads ',
|
||||
SKIPPED_FILES: 'duplicate files',
|
||||
SKIPPED_FILES: 'ignored uploads',
|
||||
UNSUPPORTED_FILES: 'unsupported files',
|
||||
SUCCESSFUL_UPLOADS: 'successful uploads',
|
||||
FAILED_INFO:
|
||||
' unable to upload these files because of network issue, you can retry upload these files',
|
||||
SKIPPED_INFO: 'these files already existed in the album',
|
||||
UNSUPPORTED_INFO: 'these files are currently not supported by ente',
|
||||
SUCCESS_INFO: 'successfully backed-up memories',
|
||||
SKIPPED_INFO:
|
||||
'skipped these as there are files with matching names in the same album',
|
||||
UNSUPPORTED_INFO: 'ente does not support these file formats yet',
|
||||
BLOCKED_UPLOADS: 'blocked uploads',
|
||||
INPROGRESS_UPLOADS: 'uploads in progress',
|
||||
TOO_LARGE_UPLOADS: 'large files',
|
||||
TOO_LARGE_INFO:
|
||||
'these files were not uploaded as they exceed the maximum size limit for your storage plan',
|
||||
UPLOAD_TO_COLLECTION: 'upload to album',
|
||||
};
|
||||
|
||||
export default englishConstants;
|
||||
|
|
51
src/utils/upload/index.ts
Normal file
51
src/utils/upload/index.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { FileWithCollection } from 'services/upload/uploadManager';
|
||||
import { MetadataObject } from 'services/upload/uploadService';
|
||||
import { File } from 'services/fileService';
|
||||
const TYPE_JSON = 'json';
|
||||
|
||||
export function fileAlreadyInCollection(
|
||||
existingFilesInCollection: File[],
|
||||
newFileMetadata: MetadataObject
|
||||
): boolean {
|
||||
for (const existingFile of existingFilesInCollection) {
|
||||
if (areFilesSame(existingFile.metadata, newFileMetadata)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export function areFilesSame(
|
||||
existingFile: MetadataObject,
|
||||
newFile: MetadataObject
|
||||
): boolean {
|
||||
if (
|
||||
existingFile.fileType === newFile.fileType &&
|
||||
existingFile.creationTime === newFile.creationTime &&
|
||||
existingFile.modificationTime === newFile.modificationTime &&
|
||||
existingFile.title === newFile.title
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function segregateFiles(
|
||||
filesWithCollectionToUpload: FileWithCollection[]
|
||||
) {
|
||||
const metadataFiles: FileWithCollection[] = [];
|
||||
const mediaFiles: FileWithCollection[] = [];
|
||||
filesWithCollectionToUpload.forEach((fileWithCollection) => {
|
||||
const file = fileWithCollection.file;
|
||||
if (file.name.startsWith('.')) {
|
||||
// ignore files with name starting with . (hidden files)
|
||||
return;
|
||||
}
|
||||
if (file.name.toLowerCase().endsWith(TYPE_JSON)) {
|
||||
metadataFiles.push(fileWithCollection);
|
||||
} else {
|
||||
mediaFiles.push(fileWithCollection);
|
||||
}
|
||||
});
|
||||
return { mediaFiles, metadataFiles };
|
||||
}
|
|
@ -147,6 +147,30 @@ export class Crypto {
|
|||
async fromHex(string) {
|
||||
return libsodium.fromHex(string);
|
||||
}
|
||||
|
||||
// temporary fix for https://github.com/vercel/next.js/issues/25484
|
||||
async getUint8ArrayView(file) {
|
||||
try {
|
||||
return await new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onabort = () =>
|
||||
reject(Error('file reading was aborted'));
|
||||
reader.onerror = () => reject(Error('file reading has failed'));
|
||||
reader.onload = () => {
|
||||
// Do whatever you want with the file contents
|
||||
const result =
|
||||
typeof reader.result === 'string'
|
||||
? new TextEncoder().encode(reader.result)
|
||||
: new Uint8Array(reader.result);
|
||||
resolve(result);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e, 'error reading file to byte-array');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Comlink.expose(Crypto);
|
||||
|
|
2
thirdparty/photoswipe
vendored
2
thirdparty/photoswipe
vendored
|
@ -1 +1 @@
|
|||
Subproject commit e0fa4850e214885e92d7312e03d7c8e071150648
|
||||
Subproject commit 443b1e393aa37899373b71272e4bcf191529bb74
|
85
yarn.lock
85
yarn.lock
|
@ -1003,6 +1003,21 @@
|
|||
minimatch "^3.0.4"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@ffmpeg/core@^0.10.0":
|
||||
version "0.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@ffmpeg/core/-/core-0.10.0.tgz#f6a58361b22d7c23c6f7071b9fff6d572bc3f499"
|
||||
integrity sha512-qunWJl5PezpXEm31tb8Qu5z37B5KVA1VYZCpXchMhuAb3X9T7PuE3SlhOwphEoRhzaOa3lpofDfzihAUMFaVPQ==
|
||||
|
||||
"@ffmpeg/ffmpeg@^0.10.1":
|
||||
version "0.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@ffmpeg/ffmpeg/-/ffmpeg-0.10.1.tgz#3dacf3985de9c83a95fbf79fe709920cc009b00a"
|
||||
integrity sha512-ChQkH7Rh57hmVo1LhfQFibWX/xqneolJKSwItwZdKPcLZuKigtYAYDIvB55pDfP17VtR1R77SxgkB2/UApB+Og==
|
||||
dependencies:
|
||||
is-url "^1.2.4"
|
||||
node-fetch "^2.6.1"
|
||||
regenerator-runtime "^0.13.7"
|
||||
resolve-url "^0.2.1"
|
||||
|
||||
"@hapi/accept@5.0.2":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-5.0.2.tgz#ab7043b037e68b722f93f376afb05e85c0699523"
|
||||
|
@ -1332,6 +1347,11 @@
|
|||
ejs "^2.6.1"
|
||||
magic-string "^0.25.0"
|
||||
|
||||
"@tokenizer/token@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276"
|
||||
integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==
|
||||
|
||||
"@types/debounce-promise@^3.1.3":
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/debounce-promise/-/debounce-promise-3.1.4.tgz#bf10eead11724e666ea541df1c9d3969677a505b"
|
||||
|
@ -1415,6 +1435,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
|
||||
integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
|
||||
|
||||
"@types/react-collapse@^5.0.1":
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-collapse/-/react-collapse-5.0.1.tgz#078ea1ad15e00ba2063f2e4d8d6760c9375a2023"
|
||||
integrity sha512-Iq3OrqvzCIP0DmAawU4T2VKH6XAplbjo/D7Qk14mcfQ92plU+OrA2SF10r2XrcFg1Wvya/5f8w1vS29RVpdoLQ==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-dom@*":
|
||||
version "17.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add"
|
||||
|
@ -3258,6 +3285,15 @@ file-selector@^0.2.2:
|
|||
dependencies:
|
||||
tslib "^2.0.3"
|
||||
|
||||
file-type@^16.5.3:
|
||||
version "16.5.3"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.3.tgz#474b7e88c74724046abb505e9b8ed4db30c4fc06"
|
||||
integrity sha512-uVsl7iFhHSOY4bEONLlTK47iAHtNsFHWP5YE4xJfZ4rnX7S1Q3wce09XgqSC7E/xh8Ncv/be1lNoyprlUH/x6A==
|
||||
dependencies:
|
||||
readable-web-to-node-stream "^3.0.0"
|
||||
strtok3 "^6.2.4"
|
||||
token-types "^4.1.1"
|
||||
|
||||
filesize@^3.6.1:
|
||||
version "3.6.1"
|
||||
resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317"
|
||||
|
@ -3680,7 +3716,7 @@ idb@^6.0.0:
|
|||
resolved "https://registry.yarnpkg.com/idb/-/idb-6.1.2.tgz#82ef5c951b8e1f47875d36ccafa4bedafc62f2f1"
|
||||
integrity sha512-1DNDVu3yDhAZkFDlJf0t7r+GLZ248F5pTAtA7V0oVG3yjmV125qZOx3g0XpAEkGZVYQiFDAsSOnGet2bhugc3w==
|
||||
|
||||
ieee754@^1.1.4:
|
||||
ieee754@^1.1.4, ieee754@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||
|
@ -3969,6 +4005,11 @@ is-unicode-supported@^0.1.0:
|
|||
resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
|
||||
integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
|
||||
|
||||
is-url@^1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52"
|
||||
integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==
|
||||
|
||||
isarray@^1.0.0, isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
|
@ -4518,7 +4559,7 @@ next@^11.1.0:
|
|||
vm-browserify "1.1.2"
|
||||
watchpack "2.1.1"
|
||||
|
||||
node-fetch@2.6.1, node-fetch@^2.6.0:
|
||||
node-fetch@2.6.1, node-fetch@^2.6.0, node-fetch@^2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||
|
@ -4881,6 +4922,11 @@ pbkdf2@^3.0.3:
|
|||
safe-buffer "^5.0.1"
|
||||
sha.js "^2.4.8"
|
||||
|
||||
peek-readable@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.0.1.tgz#9a045f291db254111c3412c1ce4fec27ddd4d202"
|
||||
integrity sha512-7qmhptnR0WMSpxT5rMHG9bW/mYSR1uqaPFj2MHvT+y/aOUu6msJijpKt5SkTDKySwg65OWG2JwTMBlgcbwMHrQ==
|
||||
|
||||
"photoswipe@file:./thirdparty/photoswipe":
|
||||
version "4.1.3"
|
||||
|
||||
|
@ -5168,6 +5214,11 @@ react-burger-menu@^3.0.4:
|
|||
prop-types "^15.7.2"
|
||||
snapsvg-cjs "0.0.6"
|
||||
|
||||
react-collapse@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-collapse/-/react-collapse-5.1.0.tgz#36f69ecb0fe797f976aaf5e4f2b2c248d2760140"
|
||||
integrity sha512-5v0ywsn9HjiR/odNzbRDs0RZfrnbdSippJebWOBCFFDA12Vx8DddrbI4qWVf1P2wTiVagrpcSy07AU0b6+gM9Q==
|
||||
|
||||
react-dom@16.13.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f"
|
||||
|
@ -5331,6 +5382,13 @@ readable-stream@^3.5.0, readable-stream@^3.6.0:
|
|||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
readable-web-to-node-stream@^3.0.0:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb"
|
||||
integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==
|
||||
dependencies:
|
||||
readable-stream "^3.6.0"
|
||||
|
||||
readdirp@~3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
|
||||
|
@ -5355,7 +5413,7 @@ regenerator-runtime@^0.11.0:
|
|||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
|
||||
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
|
||||
|
||||
regenerator-runtime@^0.13.4:
|
||||
regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7:
|
||||
version "0.13.9"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
|
||||
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
|
||||
|
@ -5419,6 +5477,11 @@ resolve-from@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
||||
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
|
||||
|
||||
resolve-url@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
|
||||
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
|
||||
|
||||
resolve@1.1.7:
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
|
||||
|
@ -5993,6 +6056,14 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
|
|||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
|
||||
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
||||
|
||||
strtok3@^6.2.4:
|
||||
version "6.2.4"
|
||||
resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.2.4.tgz#302aea64c0fa25d12a0385069ba66253fdc38a81"
|
||||
integrity sha512-GO8IcFF9GmFDvqduIspUBwCzCbqzegyVKIsSymcMgiZKeCfrN9SowtUoi8+b59WZMAjIzVZic/Ft97+pynR3Iw==
|
||||
dependencies:
|
||||
"@tokenizer/token" "^0.3.0"
|
||||
peek-readable "^4.0.1"
|
||||
|
||||
styled-components@^5.2.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.0.tgz#e47c3d3e9ddfff539f118a3dd0fd4f8f4fb25727"
|
||||
|
@ -6150,6 +6221,14 @@ toidentifier@1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
|
||||
|
||||
token-types@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/token-types/-/token-types-4.1.1.tgz#ef9e8c8e2e0ded9f1b3f8dbaa46a3228b113ba1a"
|
||||
integrity sha512-hD+QyuUAyI2spzsI0B7gf/jJ2ggR4RjkAo37j3StuePhApJUwcWDjnHDOFdIWYSwNR28H14hpwm4EI+V1Ted1w==
|
||||
dependencies:
|
||||
"@tokenizer/token" "^0.3.0"
|
||||
ieee754 "^1.2.1"
|
||||
|
||||
toposort@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
|
||||
|
|
Loading…
Reference in a new issue