Merge branch 'master' into dependabot/npm_and_yarn/next-11.1.0

This commit is contained in:
Abhinav-grd 2021-09-04 23:53:26 +05:30
commit 238823a8bf
88 changed files with 3381 additions and 1859 deletions

1
.gitignore vendored
View file

@ -45,3 +45,4 @@ public/sw.js
public/sw.js.map
public/worker-*.js
public/worker-*.js.map

View file

@ -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!

View file

@ -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"
X-XSS-Protection = "1; mode=block"
Cross-Origin-Opener-Policy = "same-origin"
Cross-Origin-Embedder-Policy = "require-corp"

View file

@ -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 }

View file

@ -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",

View 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=`;

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

View 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

Binary file not shown.

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

View file

@ -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

View file

@ -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;

View file

@ -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}`);
}

View file

@ -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: '' }}

View file

@ -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;
`;

View file

@ -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"
/>
);

View file

@ -20,6 +20,7 @@ interface Props {
cancelExport: () => void;
pauseExport: () => void;
}
export default function ExportInProgress(props: Props) {
return (
<>

View file

@ -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;

View file

@ -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}`);
}

View file

@ -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>

View file

@ -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

View file

@ -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;
display: flex;
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;
right: 80px;
color: #fff;
cursor: pointer;
transition: opacity 1s ease;
opacity: ${(props) => (props.isDisabled ? 0 : 1)};
min-height: 64px;
position: fixed;
display: flex;
align-items: center;
justify-content: center;
const SearchButton = styled.div<{ isOpen: boolean }>`
display: none;
@media (max-width: 624px) {
display: ${({ isOpen }) => (!isOpen ? 'flex' : 'none')};
right: 80px;
cursor: pointer;
position: fixed;
top: 0;
z-index: 1000;
align-items: 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>
</Wrapper>
) : (
<SearchButton
isDisabled={props.isFirstFetch}
onClick={() => !props.isFirstFetch && props.setOpen(true)}>
<SearchIcon />
</SearchButton>
)}
</SearchInput>
</Wrapper>
<SearchButton
isOpen={props.isOpen}
onClick={() => !props.isFirstFetch && props.setOpen(true)}>
<SearchIcon />
</SearchButton>
</>
);
}

View file

@ -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);
}

View file

@ -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',

View file

@ -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;
@ -41,34 +42,45 @@ export default function SignUp(props: SignUpProps) {
) => {
setLoading(true);
try {
setData(LS_KEYS.USER, { email });
await getOtt(email);
} catch (e) {
setFieldError('email', `${constants.UNKNOWN_ERROR} ${e.message}`);
}
try {
if (passphrase === confirm) {
const { keyAttributes, masterKey } =
await generateKeyAttributes(passphrase);
setData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES, keyAttributes);
await generateAndSaveIntermediateKeyAttributes(
passphrase,
keyAttributes,
masterKey
try {
setData(LS_KEYS.USER, { email });
await getOtt(email);
} catch (e) {
setFieldError(
'confirm',
`${constants.UNKNOWN_ERROR} ${e.message}`
);
await SaveKeyInSessionStore(
SESSION_KEYS.ENCRYPTION_KEY,
masterKey
);
setJustSignedUp(true);
router.push('/verify');
} else {
setFieldError('confirm', constants.PASSPHRASE_MATCH_ERROR);
throw e;
}
} catch (e) {
logError(e);
setFieldError('passphrase', constants.PASSWORD_GENERATION_FAILED);
try {
if (passphrase === confirm) {
const { keyAttributes, masterKey } =
await generateKeyAttributes(passphrase);
setData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES, keyAttributes);
await generateAndSaveIntermediateKeyAttributes(
passphrase,
keyAttributes,
masterKey
);
await SaveKeyInSessionStore(
SESSION_KEYS.ENCRYPTION_KEY,
masterKey
);
setJustSignedUp(true);
router.push(PAGES.VERIFY);
} else {
setFieldError('confirm', constants.PASSPHRASE_MATCH_ERROR);
}
} catch (e) {
setFieldError(
'passphrase',
constants.PASSWORD_GENERATION_FAILED
);
throw e;
}
} catch (err) {
logError(err, 'signup failed');
}
setLoading(false);
};

View file

@ -23,7 +23,7 @@ const SubmitButton = ({ loading, buttonText, inline, disabled }: Props) => (
width: '22px',
height: '22px',
borderWidth: '0.20em',
color: '#2dc262',
color: '#51cd7c',
}}
/>
) : (

View file

@ -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>

View 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',
};

View file

@ -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' }}>

View file

@ -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>
) : (
<></>
);
}

View file

@ -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>

View file

@ -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]);

View file

@ -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:

View file

@ -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);
}
}
}

View file

@ -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;
}

View file

@ -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) => {
props.closeCollectionSelector();
await uploadFilesToNewCollections(
UPLOAD_STRATEGY.SINGLE_COLLECTION,
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();

View file

@ -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>

View file

@ -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>
<FileList>
{fileList.map((fileName) => (
<li key={fileName}>{fileName}</li>
))}
</FileList>
</Content>
</>
<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,54 +229,54 @@ 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}
/>
{props.uploadStage === UPLOAD_STAGES.FINISH && (
<Modal.Footer style={{ border: 'none' }}>
{props.uploadStage === UPLOAD_STAGES.FINISH &&
(fileUploadResultMap?.get(FileUploadResults.FAILED)
?.length > 0 ||
fileUploadResultMap?.get(FileUploadResults.BLOCKED)
?.length > 0 ? (
<Button
variant="outline-success"
style={{ width: '100%' }}
onClick={props.retryFailed}>
{constants.RETRY_FAILED}
</Button>
) : (
<Button
variant="outline-secondary"
style={{ width: '100%' }}
onClick={props.closeModal}>
{constants.CLOSE}
</Button>
))}
</Modal.Footer>
)}
<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 &&
(fileUploadResultMap?.get(FileUploadResults.FAILED)
?.length > 0 ||
fileUploadResultMap?.get(FileUploadResults.BLOCKED)
?.length > 0 ? (
<Button
variant="outline-success"
style={{ width: '100%' }}
onClick={props.retryFailed}>
{constants.RETRY_FAILED}
</Button>
) : (
<Button
variant="outline-secondary"
style={{ width: '100%' }}
onClick={props.closeModal}>
{constants.CLOSE}
</Button>
))}
</Modal.Footer>
)}
</Modal>
);
}

View file

@ -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 && (

View file

@ -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);

View file

@ -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

View file

@ -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}>

View file

@ -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}

View file

@ -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}
/>

View file

@ -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>

View file

@ -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 (

View file

@ -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);
}
};

View file

@ -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 (

View file

@ -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);
}
};

View file

@ -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>

View file

@ -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}>

View file

@ -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}`);
}

View file

@ -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`,
{
productID,
},
null,
{
'X-Auth-Token': getToken(),
}
const paymentToken = await getPaymentToken();
await this.redirectToPayments(
paymentToken,
productID,
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(),
}

View file

@ -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;

View file

@ -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]);
}
if (fileIsHEIC(file.metadata.title)) {
fileBlob = await convertHEIC2JPEG(fileBlob);
}
fileBlob = await convertForPreview(file, 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');
}

View 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[]) {

View 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();

View file

@ -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;

View 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);
}

View 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;
}

View 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
}
}

View 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);
}

View 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();
}
}
}

View 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;
}
}

View 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;
}

View 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();

View 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();

View 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();

View 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();

View 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

View file

@ -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, {
'X-Auth-Token': getToken(),
});
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;

View file

@ -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 = '/',
}

View file

@ -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,
close: { variant: 'danger' },
});
break;
default:
setDialogMessage({
title: constants.ERROR,
content: constants.SUBSCRIPTION_PURCHASE_FAILED,
close: { variant: 'danger' },
});
}
setDialogMessage({
title: constants.ERROR,
content: constants.SUBSCRIPTION_UPDATE_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;

View file

@ -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`;
};

View file

@ -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;
}

View file

@ -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;
}
}
}

View file

@ -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) =>

View 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;
}

View 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);
}

View file

@ -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,
}),
},
});
};

View file

@ -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,26 +111,36 @@ 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}
{' - '}
{(() => {
switch (progress) {
case -1:
return 'failed';
case -2:
return 'already uploaded, skipping...';
case -3:
return ',unsupported file format, skipping....';
default:
return `${progress}%`;
}
})()}
<span style={{ color: '#eee' }}>
{(() => {
switch (progress) {
case -1:
return 'failed';
case -2:
return 'already uploaded, skipping...';
case -3:
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
View 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 };
}

View file

@ -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);

@ -1 +1 @@
Subproject commit e0fa4850e214885e92d7312e03d7c8e071150648
Subproject commit 443b1e393aa37899373b71272e4bcf191529bb74

View file

@ -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"