/**
 * Minified by jsDelivr using Terser v5.39.0.
 * Original file: /npm/http-file-upload@2.0.5/http-file-upload.js
 *
 * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
 */
var key,cert,cacert,server,port=3e3,userdir="",chunksize=1048576,cwd="",mode="https",https=require("https"),http=require("http"),fs=require("fs"),fsp=fs.promises,path=require("path"),dns=require("dns"),os=require("os"),program={name:"http-file-upload",version:"v2.0.5"},ws=wsmod(),txt={};function cmdline(){for(var n=process.argv.length,e=0;e<n;e++){var o=process.argv[e];n:for(var t=cmdline.list.length,r=0;r<t;r++)for(var a=cmdline.list[r],i=a.id.length,s=0;s<i;s++)if(o==a.id[s]||o=="-"+a.id[s]||o=="--"+a.id[s]){e+=cmdline[a.fn](e);break n}}}setup(),cwd=cwd||process.cwd(),cmdline.list=[{id:["p","port"],fn:"port"},{id:["d","dir"],fn:"dir"},{id:["cwd"],fn:"cwd"},{id:["http","https"],fn:"mode"},{id:["v","version"],fn:"version"},{id:["cert"],fn:"cert"},{id:["h","help"],fn:"help"}],cmdline.port=function(n){var e=Number(process.argv[n+1]);return Number.isInteger(e)&&(port=e),1},cmdline.dir=function(n){var e=process.argv[n+1];return userdir=e,1},cmdline.cwd=function(n){var e=process.argv[n+1];return cwd=e,1},cmdline.mode=function(n){var e=process.argv[n];return-1==(e=e.toLowerCase()).indexOf("https")&&(mode="http"),0},cmdline.version=function(n){console.log(),console.log(program.name,program.version),console.log(),process.exit()},cmdline.cert=function(n){return loadcert(n),2},cmdline.help=function(n){help(),process.exit()},cmdline();var c,sep=-1!=cwd.indexOf("/")?"/":"\\",i=cwd.length-1;("/"!==(c=cwd[i])&&"\\"!==c&&(cwd+=sep),userdir)&&("/"!==(c=userdir.slice(-1))&&"\\"!==c&&(userdir+=sep));function listen(n,e){server.listen(port),console.log("   ","generate key/certificate    https://javascript-2020.github.io/utils/generate-https-certificate/generate-https-certificate.html"),console.log(),console.log("   ","source code                 https://github.com/javascript-2020/code-projects/tree/main/http-file-upload/"),console.log(),console.log("   ","feedback can be provided    https://chat.stackoverflow.com/rooms/17/javascript   user: @matt"),console.log("   ","email                       javascript.12.03.2024@gmail.com"),console.log(),console.log("   ","online help                 https://javascript-2020.github.io/code-projects/http-file-upload/http-file-upload.html"),console.log("   ","quick help                  http-file-upload -help"),console.log(),console.log("   ","quit                        escape | q | ctrl-c"),console.log("   ",`server listening            ${mode} , all interfaces , port ${port}`),console.log();var o=require("os").networkInterfaces();for(var t in o)o[t].forEach((n=>{"IPv4"===n.family&&console.log("   ",n.address)}));console.log()}function listening(){console.log("   ",`${mode}://localhost:${port}/`),console.log("   ",`serving directory           ${cwd+userdir}`),console.log()}function request(n,e){if(console.log(n.url),e.setHeader("access-control-allow-origin","*"),e.setHeader("access-control-allow-headers","content-type, content-length"),"OPTIONS"==n.method)return e.writeHead(204),void e.end();if(n.url.startsWith("/download?"))request.download(n,e);else if(n.url.startsWith("/upload"))request.upload(n,e);else if(n.url.startsWith("/docs"))request.docs(n,e);else switch(n.url){case"/":case"/hello":request.hello(n,e);break;case"/worker.js":request.worker(n,e);break;case"/cert":request.cert(n,e);break;case"/favicon.ico":request.favicon(n,e);break;case"/download-list":request.download.list(n,e);break;case"/help":request.help(n,e);break;case"/quit":request.quit(n,e);break;default:request.notfound(n,e)}}function upgrade(n,e,o){ws.upgrade.server(n,e,(function(n,e,o){var t=n.toString(),r=JSON.parse(t);switch(r.type){case"upload":upload(o,r);break;case"download":download(o,r);break;case"download-list":download.list(o,r)}}))}function upload(n,e){var o,t,r,a;async function i(e){({name:t,num:r}=e),!1!==chk(n,t)&&(a=0,console.log("upload",t,r),!1!==(o=await open(n,t,"w"))&&n.send.json({type:"open"}))}async function s(){console.log("abort",t),await o.close(),del(n,t)}async function l(e){a++,console.log(t,a,"/",r),await write(n,o,e)&&(a===r&&o.close(),n.send.json({type:"progress"}))}n.onrec=function(n,e,o){switch(e){case"text":var t=n.toString(),r=JSON.parse(t);switch(r.type){case"upload":i(r);break;case"abort":s()}break;case"binary":l(n)}},i(e)}function download(n,e){console.log("download"),n.onrec=function(n,e,r){var a=n.toString(),i=JSON.parse(a);switch(i.type){case"download":t(i);break;case"progress":o.send();break;case"abort":o.abort()}};Buffer.alloc(chunksize);var o={};async function t(e){o.send=l,o.abort=function(){console.log("abort"),s.close()};var t=e.file;if(!1!==chk(n,t)){console.log("start",t);var r=await filesize(n,t);if(!1!==r){var a=Math.ceil(r/chunksize);console.log(t,a);var i=0;n.send.json({type:"start",size:r,num:a});var s=await open(n,t,"r");!1!==s&&l()}}async function l(){i++,console.log(t,i,"/",a);var e=await read(n,s);e&&(n.send.binary(e),i===a&&s.close())}}t(e)}function chk(n,e){if(!1===chk.file(e))return n.send.json({type:"error",msg:"invalid filename"}),!1}async function open(n,e,o){try{var t=await fsp.open(userdir+e,o)}catch(n){console.log(n);var r=!1,a=n.toString()}return!1===r?(n.send.json({type:"error",msg:a}),!1):t}async function del(n,e){try{await fsp.unlink(userdir+e)}catch(n){console.log(n);n.toString()}}async function write(n,e,o){try{await e.write(o)}catch(n){console.log(n);var t=!1,r=n.toString()}return!1!==t||(n.send.json({type:"error",msg:r}),!1)}async function filesize(n,e){try{var o=await fsp.stat(userdir+e)}catch(n){console.log(n);var t=!1,r=n.toString()}return!1===t?(n.send.json({type:"error",msg:r}),!1):o.size}async function read(n,e){var o=Buffer.alloc(chunksize);try{var{bytesRead:t}=await e.read(o,{length:chunksize})}catch(n){console.log(n);var r=!1,a=n.toString()}if(!1!==r){if(t!==chunksize){var i=Buffer.alloc(t);o.copy(i,0,0,t),o=i}return o}n.send.json({type:"error",msg:a})}function title(n){"win32"===process.platform?process.title=n:process.stdout.write("]2;"+n+"\\")}function createdir(){if(!userdir)return!0;var n=fs.statSync(cwd+userdir,{throwIfNoEntry:!1});if(!n){try{fs.mkdirSync(cwd+userdir)}catch(n){var e=n}return!e||(console.log("unable to create upload directory"),console.log(cwd+userdir),console.error(e),!1)}return!!n.isDirectory()||(console.error("invalid upload directory"),console.log(cwd+userdir),!1)}function loadcert(n){var e={};function o(n){console.log(process.cwd()),n||(console.log("-cert requires two files, pem encoded key and certificate"),process.exit());try{var o=fs.readFileSync(n,"utf8")}catch(n){console.log("-cert requires two files, pem encoded key and certificate"),console.error(n),process.exit()}var t=!1;iscert(o)&&(t=!0,e.cert=o),iskey(o)&&(t=!0,e.key=o),t||(console.error(n,"not recognised as pem encoded key or certificate"),process.exit())}o(process.argv[n+1]),o(process.argv[n+2]),e.cert||(console.log("-cert certificate not found"),process.exit()),e.key||(console.log("-cert key not found"),process.exit()),cert=e.cert,key=e.key,function(){var n,e,o=fs.readFileSync(__filename,"utf8"),t=o.indexOf("//certificate:");n=o.indexOf("key =",t),n=o.indexOf("`",n),e=o.indexOf("`",n+1),o=o.slice(0,n+1)+"\n"+key+o.slice(e),n=o.indexOf("cert =",t),n=o.indexOf("`",n),e=o.indexOf("`",n+1),o=o.slice(0,n+1)+"\n"+cert+o.slice(e),fs.writeFileSync(__filename,o)}()}function iskey(n){return-1!=n.indexOf("-----BEGIN RSA PRIVATE KEY-----")}function iscert(n){return-1!=n.indexOf("-----BEGIN CERTIFICATE-----")}function help(){console.log(),console.log(program.name,program.version),console.log();for(var n=["-p ","<port>","set the port that http-file-upload listens on, default 3000","-cwd ","<directory>","set the working directory for http-file-upload, default current directory","-d ","<directory>","set the upload/download directory, relative to cwd, default not used","-https|-http ","","set whether http-file-upload uses http or https, default https","-cert ","<key-file> <cert-file>","update the key/certificate used by the server, will write to file","-v ","","display current version","-h ","","display this help dialogue"],e=[0,0],o=n.length,t=0;t<o;t+=3)n[t].length>e[0]&&(e[0]=n[t].length),n[t+1].length>e[1]&&(e[1]=n[t+1].length);for(o=n.length,t=0;t<o;t+=3)console.log(n[t].padStart(e[0]+3),n[t+1].padEnd(e[1]+3),n[t+2]);console.log()}function wsmod(){var n={},e=require("crypto"),o=require("http"),t=require("https");const r=Math.pow(2,16);Math.pow(2,64);var a={},i={};n.frame=i;var s={},l={},c={},d={},u={},p={};function f(n,e,o,t){var r=function(n,e,o,t){var r={};g.push(r),r.onrec=e,r.onerror=o,r.onclose=t,r.websocket=n,r.buffer=Buffer.alloc(0),r.send={};var a={buffer:null,type:null,add:function(n){var e=s.payload(n);a.buffer=Buffer.concat([a.buffer,e]),s.fin(n)&&(i(a.buffer,a.type,r),a.reset())},reset:function(){a.buffer=Buffer.alloc(0),a.type=null}};function i(n,e,o){c("rec",n,e,o)}r.payload=a,a.reset(),r.send.frame=function(n){return w(r,n)},r.send.text=function(n){return w.text(r,n)},r.send.binary=function(n){return w.binary(r,n)},r.send.ping=function(){return w.ping(r)},r.send.json=function(n){var e=JSON.stringify(n);return w.text(r,e)},r.close=function(){h(r)},n.on("error",(n=>{c("error",n,r)})),n.on("close",(()=>{c("close",r)}));var l={};function c(n){var e=Array.prototype.slice.call(arguments,1);function o(n){"function"==typeof n&&n.apply(null,e)}o(r["on"+n]),l[n].forEach((n=>n.apply(null,e)))}return l.rec=[],l.error=[],l.close=[],r.on=function(n,e){if("function"==typeof e){var o=l[n];o&&o.push(e)}},r.remove=function(n,e){var o=l[n];if(o)for(var t=o.length,r=0;r<t;r++){if(o[r]===e)return void o.splice(r,1)}},r}(n,e,o,t);return n.on("data",(n=>m(r,n))),n.on("error",(n=>{console.log("error"),console.error(n),"function"==typeof o&&o(n)})),n.on("close",(()=>{g.remove(r),console.log("closed"),"function"==typeof t&&t()})),r}n.client=function(n,e,o,t,r){if(!r)var a=new Promise((n=>r=n));return v.client(n,(function(n,a){if(null===n)return void r(null);var i=f(n,e,o,t);m(i,a),r(i)})),a},n.upgrade=function(n,e,o,t){return f(n,e,o,t)},n.upgrade.server=function(n,e,o,t,r){return f.server(n,e,o,t,r)},f.server=function(n,e,o,t,r){return console.log("upgrade"),"websocket"!==n.headers.upgrade.toLowerCase()?(console.log(n.url,"bad request",n.headers.upgrade),void e.end("HTTP/1.1 400 Bad Request")):(v(n,e),f(e,o,t,r))},n.upgrade.deny=function(n){return f.deny(n)},f.deny=function(n){n.end("HTTP/1.1 401 Web Socket Protocol Handshake\r\nUpgrade: WebSocket\r\nConnection: Upgrade\r\n\r\n"),n.destroy()};var g=[];function v(n,o){var t=["HTTP/1.1 101 Web Socket Protocol Handshake","Upgrade: WebSocket","Connection: Upgrade"],r=n.headers["sec-websocket-key"];if(r){var a=function(n){var o=e.createHash("sha1").update(n+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11","binary");return o.digest("base64")}(r);t.push("Sec-WebSocket-Accept: "+a)}t=t.join("\r\n"),t+="\r\n\r\n",o.write(t)}function m(n,e){if(0!==e.length)for(n.buffer=Buffer.concat([n.buffer,e]);i.available(n.buffer);){e=i.rd(n.buffer);switch(n.buffer=i.remove(n.buffer),s.opcode(e)){case 0:a.continuation(n,e);break;case 1:a.text(n,e);break;case 2:a.binary(n,e);break;case 8:a.close(n);break;case 9:a.ping(n);break;case 10:a.pong(n)}}}function h(n){w.close(n),n.websocket.destroy(),g.remove(n)}function w(n,e){n.websocket.write(e)}return n.list=g,g.remove=function(n){for(var e=g.length,o=0;o<e;o++)if(g[o]===n)return void g.splice(o,1)},g.closeall=function(){for(var n=g.length-1;n>=0;n--){h(g[n])}},v.client=function(n,r){if(!r)var a=new Promise(((n,e)=>r=n));for(var i="wss:"===(n=new URL(n)).protocol,s=n.hostname,l=n.port,c=n.pathname+n.search,d=Buffer.alloc(16),u=0;u<16;u++)d[u]=Math.round(255*Math.random());var p=d.toString("base64"),f={hostname:s,port:l,method:"GET",path:c,headers:{Upgrade:"websocket",Connection:"Upgrade","Sec-WebSocket-Version":13,"Sec-WebSocket-Key":p,Host:s+":"+l},rejectUnauthorized:!1},g=(i?t:o).request(f);return g.on("error",(n=>console.log(n))),g.on("upgrade",(function(n,o,t){if(!1===function(n){if("websocket"!==n.headers.upgrade.toLowerCase())return console.log("validate upgrade failed - response.headers.upgrade : "+n.headers.upgrade),n.socket.end(),!1;var o=e.createHash("sha1");o.update(p+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11");var t=o.digest("base64");if(n.headers["sec-websocket-accept"]!==t)return console.log("validate upgrade failed - invalid key : "+n.headers["sec-websocket-accept"]),n.socket.end(),!1}(n))return void r(null);console.log("upgrade ok"),r(o,t)})),g.on("response",(function(n){console.log("upgrade failed, response"),n.socket.end()})),g.end(),a},a.continuation=function(n,e){null!==n.payload.type?n.payload.add(e):h(n)},a.text=function(n,e){null===n.payload.type?(n.payload.type="text",n.payload.add(e)):h(n)},a.binary=function(n,e){null===n.payload.type?(n.payload.type="binary",n.payload.add(e)):h(n)},a.close=function(n,e){h(n)},a.ping=function(n){console.log("ping received"),w(n,i.create.pong())},a.pong=function(){console.log("pong received")},w.text=function(n,e){var o=Buffer.from(e,"utf8");w(n,i.create(1,1,!1,o))},w.binary=function(n,e){w(n,i.create(1,2,!1,e))},w.ping=function(n){w(n,i.create.ping())},w.close=function(n){w(n,i.create.close())},i.rd=function(n){var e=c.endframe(n),o=Buffer.alloc(e);return n.copy(o,0,0,e),o},i.create=function(n,e,o,t){t=t||Buffer.alloc(0);var r=i.size(t,o),a=Buffer.alloc(r);if(l.fin(a,n),l.opcode(a,e),l.ismasked(a,o),o){var s=d.create();l.mask(a,s),d.mask(t,s)}return l.paylen(a,t.length),l.payload(a,t),a},i.remove=function(n){var e=c.endframe(n),o=n.length-e,t=Buffer.alloc(o);return n.copy(t,0,e),t},i.size=function(n,e){var o=n.length;return 2+u.len(o)+(e?4:0)+o},i.available=function(n){if(n.length<2)return!1;var e=s.ismasked(n),o=s.payloadlength(n),t=2+u.paylen(o);if(n.length<t)return!1;var r=t+(e?4:0)+s.paylen(n);return!(n.length<r)},i.create.binary=function(n){return i.create(1,2,0,n)},i.create.text=function(n){var e=Buffer.from(n,"utf8");return i.create(1,1,0,e)},i.create.close=function(){return i.create(1,8)},i.create.ping=function(){return i.create(1,9)},i.create.pong=function(){return i.create(1,10)},s.fin=function(n){var e=n.readUInt8(0);return p.rd(e,7)},s.opcode=function(n){return 15&n.readUInt8(0)},s.ismasked=function(n){var e=n.readUInt8(1);return p.rd(e,7)},s.payloadlength=function(n){return 127&n.readUInt8(1)},s.extpaylen=function(n){var e,o=s.payloadlength(n);return o<126?null:126===o?e=n.readUInt16BE(2):(e=n.readBigUInt64BE(2),e=Number(e))},s.paylen=function(n){var e=s.payloadlength(n);return e<126?e:e=s.extpaylen(n)},s.mask=function(n){var e=c.mask(n),o=Buffer.alloc(4);return n.copy(o,0,e,e+4),o},s.payload=function(n){var e=c.payload(n),o=s.paylen(n),t=Buffer.alloc(o);if(n.copy(t,0,e,e+o),s.ismasked(n)){var r=s.mask(n);d.mask(t,r)}return t},l.fin=function(n,e){var o=n.readUInt8(0);o=p.wt(o,7,e),n.writeUInt8(o,0)},l.opcode=function(n,e){for(var o=n.readUint8(0),t=0;t<4;t++){var r=p.rd(e,t);o=p.wt(o,t,r)}n.writeUInt8(o,0)},l.ismasked=function(n,e){var o=n.readUInt8(1);o=p.wt(o,7,e),n.writeUInt8(o,1)},l.payloadlength=function(n,e){var o=n.readUInt8(1);function t(e){for(var t=0;t<7;t++){var r=p.rd(e,t);o=p.wt(o,t,r)}n.writeUInt8(o,1)}t(e<126?e:e<r?126:127)},l.extpaylen=function(n,e){if(!(e<126))if(e<r)n.writeUInt16BE(e,2);else{var o=BigInt(e);n.writeBigUInt64BE(o,2)}},l.paylen=function(n,e){l.payloadlength(n,e),l.extpaylen(n,e)},l.mask=function(n,e){var o=c.mask(n);e.copy(n,o)},l.payload=function(n,e){var o=c.payload(n);e.copy(n,o)},c.mask=function(n){var e=s.payloadlength(n);return 2+u.paylen(e)},c.payload=function(n){return c.mask(n)+(s.ismasked(n)?4:0)},c.endframe=function(n){return c.payload(n)+s.paylen(n)},d.create=function(){for(var n=Buffer.alloc(4),e=0;e<4;e++){var o=Math.floor(256*Math.random());n.writeUInt8(o,e)}return n},d.mask=function(n,e){var o=n.length;for(let r=0;r<o;r++){var t=n.readUInt8(r);t^=e[3&r],n.writeUInt8(t,r)}},u.len=function(n){return n<126?0:n<r?2:8},u.paylen=function(n){return n<126?0:126===n?2:8},p.set=function(n,e){return n|1<<e},p.clear=function(n,e){return n&~(1<<e)},p.wt=function(n,e,o){return o?p.set(n,e):p.clear(n,e)},p.rd=function(n,e){return n>>e&1},n}console.clear(),title("http-file-upload : "+port),console.log(),console.log(program.name,program.version),console.log(),dns.lookup(os.hostname(),{family:4},listen),createdir()||process.exit(),(server="https"===mode?https.createServer({key:key,cert:cert}):http.createServer()).on("request",request),server.on("upgrade",upgrade),server.on("error",console.error),server.on("listening",listening),request.hello=function(n,e){e.writeHead(200,{"Content-Type":"text/html"}),e.end(txt.hello)},request.worker=function(n,e){e.writeHead(200,{"Content-Type":"text/javascript"}),e.end(txt.worker)},request.cert=function(n,e){e.setHeader("Content-Description","File Transfer"),e.setHeader("Content-Type","application/octet-stream"),e.setHeader("Content-Disposition","attachment; filename=cert.pem"),e.end(cert.trim())},request.favicon=function(n,e){var o=Buffer.from(img.favicon,"base64");e.writeHead(200,{"content-type":"image/png"}),e.end(o)},request.notfound=function(n,e,o){var t="not found";o&&(t+=" : "+o),e.writeHead(404,{"Content-Type":"text/html"}),e.end(t)},request.download=function(n,e){var o=n.url.slice(10);if(chk.file(o)){var t=fs.createReadStream(userdir+o);t.on("error",(()=>{request.notfound(n,e)})),e.setHeader("Content-Description","File Transfer"),e.setHeader("Content-Type","application/octet-stream"),e.setHeader("Content-Disposition",`attachment; filename=${o}`),t.pipe(e)}else request.notfound(n,e,o)},request.download.list=async function(n,e){try{var o=await fsp.readdir(cwd+userdir,{withFileTypes:!0})}catch(n){var t=!1,r=n.toString()}if(!1!==t){var a=[];o.forEach((n=>n.isFile()&&a.push(n.name))),e.end(JSON.stringify({files:a}))}else e.end(JSON.stringify({error:r}))},request.upload=function(n,e){var o=n.url.slice(8);if(!o)return e.writeHead(500),void e.end("no filename");var t=fs.createWriteStream(o);t.on("error",(()=>{e.writeHead(500),e.end("create write stream error : "+o)})),n.pipe(t),n.on("end",(()=>{e.writeHead(200),e.end("ok")}))},request.docs=function(n,e){var o=__dirname+n.url;if(fs.existsSync(o)){var t=fs.readFileSync(o);e.writeHead(200,{"content-type":"text/html"}),e.end(t)}else request.notfound(n,e)},request.help=function(n,e){var o;try{fs.statSync(__dirname+"/docs/http-file-upload.html")}catch(n){console.log(n),o=!0}o?e.end("not found"):e.end("ok")},request.quit=function(n,e){e.end(),process.exit()},download.list=async function(n,e){console.log("list");try{var o=await fsp.readdir(cwd+userdir,{withFileTypes:!0})}catch(n){var t=!1,r=n.toString()}if(!1!==t){var a=[];o.forEach((n=>{n.isFile()&&a.push(n.name)})),n.send.json({type:"list",files:a})}else n.send.json({type:"error",msg:r})},chk.file=function(n){var e=path.resolve(userdir);return e===path.resolve(userdir,n).substring(0,e.length)},function(){if(process.stdin.isTTY){process.stdout.setEncoding("utf8"),process.stdin.setRawMode(!0),process.stdin.resume(),process.stdin.setEncoding("utf8");var n=String.fromCharCode(27);process.stdin.on("data",(e=>{""!==e&&"q"!==e&&e!==n||process.exit()}))}}(),txt.hello=`\n<meta name="viewport" content="width=device-width, initial-scale=1" />\n<title>http-file-upload</title>\n<style>\n      html,body {\n            height            : calc(100%-60px);\n      }\n      body {\n            font-family       : arial;\n            font-size         : 18px;\n            margin            : 30px;\n      }\n      .box {\n            display           : flex;\n            flex-flow         : column;\n            height            : 100%;\n      }\n      .box>div:first-of-type {\n            flex              : 0 1 auto;\n      }\n      .box>div:nth-of-type(2) {\n            flex              : 1 1 auto;\n      }\n      \n      h3 {\n            margin-top        : 0;\n      }\n      h3>div:first-of-type {\n            margin-bottom     : 10px;\n      }\n      h3>div:nth-of-type(2) {\n            color             : blue;\n      }\n      .zone {\n            position          : relative;\n            margin            : 20px auto;\n            padding           : 20px;\n            border            : 3px dashed lightblue;\n            border-radius     : 5px;\n            height            : 150px;\n      }\n      .zone>* {\n            margin            : 10px;\n      }\n      #output {\n            line-height       : 24px;\n            flex              : 1;\n            border            : 1px solid lightgray;\n            overflow          : auto;\n            padding           : 20px;\n      }\n      input {\n            font-size         : 18px;\n            cursor            : pointer;\n      }\n      \n/*\n  page-hdr\n*/\n\n      #hdr {\n        font-family:arial;\n        position:relative;\n        margin-bottom:20px;\n      }\n      .icon {\n        display:inline-block;\n        text-align:center;\n        background:whitesmoke;\n        border-radius:5px;\n        border:1px solid lightgray;\n        padding:5px 3px 0px;\n        cursor:pointer;\n        margin-right:10px;\n        min-width:50px;\n      }\n      .icon-label {\n        margin:2px 0 0;\n      }\n      #title {\n        text-align:center;\n        position:absolute;\n        left:0;\n        right:0;\n        top:5px;\n        z-index:-1;\n      }\n      \n</style>\n\n<page-hdr>\n    <section id="hdr" style="margin-top: 0px;">\n        <div id="github" onclick="window.location='https://github.com/javascript-2020/code-projects/tree/main/http-file-upload/'" class="icon">\n            <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAbFJREFUOE/l1E2IT2EUx/HPIFI2lJfGwmYsxFZKEUWjKWM3pZSpKcqeBcpLZsNeUeNlZWdBSVFmkAXbmRWbWRCRjYW8jv+pc/V4utff3q3bvfc5z/k+557zO2dA97UHy7ERx3LbZbzEZzxscx3o4L3GWizusP/AO6yv7TXwAo5g9V8iL03vcRWnm8USeBHH/xFUb7uEE7HYAOMZeVmGm7iN61jVccBHjOMQxvAl870QoIDMZ87CfxR3E7QDTyroTjzOtf24k++R0w0BHMY9LEpDCeyXgRL4EyMBjEKcSs+o3r4uSbTQQ1r3CzVMBnCh2PimTQp9wgyJDZZV/g+Bz7E1c/AdezHdr7xp34UHvcIuye8XUZSy9A1nDT5UBSvPCL9oz9BeeY2GYR3msBIHsi834WlPTt9yrXQKIUdEIfoVhSG6Z3PTejdwGG+xDc9SPud7UZ6popjEyZaURMuOl8NhKvtzpKf4yGvMwk95l/5xwNkKeA0T5XCI96EcRbsLh5mWAtXARznyXtXA5tD4/YNYmuBzVTQN8Ctu5V/93tI1sbdjS463KxXwaFZ/NnP9h/kXPcNYElddRc0AAAAASUVORK5CYII=" />\n            <div class="icon-label">\ngithub\n            </div>\n        </div>\n        <div id="npm" onclick="window.location='https://www.npmjs.com/package/http-file-upload'" class="icon">\n            <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAALxJREFUOE9jPKio/p+BioCR9gb+J9HBjIwo/kNxIauwMAOvjjZJAfD5ylWG32/fwvUgDPz/n0HIwZ5BZ/4skgy8kpjG8O7AQQYGqEsHxsDfb94y3G3tYPjz4SPY9bz6ugzyBblgNlkuBBl4xtOP4febNwwMoKBxdIAHDWUGggIfLaxHDWQgKtnAI2U0DPEnbAYGBlyFw78/vxk+HD0Oz+PI6nAXDjDl2IovtCIKrBSmDl/xRVIxg0Mx1UtsAMPADMS87x0xAAAAAElFTkSuQmCC" />\n            <div class="icon-label">\nnpm\n            </div>\n        </div>\n        <div id="help" class="icon">\n            <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAAAXNSR0IArs4c6QAAAtBJREFUOE+tlN+LVVUUx7/ftfc55947P2KsqUSMqaceEpEginnqIaGgnAJJoogsfAjKwRB8KLlQT/nQEBFBUtpTWIQD/QFC4ItG0UQE0kuQaITdYLw/zjl7feVcJ5yBppBcL3uzWHz2Wt+91uKf+7pzU756WMln4YK7iJswixRBwHC5amXHWD5z6OOY6ldugrFpqOfxA472HFzOoKduCTDLTrFaWDwd5HtAONTkDoBUcwhKBH3tsQxS4w/XY+AAa0FNdGw83gDHGRKPObliwlbIM5ldRKOKhZVEr00kpUed+i4kf1JCgPEHWTgHkuZ+D9znE+3rNaDmqzw/Hur0sMk7KcazJDKP9qMNq4cATBJ8oG7lR2J/eIpS5hY+8zyct6SdyMJZG46Ols7zY2CE5utW/l50zcK97dF+g3S7m10MZbVgjgcF/lVNtl7IVwenIeUe7KQb+8H1dN3J3siu1m+Wrp9uAIv8mBkDEgoQV4G0TTF8yzrtCmXaL7DcCAyf1BP5siXdyzrdFcr6QCWeWQ/8MCTfdb1kOwegDbIXRvXzkOZEu9IAs9XBclOyQjjhxt9DSnvhmgFwWwX76gYwzz8KKT0CaVJZ+AZodLIzTNhGskVPewdTxUtFv9pN15wF9pT8TqvSfkhF8/EV7Mu1PsQTMl4h1IHLYLY6bg3jr4D6zdWTdpjZBVBDOKZFbIEwQfct4ybaCLw1jV02GZYLi59G+Yt/v/I/JkYjC8dZPXt4dyir11NK7fUwM9tK6T4AY33W2QjkL8n90nonGQZ1EZeobtf++Ll/tw8HG4BTMewsaj9KYcd4asajCAf5fR3i271UrmxIoNUe3HF/59Kmq+rC468V29vx5cxTl8Ls2vxerkN8K7Ppk/yiW/6TPP+6+3rPHZnpDAbd6HoVhErj+4NpvjNzYqm3mdb/uUz7+w5ttwqL8JRSUSxNfP5uszg2tWvOIHGsL8FMpQAAAABJRU5ErkJggg==" />\n            <div class="icon-label">\nhelp\n            </div>\n        </div>\n        <div id="quit" onclick="window.location='https://javascript-2020.github.io/code-projects/http-file-upload/http-file-upload.html'" class="icon" style="float: right;">\n            <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAAAXNSR0IArs4c6QAAAwpJREFUOE+tk1tIVFEUhv+9z8ycOTOOaamoYZoVRhAqXl4ioqKLTc5YCFnRBQqqKSuEwpdiKAkqAokwsKB7pFk0hVYYEWjQ/aKhUWkX1NQSLZ05c86Zs3fM+GLhWEL7abNY69tr/f/apL+oNMWq+vbwAIvlHGCcEYzjCJRySgEd6OE261Hid24/5X1+d/M4GGFTrdlLTxC/fZvH+7re8T+Atpy8aqLYXTeGmuqdJNIGOmkiuM7Av/eBe72gkxPAh7zgPwdBbBEgUVHgvd9AJkYDug4SYQXIsEJc9sOamFFN/PbtHvlbk8PkXA5isYQSWEcntHv3Ia5ZhUBTM7SGhzDmZMO4aAGUS1UwLc+D3v4JQnIS6JQksO4e6B/aYOwVrhLFucujpUU4jAvnQzl/ETQhAcZ5c6FcuAxx/VpoDY1QqmpgWrYE5g3r4DtwCGbXFijV10BEEeK61VCvexB48RKmCTNriFJY4uEFuQ7W3w/58DEIqVMh7XJBrbsL0wpnWKD/ZCW4LEPaWwJ/RSUCz19Cylg8DGT2TEdQE5+7DELaDJh3bIV6sw7iCge0R4+hXK6GqSAf4kpn6NFgh+GBzmKPmkgc5s0boT1oAI2NCYkuV1TCtGghDFmZUGtvQ8y3I/CmBeqt2rGBwbXxvWt0GHKyIEyfBmK1QMjKhB4srqoJ3WlcLFhfH7Q790JuG3JzEHjyFNA0GOfOgfbkGVjX1+GRR9tDOjkRNHkK9JZW8IEf/7yiYYH/TPgjcdgUu+uM9839DVwPjOsP//koEQxcnL3gNPEXFi9WWp/t1OQhaWQSFYQEKHIqZ7o4Mk6ooECU2piud4+MC1KELKVllxPudtPvb33xzC//BrQMDaQHut7tZ4P9s8EZDRVTgRmi4l6R+OSDsiW6+bcGzJIcM9PSHXbM93nFYtxg5ybW89HN5MHYYLEg2XqMqen7LJEp58hVtzqa1mPqNrCmNFr48t6tfm52EYDT5FnH+fSUsuiz5QPhjPurEb6ikiSto30351w3JqWVW68c6RprC34BWGtqIrKMpd8AAAAASUVORK5CYII=" />\n            <div class="icon-label">\nquit\n            </div>\n        </div>\n        \n        <h3 style="color: blue; font-weight: bold;" id="title">\nhttp-file-upload v2.0\n        </h3>\n    </section>\n</page-hdr>\n\n<div class=box>\n      <div>\n            <div class=zone ondrop='upload.drop(event)' ondragenter='upload.drop.enter(event)' ondragover='upload.drop.enter(event)' ondragleave='upload.drop.leave(event)'>\n                  <span>drag files here</span>\n                  <span>or</span>\n                  <input value='select files' type=button onclick='upload.fileinput(event)'>\n                  <span>or</span>\n                  <input value='download' type=button onclick='download.read()'>\n            </div>\n            <input value=clear type=button onclick='log.clear()'>\n            <br>\n            <br>\n      </div>\n      <div id=output></div>\n</div>\n\n<script>\n\n        console.clear();\n        console.log('http-file-upload v2.0');\n        console.log();\n        \n        var websocketurl    = 'ws'+(self.location.protocol==='https:'?'s':'')+'://'+self.location.host;\n        console.log('websocket url :',websocketurl);\n        var remote_dir      = '${cwd.replaceAll("\\"," \\\\ ")}';\n        \n        log('remote dir : '+remote_dir);\n        log();\n        log('ready');\n        log();\n        \n        \n        var upload      = uploadmod();\n        var download    = downloadmod();\n        \n        document.querySelector('page-hdr #help').onclick    = async e=>{\n        \n                                                                    var txt   = await fetch('/help').then(res=>res.text());\n                                                                    if(txt=='ok'){\n                                                                          window.location   = '/docs/http-file-upload.html';\n                                                                    }else{\n                                                                          window.location   = 'https://javascript-2020.github.io/code-projects/http-file-upload/http-file-upload.html';\n                                                                    }\n                                                                    \n                                                              };\n                                                              \n        document.querySelector('page-hdr #quit').onclick    = e=>{\n        \n                                                              fetch('/quit');\n                                                              log('quit.');\n                                                              \n                                                        };\n                                                        \n  //:\n  \n  \n  \nfunction uploadmod(){\n\n  var obj   = {};\n  \n  \n        var chunksize   = 1024*1024;\n        \n        \n        var create    = {};\n        \n        \n  //:\n  \n        obj.drop=function(e){console.log('drop');\n        \n              e.preventDefault();\n              e.target.style.borderColor    = '';\n              \n              var files   = e.dataTransfer.files;\n              upload(files,complete);\n              \n              function complete(){\n              \n                    console.log('done');\n                    \n              }//complete\n              \n        }//drop\n        \n        \n        obj.drop.enter=function(e){\n        \n              e.preventDefault();\n              e.target.style.borderColor    = 'cyan';\n              \n        }//drop.enter\n        \n        \n        obj.drop.leave=function(e){\n        \n              e.preventDefault();\n              e.target.style.borderColor    = '';\n              \n        }//drop.leave\n        \n        \n        obj.files=function(files,callback){\n        }//files\n        \n        \n        obj.fileinput=function(event){\n        \n              var input         = document.createElement('input');\n              input.type        = 'file';\n              input.multiple    = true;\n              input.onchange    = onchange;\n              input.click();\n              \n              function onchange(){\n              \n                    var files   = input.files;\n                    upload(files,complete);\n                    \n              }//onchange\n              \n              function complete(){\n              \n                    console.log('--- complete ---');\n                    \n              }//complete\n              \n        }//fileinput\n        \n        \n  //:\n  \n        function upload(files,callback){\n                                                                                //console.log('upload',files.length,'files');\n              var process   = create.process(files,callback);\n              \n              var n   = files.length;\n              for(var i=0;i<n;i++){\n              \n                    create.progress(process,files,i);\n                    \n              }//for\n              \n              process.upload();\n              \n        }//upload\n        \n        create.process=function(files,callback){\n        \n              var rec       = {};\n              var send      = {};\n              var worker    = create.worker(rec);\n              \n              var file;\n              var ct;\n              var num;\n              \n              var process   = {files,callback,cur:0,send};\n              \n              var root                                = Tuploadprocess.content.firstElementChild.cloneNode(true);\n              $(root,'#num').textContent              = files.length;\n              $(root,'[value="abort all"]').onclick   = abortall;\n              attachshadow(root);\n              \n              process.upload=function(){\n              \n                    file        = files[process.cur];\n                    var size    = file.size;\n                    num         = Math.ceil(size/chunksize);\n                                                                                //console.log(file.name,size,num);\n                    worker.postMessage({type:'upload',name:file.name,num});\n                    ct          = 0;\n                    \n              }//upload\n              \n              rec.open=function(){\n                                                                                //console.log('rec.open');\n                    send.blob();\n                    \n              }//open\n              \n              rec.progress=function(){\n              \n                    if(file.status==='abort'){\n                          next();\n                          return;\n                    }\n                    \n                    ct++;\n                    var value   = Math.ceil(ct/num*100)+'%';\n                    var root    = file.progress;\n                    $(root,'#bar').style.width      = value;\n                    $(root,'#value').textContent    = value;\n                    \n                    if(ct<num){\n                          send.blob();\n                          return;\n                    }\n                    \n                    file.status                     = 'done';\n                    $(root,'#value').textContent    = 'done';\n                    next();\n                    \n              }//progress\n              \n              send.blob=function(){\n              \n                    var start   = ct*chunksize;\n                    var end     = start+chunksize;\n                    var blob    = file.slice(start,end);\n                    worker.postMessage(blob);\n                    \n              }//blob\n              \n              send.abort=function(){\n              \n                    worker.postMessage({type:'abort'});\n                    \n              }//abort\n              \n              rec.error=function(json){\n              \n                    var root                        = file.progress;\n                    $(root,'#value').textContent    = 'error';\n                    log('error');\n                    log(json.msg);\n                    log.spc();\n                    next();\n                    \n              }//error\n              \n              function next(){\n              \n                    var cur   = process.cur+1;\n                    for(cur;cur<files.length;cur++){\n                    \n                          if(files[cur].status==='pending'){\n                                process.cur   = cur;\n                                process.upload();\n                                return;\n                          }\n                          \n                    }//for\n                    \n                    complete();\n                    \n              }//next\n              \n              function abortall(){\n              \n                    if(process.cur===null){\n                          return;\n                    }\n                    \n                    send.abort();\n                    \n                    var i   = process.cur;\n                    for(i;i<files.length;i++){\n                    \n                          var file    = files[i];\n                          var root    = file.progress;\n                          $(root,'#value').textContent    = 'abort';\n                          file.status   = 'abort';\n                          \n                    }//for\n                    \n              }//abortall\n              \n              function complete(){\n              \n                    process.cur   = null;\n                    worker.terminate();\n                    \n                    if(typeof callback==='function'){\n                          callback();\n                          return;\n                    }\n                    \n              }//complete\n              \n              return process;\n              \n        }//process\n        \n        create.worker=function(rec){\n        \n              var worker                        = new Worker('worker.js');\n              worker.onmessage                  = onmsg;\n              worker.onerror                    = werror;\n              worker.postMessage({type:'init',url:websocketurl});\n              return worker;\n              \n              function onmsg(event){\n              \n                    var data    = event.data;\n                    var type    = datatype(data);\n                    switch(type){\n                    \n                      case 'string'   : var json    = JSON.parse(data);\n                                        rec[json.type](json);\n                                        break;\n                                        \n                    }//switch\n                    \n              }//onmsg\n              \n        }//worker\n        \n        create.progress=function(process,files,i){\n        \n              var file                          = files[i];\n              var root                          = Tuploadprogress.content.firstElementChild.cloneNode(true);\n              $(root,'#name').textContent       = file.name;\n              $(root,'#size').textContent       = hs(file.size);\n              $(root,'[value=abort]').onclick   = click;\n              attachshadow(root);\n              file.progress   = root;\n              file.status     = 'pending';\n              \n              function click(){\n              \n                    if(file.status!=='pending'){\n                          return;\n                    }\n                    $(root,'#value').textContent    = 'abort';\n                    file.status                     = 'abort';\n                    if(process.cur===i){\n                          process.send.abort();\n                    }\n                    \n              }//click\n              \n        }//progress\n        \n  //:\n  \n  \n  return obj;\n  \n//uploadmod:-\n}\n\n  //:\n  \nfunction downloadmod(){\n\n  var obj   = {};\n  \n        var create    = {};\n        \n  //:\n  \n        obj.files=function(files,callback){\n        \n              download(files,callback);\n              \n        }//files\n        \n        obj.list=function(){\n        \n              var files   = ['testX.tmp','test2.tmp'];\n              download(files,complete);\n              \n        }//list\n        \n        obj.prompt=function(){\n        \n              var filename    = getfilename();\n              var files       = [filename];\n              download(files,complete);\n              \n        }//prompt\n        \n        obj.read=function(){\n        \n              var hldr                                    = document.createElement('div');\n              var root                                    = Tdownloadlist.content.firstElementChild.cloneNode(true);\n              $(root,'[value=cancel]').onclick            = cancel;\n              $(root,'[value=ok]').onclick                = ok;\n              var list                                    = $(root,'#list');\n              var item                                    = $(root,'.item');\n              item.remove();\n              hldr.attachShadow({mode:'open'}).append(root);\n              document.body.append(hldr);\n              \n              var worker                        = new Worker('worker.js');\n              worker.onmessage                  = onmsg;\n              worker.onerror                    = werror;\n              worker.postMessage({type:'init',url:websocketurl});\n              worker.postMessage({type:'download-list'});\n              return worker;\n              \n              function onmsg(event){\n                                                                                //console.log('onmsg',event.data);\n                    var json    = JSON.parse(event.data);\n                    \n                    if(json.type==='error'){\n                          hldr.remove();\n                          log('directory read error');\n                          log(json.msg);\n                          log.spc();\n                          return;\n                    }\n                    \n                    var files   = json.files;\n                    files.forEach(name=>{\n                    \n                          var nitem   = item.cloneNode(true);\n                          var input   = $(nitem,'input');\n                          nitem.onclick   = e=>{if(e.target!==input)input.checked    = !input.checked;};\n                          $(nitem,'.name').textContent   = name;\n                          list.append(nitem);\n                          \n                    });\n                    \n              }//onmsg\n              \n              function cancel(){\n              \n                    hldr.remove();\n                    \n              }//cancel\n              \n              function ok(){\n              \n                    var files   = [];\n                    var items   = list.querySelectorAll('.item');\n                    items.forEach(item=>{\n                    \n                          var input   = $(item,'input');\n                          if(input.checked){\n                                var name    = $(item,'.name').textContent;\n                                files.push(name);\n                          }\n                          \n                    });\n                    hldr.remove();\n                    \n                    download(files,complete);\n                    \n              }//ok\n              \n        }//list\n        \n        \n        function complete(){\n        \n              console.log('--- complete ---');\n              \n        }//complete\n        \n        \n        \n        \n  //:\n  \n        function download(files,callback){\n        \n              var process   = create.process(callback,files);\n              \n              var n           = files.length;\n              for(var i=0;i<n;i++){\n              \n                    create.progress(process,files,i);\n                    \n              }//for\n              \n              process.download();\n              \n        }//download\n        \n        create.process=function(callback,files){\n        \n              var root                                = Tdownloadprocess.content.firstElementChild.cloneNode(true);\n              var n                                   = files.length;\n              $(root,'#num').textContent              = n;\n              $(root,'[value="abort all"]').onclick   = abortall;\n              attachshadow(root);\n              \n              var cur                                 = 0;\n              var ct                                  = 0;\n              var num;\n              var rec                                 = {};\n              var send                                = {};\n              var worker                              = create.worker(rec);\n              \n              send.download     = function(file){\n              \n                    process.list[cur].status    = 'downloading';\n                    var file    = files[cur];\n                    worker.postMessage({type:'download',file});\n                    \n              };\n              send.progress     = function(){worker.postMessage({type:'progress'})}\n              send.abort        = function(){worker.postMessage({type:'abort'})}\n              \n              var process   = {\n                    download    : send.download,\n                    abort       : send.abort,\n                    list        : [],\n                    cur         : 0,\n              };\n              \n              \n              rec.start=function(json){\n              \n                    num         = json.num;\n                    var size    = json.size;\n                    var root    = process.list[cur].root;\n                    $(root,'#size').textContent   = hs(size);\n                                                                                //console.log('start',num,size);\n              }//start\n              \n              rec.blob=function(blob){\n                                                                                //console.log('rec.blob');\n                    if(process.list[cur].status==='abort'){\n                          next();\n                          return;\n                    }\n                    \n                    ct++;\n                    process.list[cur].blob.push(blob);\n                    \n                    var root    = process.list[cur].root;\n                    var value   = Math.ceil(ct/num*100)+'%';\n                    $(root,'#bar').style.width      = value;\n                    $(root,'#value').textContent    = value;\n                    \n                    if(ct<num){\n                          send.progress();\n                    }else{\n                          process.list[cur].status    = 'done';\n                          process.list[cur].blob      = new Blob(process.list[cur].blob);\n                          \n                          var root    = process.list[cur].root;\n                          $(root,'#value').textContent    = 'done';\n                          $(root,'input').value           = 'download';\n                          next();\n                    }\n                    \n              }//blob\n              \n              rec.error=function(json){\n              \n                    var msg         = json.msg;\n                    var filename    = files[cur];\n                    var root        = process.list[cur].root;\n                    $(root,'#value').textContent   = 'error';\n                    log('error',filename);\n                    log(msg);\n                    log.spc();\n                    next();\n                    \n              }//error\n              \n              function next(){\n              \n                    for(cur++;cur<files.length;cur++){\n                    \n                          if(process.list[cur].status==='pending'){\n                                process.cur   = cur;\n                                ct            = 0;\n                                process.download();\n                                return;\n                          }\n                          \n                    }//for\n                    \n                    complete();\n                    \n              }//next\n              \n              function abortall(){\n              \n                    if(cur===null){\n                          return;\n                    }\n                    \n                    send.abort();\n                    \n                    for(var i=cur;i<files.length;i++){\n                    \n                          var o   = process.list[i];\n                          o.status                          = 'abort';\n                          $(o.root,'#value').textContent    = 'aborted';\n                          \n                    }//for\n                    \n              }//abortall\n              \n              function complete(){\n              \n                    cur   = null;\n                    worker.terminate();\n                    \n                    if(typeof callback==='function'){\n                          callback();\n                    }\n                    \n              }//complete\n              \n              return process;\n              \n        }//process\n        \n        \n        create.worker=function(rec){\n        \n              var worker                        = new Worker('worker.js');\n              worker.onmessage                  = onmsg;\n              worker.onerror                    = werror;\n              worker.postMessage({type:'init',url:websocketurl});\n              return worker;\n              \n              function onmsg(event){\n                                                                                //console.log('onmsg',event.data);\n                    var data    = event.data;\n                    var type    = datatype(data);\n                    switch(type){\n                    \n                      case 'string'   : var json    = JSON.parse(data);\n                                        rec[json.type](json);\n                                        break;\n                                        \n                      case 'blob'     : rec.blob(data);     break;\n                      \n                    }//switch\n                    \n              }//onmsg\n              \n        }//worker\n        \n        \n        create.progress=function(process,files,index){\n        \n              var filename                  = files[index];\n              \n              var root                      = Tdownloadprogress.content.firstElementChild.cloneNode(true);\n              var btn                       = $(root,'input');\n              btn.onclick                   = click;\n              $(root,'#name').textContent   = filename;\n              attachshadow(root);\n              \n              var o   = {\n                    root,\n                    status    : 'pending',\n                    blob      : []\n              };\n              \n              process.list.push(o);\n              \n              function click(){\n              \n                    if(o.status==='done'){\n                          var url       = window.URL.createObjectURL(o.blob);\n                          var a         = document.createElement('a');\n                          a.href        = url;\n                          a.download    = filename;\n                          a.click();\n                          return;\n                    }\n                    \n                    if(index===process.cur){\n                          process.abort();\n                    }\n                    \n                    o.status                        = 'abort';\n                    $(root,'#value').textContent    = 'aborted';\n                    \n              }//click\n              \n        }//progress\n        \n  //:\n  \n  return obj;\n  \n//downloadmod:-\n}\n\n\n  //:\n  \n        function attachshadow(root){\n        \n                var div   = document.createElement('div');\n                div.attachShadow({mode:'open'}).append(root);\n                output.append(div);\n                \n        }//attachshadow\n        \n        function log(){\n        \n              var str   = Array.prototype.join.call(arguments,' ');\n              var div   = document.createElement('div');\n              div.textContent   = str;\n              div.innerHTML+='&nbsp;';\n              output.append(div);\n              output.scrollTop    = output.scrollHeight;\n              \n        }//log\n        \n        log.spc=function(){\n        \n              var div   = document.createElement('div');\n              div.style.height   = '10px';\n              output.append(div);\n              output.scrollTop    = output.scrollHeight;\n              \n        }//spc\n        \n        log.clear=function(){\n        \n              output.innerHTML    = '';\n              \n        }//clear\n        \n        function $(root,selector){\n        \n              var node    = root.querySelector(selector);\n              return node;\n              \n        }//$\n        \n        function hs(bytes){\n        \n              var thresh    = 1000;\n              var unit      = ['B','KB','MB','GB','TB','PB','EB','ZB','YB'];\n              for(var i=0;i<unit.length;i++){\n              \n                    if(bytes<thresh){\n                          var b   = bytes.toFixed(2);\n                          return b+' '+unit[i];\n                    }\n                    bytes  /= thresh;\n                    \n              }//for\n              \n        }//hs\n        \n        function werror(e){\n        \n              log('ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message);\n              \n        }//werror\n        \n        function getfilename(){\n        \n              var filename    = window.prompt('enter filename to download');\n              if(!filename){\n                    log('download cancelled');\n                    return;\n              }\n              return filename;\n              \n        }//getfilename\n        \n        function datatype(value){\n        \n              var str     = Object.prototype.toString.call(value);\n              var i       = str.indexOf(' ');\n              var type    = str.slice(i+1,-1);\n              type        = type.toLowerCase();\n              return type;\n              \n        }//datatype\n        \n        \n<\/script>\n\n\x3c!--\n  //:\n  //template-upload:\n--\x3e\n\n<template id=Tuploadprocess>\n      <div>\n            <style>\n                  #root {\n                        margin              : 10px auto;\n                        padding             : 10px 20px;\n                        background-color    : rgba(222,250,222,1);\n                        border-top          : 2px solid green;\n                  }\n                  #hdr {\n                        margin-right        : 20px;\n                  }\n                  input {\n                        font-size           : 18px;\n                        cursor              : pointer;\n                  }\n            </style>\n            <div id=root>\n                  <span id=hdr>\n                        uploading <span id=num>n</span> files\n                  </span>\n                  <input value='abort all' type=button>\n            </div>\n      </div>\n</template>\n\n<template id=Tuploadprogress>\n      <div>\n            <style>\n                  #name {\n                        color               : green;\n                        font-weight         : bold;\n                        margin              : auto 25px;\n                  }\n                  #size {\n                        color               : green;\n                        font-weight         : bold;\n                        margin              : auto 25px auto 10px;\n                  }\n                  #root {\n                        display             : flex;\n                        margin              : 10px auto;\n                  }\n                  #progress {\n                        flex                : 1;\n                        position            : relative;\n                        left                : 0;\n                        right               : 0;\n                        border              : 1px solid lightgray;\n                        text-align          : center;\n                        margin              : 5px 10px;\n                  }\n                  #bar {\n                        position            : absolute;\n                        left                : 0;\n                        top                 : 0;\n                        width               : 0%;\n                        height              : 100%;\n                        background-color    : lightblue;\n                        z-index             : -1;\n                  }\n                  #value {\n                  }\n                  input {\n                        font-size           : 18px;\n                        margin              : auto 10px;\n                        cursor              : pointer;\n                  }\n            </style>\n            <div>\n                  <div>\n                        <span id=name>name</span><span id=size>0.00 MB</span>\n                  </div>\n                  <div id=root>\n                        <input value=abort type=button>\n                        <div id=progress>\n                              <div id=bar>\n                              </div>\n                              <span id=value>0%</span>\n                        </div>\n                  </div>\n            </div>\n      </div>\n</template>\n\n\x3c!--\n  //:\n  //template-download:-\n--\x3e\n\n<template id=Tdownloadprocess>\n      <div>\n            <style>\n                  #root {\n                        margin              : 10px auto;\n                        padding             : 10px 20px;\n                        background-color    : rgba(222,250,222,1);\n                        border-top          : 2px solid green;\n                  }\n                  #hdr {\n                        margin-right        : 20px;\n                  }\n                  input {\n                        font-size           : 18px;\n                        cursor              : pointer;\n                  }\n            </style>\n            <div id=root>\n                  <span id=hdr>\n                        downloading <span id=num>n</span> files\n                  </span>\n                  <input value='abort all' type=button>\n            </div>\n      </div>\n</template>\n\n<template id=Tdownloadprogress>\n      <div>\n            <style>\n                  #name {\n                        color               : green;\n                        font-weight         : bold;\n                        margin              : auto 25px auto 10px;\n                  }\n                  #size {\n                        color               : green;\n                        font-weight         : bold;\n                        margin              : auto 25px auto 10px;\n                  }\n                  #root {\n                        display             : flex;\n                        margin              : 10px auto;\n                  }\n                  #progress {\n                        flex                : 1;\n                        position            : relative;\n                        left                : 0;\n                        right               : 0;\n                        border              : 1px solid lightgray;\n                        text-align          : center;\n                        margin              : 5px 10px;\n                  }\n                  #bar {\n                        position            : absolute;\n                        left                : 0;\n                        top                 : 0;\n                        width               : 0%;\n                        height              : 100%;\n                        background-color    : lightblue;\n                        z-index             : -1;\n                  }\n                  #value {\n                  }\n                  input {\n                        font-size           : 18px;\n                        margin              : auto 10px;\n                        cursor              : pointer;\n                  }\n            </style>\n            <div>\n                  <div>\n                        <span id=hdr>\n                              <span id=name>name</span><span id=size>---</span>\n                        </span>\n                  </div>\n                  <div id=root>\n                        <input value=abort type=button>\n                        <div id=progress>\n                              <div id=bar>\n                              </div>\n                              <span id=value>0%</span>\n                        </div>\n                  </div>\n            </div>\n      </div>\n</template>\n\n<template id=Tdownloadlist>\n      <div>\n            <style>\n                  #root {\n                        position    : fixed;\n                        left        : 0;\n                        top         : 0;\n                        width       : 100%;\n                        height      : 100%;\n                        box-sizing    : border-box;\n                        background-color    : white;\n                        border        : 1px solid blue;\n                        padding       : 10px;\n                  }\n                  [value=cancel] {\n                        margin    : auto 20px auto auto;\n                  }\n                  [value=ok] {\n                        margin    : auto 20px;\n                        padding-left   : 20px;\n                        padding-right   : 20px;\n                  }\n                  #list {\n                        margin-top      : 10px;\n                        overflow      : auto;\n                  }\n                  .item {\n                        margin    : 5px auto;\n                        cursor    : pointer;\n                  }\n                  .item:hover {\n                        background-color    : lightyellow;\n                  }\n                  .item input {\n                        width     : 18px;\n                        height    : 18px;\n                        vertical-align    : middle;\n                  }\n                  .name {\n                        vertical-align    : middle;\n                  }\n                  input {\n                        font-size   : 18px;\n                        cursor      : pointer;\n                  }\n            </style>\n            <div id=root>\n                  <input value=cancel type=button>\n                  select files to download\n                  <input value=ok type=button>\n                  <div id=list>\n                        <div class=item>\n                              <input type=checkbox>\n                              <span class=name>name</name>\n                        </div>\n                  </div>\n            </div>\n      </div>\n</template>\n`,txt.worker="\n\n        var socket;\n        var onopen;\n        \n        self.onmessage=function(event){\n        \n              var json    = event.data;\n              switch(json.type){\n              \n                case 'init'               : init(json);                 break;\n                case 'upload'             : upload(json);               break;\n                case 'download'           : download(json);             break;\n                case 'download-list'      : download.list(json);        break;\n                \n              }//switch\n              \n        }//onmessage\n        \n        function init(json){\n        \n              var url             = json.url;\n              socket              = new WebSocket(url);\n              socket.onerror      = console.error;\n              socket.onmessage    = event=>self.postMessage(event.data);\n              socket.onopen       = ()=>{\n              \n                    if(onopen){\n                          onopen();\n                    }else{\n                          onopen    = true;\n                    }\n                    \n              };\n              \n        }//init\n        \n        function onready(json){\n        \n              var fn    = ()=>socket.send(JSON.stringify(json));\n              if(onopen){\n                    fn();\n              }else{\n                    onopen    = fn;\n              }\n              \n        }//onready\n        \n  //:\n  \n        function upload(json){\n        \n              onready(json);\n              self.onmessage    = event=>{\n              \n                    var data    = event.data;\n                    var type    = datatype(data);\n                    switch(type){\n                    \n                      case 'object'   : socket.send(JSON.stringify(event.data));      break;\n                      case 'blob'     : socket.send(data);                            break;\n                      \n                    }//switch\n                    \n              };\n              \n        }//upload\n        \n  //:\n  \n        function download(json){\n        \n              onready(json);\n              self.onmessage    = event=>socket.send(JSON.stringify(event.data));\n              \n        }//download\n        \n        download.list=function(json){\n        \n              onready(json);\n              \n        }//list\n        \n  //:\n  \n        function datatype(value){\n        \n              var str     = Object.prototype.toString.call(value);\n              var i       = str.indexOf(' ');\n              var type    = str.slice(i+1,-1);\n              type        = type.toLowerCase();\n              return type;\n              \n        }//datatype\n        \n        \n        \n",module.exports=wsmod;var img={};function setup(){key="\n-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAifijYDZ8z15jB1gDVn+4YoET3kLn8uJ7NR1pBzRB1F9RM7yF\nFH+8awRwsv+HBwXfj0nvokc0w0OLDBagG5c5WW9A1MywjAzsv7ob9JpYEHdGhag1\nvrYz2IcoK34W9yJvja/ry3e5eIjnRHF7le8oZSU7CAHlf+BAD7Z5KncHxZ/W5ZSe\nARMsG3xeY5+dTDSoCdmDsOvUVcadpqkanLnDkfS85MFmTZEIQkrFJ0kMNJPPt6Yy\n+fjfQlEKuxvCJ5CHd+/Vbt67xQFLaAnMfIFkA8OYF+0qST8uwZJxitCRNIwb8JeE\n0vxghWjt/wpatxj0yOH9jrF1LY54KzmVcseT+QIDAQABAoIBAAfhzHOSqU6vdbjV\nrlsisMXv2dK4v8eAaMmABKAEvD4Fs/3gpzmkkn8KoeWX837TNxJYhvgeWUVOdoMa\nR8tLuFsDoBCIIQ3fLydE2pP+vUkzhzPbiDZ88jlMf178tRMpRHYztqTJEi9wosWm\nNgk6wHQGzHiIozPK+93p7sIeSrNfeMYg2zdgfaC5HYtTTgwKCjsqXojWJ2Ek76yO\n549o/mxr7FGaRWHruWnldV/jB1nlnqMO7h4DbzvseHmf7s2JRFaI8mzrpo7bXgSs\nRhtwdRMp9M/J/QgMSDpPycfe5bbUeFuRs6DfkHb7NofOejHWOlN6AWE6sNEuB0oD\nOMpIdoECgYEAxmjnSiRR6if+c2Fzvskh6ZYcw0ok9qRmJZXE/2FynHjxXcMLMfPq\nkRm62WfXx/GB1FtaN9tkwNTF5o802iokIWbNnoumIzVfTLxHe72r1x4Wi6qtriV+\neWsJXPGDFVuEfOalb628PKk/CNoE2RTuFaszpuNeal3TrQEoMFjURTsCgYEAsgTF\nTdRL2wfRLoitapdOfHSESx9YWwF985Z1VDxVhegrQKsbeHCMhy80EsRWg+zZmn5g\nr9dcgx8RZ5VyXTkK5yyrcqhGlI+ASz/XdWolYOON3ufcJZK0p8JLiSKOMigyV+zQ\nORJvF2DV028Sf2yIIBfqLWwj3A0S1HXuMdppaFsCgYEAtQLTr/J+wQ+Htp/7xt0c\nkUygJRKpslK91iBTP2IZ++Li4VWmCwaqw4Ij02BHnmQYvDIzlq4Tt1Dm07dm0nqL\nx+VQRgrFoW156joDRUL/WRxcGUO3fB88yWz7d83mdz+tGX4bBE4OY5FMrrion5vW\nsfIlB2Z7prK/ASEDF0iTMC0CgYAOrzIPT3nKYYSxz/huGHkS7xGSmM1TBHZ9R2X6\nQtfTKemXdgXYE4CkDhK/kY6JZ1oz0kTrfFpXPpLS9pLueawakaa62IKp8oRksKl3\nOABJU7v6LSoNt2JLjE1qzHzqSkKM2i365zhet1AsFrdx52P7Mb3RbWAv4Kwy9l9l\noVycjQKBgAvZkYwlCQT642TkMTsf+nQ0Ewnzmjd+C7ibBP6bQr3zSwzQRzDf6x27\n7CsUr3hTae3omhFBozNKSt76ym4HqXHYyq0LC63nu2kavfg8krIGHdIykD963Qkg\na/iAoLnnHSFqVyAZ8BIpH3MZu1T3oJ6zhavqeX1xPvpmr0ZTPCwm\n-----END RSA PRIVATE KEY-----\n",cert="\n-----BEGIN CERTIFICATE-----\nMIIDeDCCAmCgAwIBAgIBATANBgkqhkiG9w0BAQUFADAlMSMwIQYDVQQDExpsb2Nh\nbGhvc3QgdGVzdCBjZXJ0aWZpY2F0ZTAeFw0yNDA1MjUyMjEzMDBaFw0yNTA1MjUy\nMjEzMDBaMCUxIzAhBgNVBAMTGmxvY2FsaG9zdCB0ZXN0IGNlcnRpZmljYXRlMIIB\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAifijYDZ8z15jB1gDVn+4YoET\n3kLn8uJ7NR1pBzRB1F9RM7yFFH+8awRwsv+HBwXfj0nvokc0w0OLDBagG5c5WW9A\n1MywjAzsv7ob9JpYEHdGhag1vrYz2IcoK34W9yJvja/ry3e5eIjnRHF7le8oZSU7\nCAHlf+BAD7Z5KncHxZ/W5ZSeARMsG3xeY5+dTDSoCdmDsOvUVcadpqkanLnDkfS8\n5MFmTZEIQkrFJ0kMNJPPt6Yy+fjfQlEKuxvCJ5CHd+/Vbt67xQFLaAnMfIFkA8OY\nF+0qST8uwZJxitCRNIwb8JeE0vxghWjt/wpatxj0yOH9jrF1LY54KzmVcseT+QID\nAQABo4GyMIGvMA8GA1UdEwQIMAYBAf8CAQAwCwYDVR0PBAQDAgL0MDsGA1UdJQQ0\nMDIGCCsGAQUFBwMBBggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEF\nBQcDCDARBglghkgBhvhCAQEEBAMCAPcwIAYDVR0RBBkwF4IJbG9jYWxob3N0hwR/\nAAABhwR/AAACMB0GA1UdDgQWBBS+S+lkV9wybiRGN2wyJVKXe+HpkzANBgkqhkiG\n9w0BAQUFAAOCAQEAVDcA9uABIDHBtShBwPPo6Iur3B4fw3HeEKq5mEB5JWoQesy7\nTjBnBDcVCdp2m75OyYODZVcHSLMyFnKayaC3CMwnMI2EgeqiIW5YFNEB2a3WrzjZ\ngOBD3KziQJ19/coMzJXk5l7yiej87T3iDDwsqLMSQJcdetntJuE3ch6MvxGEfOcK\ntpF3+yv7U7lwExyd5NQYKxALg3iAdhYYTp6nZv40nHGHB5o+gBvzWDA1AVCxOa5R\nOepo7XYGuVWiDB3mSa+NNWY0xo5oiyXhns/oS55ZsB5s1+tLHJFKSZkcB/hLQ6sx\nul0imHaaWt/1zLXouSElPkVUHFNvSVpJx09C4Q==\n-----END CERTIFICATE-----\n"}img.favicon="iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAAXNSR0IArs4c6QAAAmNJREFUSEvt1j9oE1EYAPDv3V+Tu0Sbu+QUiroVnRRBEXTo4qqrHewgSLu6aIZqSR0sooMO7ejqJAodVLCgiIvgIBQEkaakSEk5L8mFy/Uu78lTrlySu+Rdmi7SrHnf9/ve9/4dgiF++fmTR22uWiQY6xrk7mzOb1aSpkFJAyjagK3HLWhN0VgZySs60WaS4olgita5X0su3rkWLlhG8mpe0m9VipUfrBNhhuNQCiFAbQlJH5LgTHA/dNiZD4RZ0aAA1rb3hZOiSfBYeFiUFY+E94qy4D3wqNAwrhPtRvc574VL+bN10ljAQLK7O5bg4x74J1jOqAhCGRC3EYzlAPkKl35ozplvw/GRrb74ZDwVHvTNtqZt3FxigVNc+ukZdexueOzn2xWnO3bgcaIBakmdYYbRoUXnvtMBRxV8AEcu40Gr++3u1H+xuTIPjkw3/NpzpnPMOmN6RbpCbZIjpHlKUd9FHfbcQu6K1a69xIDTg3CVU2bte/Zy1Diax0P42GFe/YSCpDxw1QIULkV9O00sTmTKzsZy8J0Vh4tI+lgg+lRUDnobfmlU33hk5zItjgmmEO2Mw5mzNBADVjpx5ApI+KqJY8/ivrto8T9b6yuJ4QCilZueIoThnNj0o5YoPGbP8KA1jvu/p9VaybjwG2+/pwFZPnPdkIzVYZP3i9t2rdN1bL7woT2uCtmbiLZgvVV+5RJ38t9birb2AwZCDPqmiyCtGXL+6t/Xic66TqxHbeKfI0DkfYEBQADxu8Knitac9Xr3WaS71uOt8xihrh07mjLoPaEK6lqw65ne49HQnVn+AP19gPx3AUdHAAAAAElFTkSuQmCC";
//# sourceMappingURL=/sm/ed331bb0535b98a480374b477de982329aebb35379d2e0a649102b4659ddaba0.map