hfs 0.48.3 → 0.49.0-alpha2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +4 -2
  2. package/admin/assets/index-6afd4b06.js +542 -0
  3. package/{frontend/assets/sha512-70e9a0d9.js → admin/assets/sha512-f5f12dc1.js} +1 -1
  4. package/admin/index.html +1 -1
  5. package/central.json +30 -8
  6. package/frontend/assets/index-3a379840.js +94 -0
  7. package/frontend/assets/index-db5f0e4b.css +1 -0
  8. package/{admin/assets/sha512-2627feb7.js → frontend/assets/sha512-3392c31f.js} +1 -1
  9. package/frontend/index.html +2 -2
  10. package/package.json +1 -2
  11. package/plugins/download-counter/plugin.js +14 -15
  12. package/src/adminApis.js +1 -0
  13. package/src/api.auth.js +6 -6
  14. package/src/api.file_list.js +15 -8
  15. package/src/api.net.js +97 -72
  16. package/src/api.plugins.js +15 -26
  17. package/src/api.vfs.js +9 -12
  18. package/src/block.js +14 -1
  19. package/src/commands.js +1 -1
  20. package/src/comments.js +49 -0
  21. package/src/config.js +1 -5
  22. package/src/const.js +3 -2
  23. package/src/cross.js +43 -2
  24. package/src/customHtml.js +1 -1
  25. package/src/debounceAsync.js +18 -1
  26. package/src/frontEndApis.js +28 -6
  27. package/src/github.js +24 -30
  28. package/src/langs/hfs-lang-it.json +5 -3
  29. package/src/langs/hfs-lang-ru.json +6 -4
  30. package/src/listen.js +25 -15
  31. package/src/middlewares.js +3 -1
  32. package/src/misc.js +6 -5
  33. package/src/perm.js +19 -12
  34. package/src/plugins.js +62 -38
  35. package/src/serveFile.js +2 -3
  36. package/src/upload.js +5 -1
  37. package/src/util-files.js +38 -2
  38. package/src/util-http.js +8 -6
  39. package/src/vfs.js +24 -32
  40. package/src/watchLoad.js +5 -12
  41. package/src/zip.js +4 -4
  42. package/admin/assets/index-4e55b514.js +0 -536
  43. package/frontend/assets/index-cadcb0e9.css +0 -1
  44. package/frontend/assets/index-e24651da.js +0 -94
@@ -0,0 +1 @@
1
+ @charset "UTF-8";:root{height:100dvh;--bg: #fff;--text: #555;--ghost-contrast: #8882;--ghost-contrast-alt: #eee;--faint-contrast: #8884;--mild-contrast: #8886;--good-contrast: #000a;--button-bg: #6080aa;--button-text: #eaeaea;--focus-color: #468;--separator: " – "}:root .highlightedText,:root .file-menu a:hover{color:#0006;text-shadow:0 0 3px rgba(0,0,0,.4)}:root .theme-dark{--bg: #000;--text: #999;--ghost-contrast-alt: #181818;--good-contrast: #fffa;--button-bg: #345;--button-text: #999;color-scheme:dark}:root .theme-dark .highlightedText,:root .theme-dark .file-menu a:hover,:root .file-menu .theme-dark a:hover{color:#fff;text-shadow:0 0 3px #fff}:root .theme-dark a{color:#8ac}:root .theme-dark .dialog-closer{background:#633}:root .theme-dark .dialog-icon{color:#ccc}:root .theme-dark .dialog-icon .icon{color:#aaa}:root .theme-dark .dialog-backdrop{background:rgba(51,51,51,.7333333333)}:root .theme-dark .error-msg{color:#b88;background-color:#623}:root .theme-dark button.toggled{color:#eee}body{background-color:var(--bg);margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body,button,select,input{font-size:12pt}#root>div{max-width:54em;margin:auto;min-height:100vh;display:flex;flex-direction:column}body,input{color:var(--text)}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}input:not([type=checkbox],[type=range]),select,textarea{padding:.3em .4em;border-radius:.5em;background:var(--bg);border-color:var(--mild-contrast);color:var(--good-contrast);max-width:100%;box-sizing:border-box;width:100%}input[type=range]{width:calc(100% - 1px)}input[type=checkbox]{transform:scale(1.7);accent-color:var(--button-bg)}textarea{font-size:14pt}label input[type=checkbox]{margin-right:.8em}select{text-align:center}.hidden{display:none!important}[class^=fa-]:before,[class*=" fa-"]:before{margin:0}.icon{font-size:1.2em;height:1.2em;width:1.4em;display:inline-block;text-align:center}img.file-icon{height:1em}.file-icon{background-size:contain;background-repeat:no-repeat;background-position:center;vertical-align:text-bottom}.icon.mirror:before{transform:scaleX(-1)}a{text-decoration:none;color:var(--button-bg)}button{background-color:var(--button-bg);color:var(--button-text);padding:.5em 1em;border:transparent;text-decoration:none;border-radius:.3em;vertical-align:middle;cursor:pointer;transition:background-color .5s}button:hover{outline:1px solid var(--mild-contrast)}button[disabled]{background-color:var(--faint-contrast)}button.toggled{color:#fff;text-shadow:0 0 3px #fff}button:focus-visible,.breadcrumb:focus-visible{outline:3px solid var(--focus-color)}a>button{width:100%}input:focus-visible,select:focus-visible,ul a:focus-visible{border-radius:.3em;border-color:transparent;outline:2px solid var(--focus-color)}.icon-button,ul.dir li .entry-panel .file-menu-button{font-size:.7em;padding:.2em .4em;margin-left:.4em;vertical-align:bottom}.error-msg{background-color:#faa;color:#833;padding:.5em 1em}.hide-back,.upload-toolbar,header{background-color:var(--bg)}header{position:sticky;top:0;padding:.2em .1em;z-index:3}kbd{background-color:#eee;border-radius:3px;border:1px solid #b4b4b4;box-shadow:0 1px 1px #0003,0 2px #ffffffb3 inset;color:#333;display:inline-block;font-size:.85em;font-weight:700;line-height:1;padding:2px 4px;white-space:nowrap;margin-right:.5em}.before-sliding{width:0!important;flex:0!important;margin:0!important;height:0!important;padding:0!important;overflow:hidden!important;transition:flex .5s}.show-sliding{transition:flex .5s;overflow:clip;flex:1;white-space:nowrap}.ani-working{animation:1s blink infinite}@keyframes blink{0%{opacity:1}50%{opacity:.2}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.spinner,.icon.spinner:before{animation:1.5s spin infinite linear;display:inline-flex;justify-content:center;align-items:center;width:min-content}.breadcrumb{padding:.1em .6em .2em;line-height:1.8em;border-radius:.7em;background-color:var(--button-bg);color:var(--button-text);border-top:1px solid #666;margin-right:-.1em}.breadcrumb:nth-child(-n+3) .icon{padding:0 .2em}#folder-stats,#filter-bar>span{font-size:90%}#folder-stats{margin:.5em 0 0 .5em;float:right}#folder-stats .icon{margin-right:.3em}#filter{flex:1;box-sizing:border-box}#filter-bar{display:flex;align-items:center;gap:.8em;margin:.2em 0 0;padding:2px 0 1px 3px;height:1.8em}#filter-bar input[type=checkbox]{margin-top:.3em}#filter-bar span:empty{display:none}ul.dir{flex:1;padding:0;margin:0;clear:both}ul.dir>p{text-align:center}ul.dir li{display:block;list-style-type:none;padding:.3em .3em .4em;border-bottom:1px solid var(--faint-contrast)}ul.dir li:nth-of-type(odd){background-color:var(--ghost-contrast)}ul.dir li input[type=checkbox]{margin-right:1em}@media (hover: none){ul.dir li .link-wrapper .popup-menu-button{display:none}}@media (hover: hover){ul.dir li .link-wrapper:not(:hover) .popup-menu-button{display:none}ul.dir li .link-wrapper:hover{padding:1em;margin:-1em}}ul.dir li .link-wrapper a:last-of-type{word-break:break-word;padding-right:.3em}ul.dir li .link-wrapper a .icon{margin-right:.5em;vertical-align:text-bottom;display:inline-block;text-align:center}ul.dir li .link-wrapper a:hover{text-decoration:underline}ul.dir li .entry-panel{float:right;padding-top:.3em;display:flex;align-items:center}ul.dir li .entry-panel .file-menu-button{margin:-3px 0 -3px .4em}ul.dir li .entry-panel .entry-details{font-size:90%;margin-left:4px;font-variant-numeric:tabular-nums}ul.dir li .entry-panel .entry-details .entry-size-unit{margin-left:.3em}ul.dir li>div:last-of-type{clear:both}ul.dir li.page-separator{margin-top:1em;position:relative}ul.dir li.page-separator:before{content:attr(label);position:absolute;top:-1.8em;font-size:smaller;margin-left:calc(50% - 1em);opacity:.9}.entry-comment{display:inline;white-space:pre-wrap;word-break:break-word}.entry-comment:before,.entry-comment:after{font-size:1.5em;font-family:serif;line-height:1px;position:relative}.entry-comment:before{content:"“";margin-left:.5em;margin-right:.1em;top:.2em}.entry-comment:after{content:"„";top:-.1em;margin-left:.1em}#menu-bar{display:flex;justify-content:space-evenly;flex-wrap:wrap}#menu-bar>*{flex:1;margin:.1em}#menu-bar>*:first-child{margin-left:0}#menu-bar>*:last-child{margin-right:0}#menu-bar button{padding:min(1vh,.5em) 0}#searched{margin:.2em}#user-panel{display:flex;flex-direction:column;gap:1em}#user-panel a>button{width:100%}button label{cursor:inherit;margin-left:.4em}.dialog-backdrop.working{font-size:5em;animation:1s fade-in}.dialog-content{padding:.2em}.dialog-alert .dialog-content{text-align:center}.dialog-alert .dialog-content p{text-align:left;display:inline-block}.dialog{min-width:11em;--color: var(--button-bg)}.dialog-icon .icon{margin-left:-1px;font-size:95%;margin-top:.4em;border-radius:.6em 0}#paging{position:sticky;bottom:0;display:flex;gap:.1em;background-color:var(--bg);padding:0 .2em .2em}#paging>button{z-index:1}#paging button{box-shadow:0 0 .3em .3em #0003}#paging #paging-middle{padding:0 .5em;margin:0 -.3em;display:flex;gap:.5em;flex:1;overflow-x:auto}#paging #paging-middle>button{flex:1;padding-top:0;padding-bottom:0}#paging button{background:var(--button-bg);text-align:center;white-space:nowrap;padding:.5em}.upload-toolbar{position:sticky;top:-4px}.upload-progress:before{content:var(--separator)}.entry-size:after{content:var(--separator)}.upload-progress{min-width:4em;display:inline-block;margin-left:.5em}.upload-list{margin-top:1em}.upload-list td:nth-child(1){width:0}.upload-list td:nth-child(2){text-align:right;width:0;white-space:nowrap;padding-left:.5em}.upload-list td:nth-child(3){padding:.2em .5em;word-break:break-word}.nowrap{white-space:nowrap}.dialog-login form{display:flex;flex-direction:column;gap:1.2em}.dialog-login label{display:block;margin-bottom:.5em;margin-left:.1em}.miss-perm{margin:.3em}.popup-menu-button{font-size:.8em;padding:.2em .3em;position:absolute;opacity:.8;white-space:nowrap}.popup-menu-button:hover{opacity:1}.file-dialog .dialog{min-width:13em}.file-dialog-properties{word-break:break-word;line-height:1.5em;margin:0}.file-dialog-properties dt{font-weight:700}.file-dialog-properties dd{margin-left:1.5em}.file-menu{margin-top:1em;padding-top:1em;border-top:1px solid var(--faint-contrast);display:flex;flex-direction:column;gap:1em}.file-menu a{display:flex;align-items:flex-start}.file-menu a label{margin-top:.1em}.file-menu a label small{display:block}.file-menu a .icon{margin-right:.5em}#root>.tiles-mode{max-width:none;margin:0 1em;--tiles-size: 5;--name-lines: 3;--name-height: calc(4.7em + var(--tiles-size) * .4em + 1.2em * var(--name-lines));--tile-width: calc(5em + 1em * var(--tiles-size))}#root>.tiles-mode ul.dir{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--tile-width),1fr));grid-auto-rows:calc(var(--name-height) + 1.3em);gap:0 20px}#root>.tiles-mode ul.dir li{text-align:center;position:relative;display:flex;flex-direction:column;border-bottom:none;overflow-x:clip;padding:.5em 0 0}#root>.tiles-mode ul.dir li .link-wrapper:not(:hover){max-height:var(--name-height);display:block;display:-webkit-box;overflow:hidden;-webkit-line-clamp:calc(var(--name-lines) + 1);-webkit-box-orient:vertical}#root>.tiles-mode ul.dir li .link-wrapper a:last-of-type{padding:0}#root>.tiles-mode ul.dir li .link-wrapper a span{display:block}#root>.tiles-mode ul.dir li .link-wrapper a .icon.icon{font-size:calc(1.2rem + .6rem * var(--tiles-size))}#root>.tiles-mode ul.dir li .link-wrapper a img.icon{width:auto;height:1em;padding:.1em 0}#root>.tiles-mode ul.dir li .link-wrapper a .icon{font-size:4rem;display:block;margin:auto}#root>.tiles-mode ul.dir li .link-wrapper:hover{overflow:visible;display:block;z-index:1}#root>.tiles-mode ul.dir li .link-wrapper:hover .icon:before{text-decoration:none}#root>.tiles-mode ul.dir li .link-wrapper:hover{padding:0;margin:0}#root>.tiles-mode ul.dir li:nth-of-type(odd){background-color:var(--bg)}#root>.tiles-mode ul.dir li .entry-panel{justify-content:center;font-size:10pt}#root>.tiles-mode ul.dir li .entry-details{font-size:80%}#root>.tiles-mode ul.dir li.page-separator:before{content:""}#root>.tiles-mode ul.dir li input[type=checkbox]{margin:0;position:absolute;top:.3em;right:1em}#root>.tiles-mode ul.dir li .link-wrapper a{display:inline}#root>.tiles-mode ul.dir li:hover{--bg: var(--ghost-contrast-alt);background:var(--bg)}#root>.tiles-mode ul.dir li:hover .link-wrapper,#root>.tiles-mode ul.dir li:hover .entry-panel{z-index:1;background:var(--bg)}#root>.tiles-mode ul.dir li:hover input[type=checkbox]{z-index:2}#root>.tiles-mode ul.dir li:hover .entry-panel{padding-bottom:.3em}#root>.tiles-mode .entry-size:after{content:none}#root>.tiles-mode .entry-ts{display:none}#root>.tiles-mode .popup-menu-button{position:absolute;top:0;left:0}#root>.tiles-mode #filter-bar{margin-bottom:1em}#root>.tiles-mode #paging{z-index:1}#root .file-show{--nav-size: min(25vh, 25vw)}#root .file-show>div{height:100%;width:100%}#root .file-show .showing-container{width:100%;height:100%;display:flex;justify-content:center;align-items:center}#root .file-show .showing{max-width:calc(100% - var(--nav-size) * 2);max-height:100%}#root .file-show img.showing{max-width:100%}#root .file-show .main{flex:1;position:relative;max-height:100%;overflow:hidden}#root .file-show .freeY .main,#root .file-show .fullWidth .main{overflow-y:auto}#root .file-show .freeY .showing,#root .file-show .fullWidth .showing{max-height:initial;margin:auto}#root .file-show .freeY .showing-container,#root .file-show .fullWidth .showing-container{overflow:auto;align-items:flex-start}#root .file-show .fullWidth .showing-container,#root .file-show .fullWidth .showing{width:100%}#root .file-show .nav{position:absolute;margin:-.4em;font-size:var(--nav-size);cursor:pointer;opacity:.3;-webkit-text-stroke:2px black;user-select:none;transition:opacity .3s}#root .file-show .nav:hover{opacity:.7}#root .file-show .nav.nav-hidden{opacity:0}#root .file-show .bar{padding:.5em 1em;background:var(--bg);opacity:.8;display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:.5em 1.5em}#root .file-show .bar .entry-details{font-size:smaller}#root .file-show .bar .entry-ts{display:inherit}#root .file-show .bar .entry-size:after{display:none}.file-show-help kbd{margin:.5em}.file-show-help kbd:first-of-type{margin-top:1em}@media (min-width: 42em){body{scrollbar-width:thin;scrollbar-color:var(--button-bg) var(--ghost-contrast)}body::-webkit-scrollbar{width:12px}body::-webkit-scrollbar-track{background:var(--ghost-contrast)}body::-webkit-scrollbar-thumb{background-color:var(--button-bg);border-radius:20px;border:1px solid var(--ghost-contrast)}}@media (max-width: 42em){:root{--ghost-contrast: #8883}body,button,select{font-size:14pt}#menu-bar button label,#filter-bar button label{display:none}#filter-bar{margin-top:.4em}#filter-bar button{width:17.6vw;height:2.3em}.breadcrumb{word-break:break-all}.breadcrumb .icon{font-size:24px}#root>.tiles-mode{margin:0}}@media (max-height: 600px){.file-dialog .dialog-content{display:flex;gap:3em;margin:1em}.file-dialog .dialog-content .file-menu{margin-top:0;padding-top:0;border-top:none;margin-left:2em;padding-left:2em;border-left:1px solid var(--faint-contrast)}}@media (pointer: coarse){#root>.tiles-mode .file-menu-button{font-size:1em;margin-top:.3em}}.dialog-backdrop{position:fixed;inset:0;background:#8886;backdrop-filter:blur(2px);display:flex;justify-content:center;align-items:center;z-index:1000}.dialog{background:#fff;background:var(--bg);padding:max(.5em,1vw,2vh);padding-top:0;border-radius:1em;position:relative;margin:0 3vw;overflow:hidden;max-height:calc(100vh - 2em);display:flex;flex-direction:column;justify-content:center}.dialog-icon{color:#fff;background-color:var(--color);position:absolute;top:0;width:2em;height:1.8em;text-align:center;border-radius:.8em 0}.dialog-icon-text{display:flex;align-items:center;justify-content:center}.dialog-title{font-size:120%;margin:.3em 0;padding:0 .5em;min-height:1.2em}.dialog-closer~.dialog-title{margin-right:2em}.dialog-type~.dialog-title{margin-left:2em}.dialog-icon~.dialog-title{text-align:center}.dialog-closer{border-radius:0 .8em;right:0;padding:0;background-color:#c88}.dialog-icon~.dialog-content{margin-top:1em}.dialog-type{left:0;top:0;overflow:hidden;opacity:.7}.dialog-content{overflow:auto;max-height:calc(100vh - 4.5em)}.dialog-content p{white-space:pre-wrap;margin:.5em 0}.dialog-confirm .dialog-content button{margin-top:1em}.dialog-alert-info{--color: #282 }.dialog-alert-warning{--color: #c91 }.dialog-alert-error{--color: #822}@media (max-width: 42em){.dialog-icon{font-size:120%}.dialog-icon~.dialog-content{margin-top:1.5em}}.dialog-prompt label{display:block;margin:.5em .1em}
@@ -1,4 +1,4 @@
1
- import{g as OF,c as UF}from"./index-4e55b514.js";function gF(sF,hF){for(var eF=0;eF<hF.length;eF++){const tF=hF[eF];if(typeof tF!="string"&&!Array.isArray(tF)){for(const w in tF)if(w!=="default"&&!(w in sF)){const lF=Object.getOwnPropertyDescriptor(tF,w);lF&&Object.defineProperty(sF,w,lF.get?lF:{enumerable:!0,get:()=>tF[w]})}}}return Object.freeze(Object.defineProperty(sF,Symbol.toStringTag,{value:"Module"}))}var dF={exports:{}};/*
1
+ import{g as OF,c as UF}from"./index-3a379840.js";function gF(sF,hF){for(var eF=0;eF<hF.length;eF++){const tF=hF[eF];if(typeof tF!="string"&&!Array.isArray(tF)){for(const w in tF)if(w!=="default"&&!(w in sF)){const lF=Object.getOwnPropertyDescriptor(tF,w);lF&&Object.defineProperty(sF,w,lF.get?lF:{enumerable:!0,get:()=>tF[w]})}}}return Object.freeze(Object.defineProperty(sF,Symbol.toStringTag,{value:"Module"}))}var dF={exports:{}};/*
2
2
  * [js-sha512]{@link https://github.com/emn178/js-sha512}
3
3
  *
4
4
  * @version 0.8.0
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0" />
6
6
  <link href="/fontello.css" rel="stylesheet" />
7
7
 
8
- <script type="module" crossorigin src="/assets/index-e24651da.js"></script>
9
- <link rel="stylesheet" href="/assets/index-cadcb0e9.css">
8
+ <script type="module" crossorigin src="/assets/index-3a379840.js"></script>
9
+ <link rel="stylesheet" href="/assets/index-db5f0e4b.css">
10
10
  </head>
11
11
  <body>
12
12
  <noscript>You need to enable JavaScript to run this app.</noscript>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hfs",
3
- "version": "0.48.3",
3
+ "version": "0.49.0-alpha2",
4
4
  "description": "HTTP File Server",
5
5
  "keywords": [
6
6
  "file server",
@@ -79,7 +79,6 @@
79
79
  "lodash": "^4.17.21",
80
80
  "minimist": "^1.2.6",
81
81
  "nat-upnp-ts": "^2.0.1",
82
- "node-html-parser": "^6.1.5",
83
82
  "open": "^8.4.0",
84
83
  "tssrp6a": "^3.0.0",
85
84
  "unzip-stream": "^0.3.1",
@@ -44,21 +44,20 @@ exports.init = async api => {
44
44
  frontend_js: 'main.js',
45
45
  frontend_css: 'style.css',
46
46
  unload: () => save.flush(), // we may have pending savings
47
- middleware: (ctx) =>
48
- () => { // execute after other middlewares are done
49
- if (ctx.status >= 300 || ctx.state.download_counter_ignore || ctx.state.includesLastByte === false) return
50
- if (!(ctx.vfsNode || api.getConfig('archives') && ctx.state.archive)) return
51
- ctx.state.completed.then(() => {
52
- const key = uri2key(ctx.path)
53
- const entries = ctx.vfsNode ? [key]
54
- : ctx.state.originalStream?.getArchiveEntries?.().filter(x => x.at(-1) !== '/').map(x => key + uri2key(x))
55
- if (!entries) return
56
- for (const k of entries)
57
- counters[k] = counters[k] + 1 || 1
58
- save()
59
- })
60
- },
61
- onDirEntry: ({ entry, listUri }) => {
47
+ middleware: ctx => () => { // callback = execute after other middlewares are done
48
+ if (ctx.status >= 300 || ctx.state.download_counter_ignore || ctx.state.includesLastByte === false) return
49
+ if (!(ctx.vfsNode || api.getConfig('archives') && ctx.state.archive)) return
50
+ ctx.state.completed.then(() => {
51
+ const key = uri2key(ctx.path)
52
+ const entries = ctx.vfsNode ? [key]
53
+ : ctx.state.originalStream?.getArchiveEntries?.().filter(x => x.at(-1) !== '/').map(x => key + uri2key(x))
54
+ if (!entries) return
55
+ for (const k of entries)
56
+ counters[k] = counters[k] + 1 || 1
57
+ save()
58
+ })
59
+ },
60
+ onDirEntry({ entry, listUri }) {
62
61
  const k = uri2key(listUri + entry.n)
63
62
  const n = counters[k]
64
63
  if (n)
package/src/adminApis.js CHANGED
@@ -107,6 +107,7 @@ exports.adminApis = {
107
107
  compatibleApiVersion: const_1.COMPATIBLE_API_VERSION,
108
108
  ...await (0, listen_1.getServerStatus)(),
109
109
  urls: await (0, listen_1.getUrls)(),
110
+ ips: await (0, listen_1.getIps)(false),
110
111
  baseUrl: middlewares_1.baseUrl.get(),
111
112
  updatePossible: !(0, update_1.updateSupported)() ? false : await (0, update_1.localUpdateAvailable)() ? 'local' : true,
112
113
  proxyDetected: (0, middlewares_1.getProxyDetected)(),
package/src/api.auth.js CHANGED
@@ -33,17 +33,17 @@ function makeExp() {
33
33
  const login = async ({ username, password }, ctx) => {
34
34
  if (!username || !password) // some validation
35
35
  return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST);
36
- const acc = (0, perm_1.getAccount)(username);
37
- if (!acc)
36
+ const account = (0, perm_1.getAccount)(username);
37
+ if (!account || !(0, perm_1.accountCanLogin)(account))
38
38
  return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
39
- if (!acc.hashed_password)
39
+ if (!account.hashed_password)
40
40
  return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_ACCEPTABLE);
41
- if (!await (0, crypt_1.verifyPassword)(acc.hashed_password, password))
41
+ if (!await (0, crypt_1.verifyPassword)(account.hashed_password, password))
42
42
  return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
43
43
  if (!ctx.session)
44
44
  return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
45
45
  await loggedIn(ctx, username);
46
- return { ...makeExp(), redirect: acc.redirect };
46
+ return { ...makeExp(), redirect: account.redirect };
47
47
  };
48
48
  exports.login = login;
49
49
  const loginSrp1 = async ({ username }, ctx) => {
@@ -52,7 +52,7 @@ const loginSrp1 = async ({ username }, ctx) => {
52
52
  const account = (0, perm_1.getAccount)(username);
53
53
  if (!ctx.session)
54
54
  return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
55
- if (!account) // TODO simulate fake account to prevent knowing valid usernames
55
+ if (!account || !(0, perm_1.accountCanLogin)(account)) // TODO simulate fake account to prevent knowing valid usernames
56
56
  return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
57
57
  try {
58
58
  const { step1, ...rest } = await srpStep1(account);
@@ -12,6 +12,8 @@ const plugins_1 = require("./plugins");
12
12
  const misc_1 = require("./misc");
13
13
  const lodash_1 = __importDefault(require("lodash"));
14
14
  const const_1 = require("./const");
15
+ const comments_1 = require("./comments");
16
+ const path_1 = require("path");
15
17
  const get_file_list = async ({ uri, offset, limit, search, c }, ctx) => {
16
18
  var _a;
17
19
  const node = await (0, vfs_1.urlToNode)(uri || '/', ctx);
@@ -36,12 +38,12 @@ const get_file_list = async ({ uri, offset, limit, search, c }, ctx) => {
36
38
  const can_upload = (0, vfs_1.hasPermission)(node, 'can_upload', ctx);
37
39
  const fakeChild = (0, vfs_1.applyParentToChild)({}, node); // we want to know if we want to delete children
38
40
  const can_delete = (0, vfs_1.hasPermission)(fakeChild, 'can_delete', ctx);
39
- const props = { can_upload, can_delete, accept: node.accept };
41
+ const can_archive = (0, vfs_1.hasPermission)(fakeChild, 'can_archive', ctx);
42
+ const props = { can_archive, can_upload, can_delete, accept: node.accept };
40
43
  if (!list)
41
44
  return { ...props, list: await (0, misc_1.asyncGeneratorToArray)(produceEntries()) };
42
45
  setTimeout(async () => {
43
- if (can_upload || can_delete)
44
- list.props(props);
46
+ list.props(props);
45
47
  for await (const entry of produceEntries())
46
48
  list.add(entry);
47
49
  list.close();
@@ -57,14 +59,18 @@ const get_file_list = async ({ uri, offset, limit, search, c }, ctx) => {
57
59
  for await (const sub of walker) {
58
60
  if (ctx.aborted)
59
61
  break;
60
- if (!filter((0, vfs_1.getNodeName)(sub)))
62
+ const name = (0, path_1.basename)((0, vfs_1.getNodeName)(sub));
63
+ if (comments_1.descriptIon.get() && name === comments_1.DESCRIPT_ION)
64
+ continue;
65
+ if (!filter(name))
61
66
  continue;
62
67
  const entry = await nodeToDirEntry(ctx, sub);
63
68
  if (!entry)
64
69
  continue;
65
70
  const cbParams = { entry, ctx, listUri: uri, node: sub };
66
71
  try {
67
- if (onDirEntryHandlers.some(cb => cb(cbParams) === false))
72
+ const res = await Promise.all(onDirEntryHandlers.map(cb => cb(cbParams)));
73
+ if (res.some(x => x === false))
68
74
  continue;
69
75
  }
70
76
  catch (e) {
@@ -96,11 +102,11 @@ const get_file_list = async ({ uri, offset, limit, search, c }, ctx) => {
96
102
  const st = await (0, promises_1.stat)(source);
97
103
  const folder = st.isDirectory();
98
104
  const { ctime, mtime } = st;
99
- const pl = node.can_list === vfs_1.WHO_NO_ONE ? 'l'
105
+ const pl = node.can_list === misc_1.WHO_NO_ONE ? 'l'
100
106
  : !(0, vfs_1.hasPermission)(node, 'can_list', ctx) ? 'L'
101
107
  : '';
102
108
  // no download here, but maybe inside?
103
- const pr = node.can_read === vfs_1.WHO_NO_ONE && !(folder && filesInsideCould()) ? 'r'
109
+ const pr = node.can_read === misc_1.WHO_NO_ONE && !(folder && filesInsideCould()) ? 'r'
104
110
  : !(0, vfs_1.hasPermission)(node, 'can_read', ctx) ? 'R'
105
111
  : '';
106
112
  const pd = !can_delete && (0, vfs_1.hasPermission)(node, 'can_delete', ctx) ? 'd' : '';
@@ -109,7 +115,8 @@ const get_file_list = async ({ uri, offset, limit, search, c }, ctx) => {
109
115
  c: ctime,
110
116
  m: Math.abs(+mtime - +ctime) < 1000 ? undefined : mtime,
111
117
  s: folder ? undefined : st.size,
112
- p: (pr + pl + pd) || undefined
118
+ p: (pr + pl + pd) || undefined,
119
+ comment: await (0, comments_1.getCommentFor)(source),
113
120
  };
114
121
  }
115
122
  catch (_a) {
package/src/api.net.js CHANGED
@@ -4,12 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.acmeMiddleware = exports.externalIp = void 0;
7
+ exports.makeCert = exports.acme_email = exports.acme_domain = exports.acmeMiddleware = exports.externalIp = void 0;
8
8
  const apiMiddleware_1 = require("./apiMiddleware");
9
9
  const nat_upnp_ts_1 = require("nat-upnp-ts");
10
10
  const const_1 = require("./const");
11
- const axios_1 = __importDefault(require("axios"));
12
- const node_html_parser_1 = require("node-html-parser");
13
11
  const lodash_1 = __importDefault(require("lodash"));
14
12
  const listen_1 = require("./listen");
15
13
  const github_1 = require("./github");
@@ -20,53 +18,59 @@ const acme_client_1 = __importDefault(require("acme-client"));
20
18
  const promises_1 = __importDefault(require("fs/promises"));
21
19
  const http_1 = require("http");
22
20
  const promises_2 = require("dns/promises");
23
- const client = new nat_upnp_ts_1.Client({ timeout: 4000 });
24
- const originalMethod = client.getGateway;
21
+ const config_1 = require("./config");
22
+ const events_1 = __importDefault(require("./events"));
23
+ const net_1 = require("net");
24
+ const upnpClient = new nat_upnp_ts_1.Client({ timeout: 4000 });
25
+ const originalMethod = upnpClient.getGateway;
25
26
  // other client methods call getGateway too, so this will ensure they reuse this same result
26
- client.getGateway = (0, misc_1.debounceAsync)(() => originalMethod.apply(client), 0, { retain: misc_1.HOUR, retainFailure: 30000 });
27
- client.getGateway().catch(() => { });
28
- exports.externalIp = Promise.resolve(''); // poll external ip
29
- (0, misc_1.repeat)(10 * misc_1.MINUTE, () => {
30
- const was = exports.externalIp;
31
- exports.externalIp = client.getPublicIp().catch(() => was); //fallback to previous value
32
- });
27
+ upnpClient.getGateway = (0, misc_1.debounceAsync)(() => originalMethod.apply(upnpClient), 0, { retain: misc_1.HOUR, retainFailure: 30000 });
28
+ upnpClient.getGateway().catch(() => { });
29
+ exports.externalIp = ''; // poll external ip
30
+ (0, misc_1.repeat)(10 * misc_1.MINUTE, () => upnpClient.getPublicIp().then(v => exports.externalIp = v));
33
31
  const getNatInfo = (0, misc_1.debounceAsync)(async () => {
34
32
  var _a, _b;
35
- const gettingIp = getPublicIp(); // don't wait, do it in parallel
36
- const res = await client.getGateway().catch(() => null);
33
+ const gettingIps = getPublicIps(); // don't wait, do it in parallel
34
+ const res = await upnpClient.getGateway().catch(() => null);
37
35
  const status = await (0, listen_1.getServerStatus)();
38
- const mappings = res && await (0, misc_1.haveTimeout)(5000, client.getMappings()).catch(() => null);
39
- console.debug('mappings found', mappings);
40
- const gatewayIp = res ? new URL(res.gateway.description).hostname : await findGateway().catch(() => null);
36
+ const mappings = res && await (0, misc_1.haveTimeout)(5000, upnpClient.getMappings()).catch(() => null);
37
+ console.debug('mappings found', mappings === null || mappings === void 0 ? void 0 : mappings.map(x => x.description));
38
+ const gatewayIp = res ? new URL(res.gateway.description).hostname : await findGateway().catch(() => undefined);
41
39
  const localIp = (res === null || res === void 0 ? void 0 : res.address) || (await (0, listen_1.getIps)())[0];
42
- const internalPort = ((_a = status === null || status === void 0 ? void 0 : status.https) === null || _a === void 0 ? void 0 : _a.listening) && status.https.port || ((_b = status === null || status === void 0 ? void 0 : status.http) === null || _b === void 0 ? void 0 : _b.listening) && status.http.port;
40
+ const internalPort = ((_a = status === null || status === void 0 ? void 0 : status.https) === null || _a === void 0 ? void 0 : _a.listening) && status.https.port || ((_b = status === null || status === void 0 ? void 0 : status.http) === null || _b === void 0 ? void 0 : _b.listening) && status.http.port || undefined;
43
41
  const mapped = lodash_1.default.find(mappings, x => x.private.host === localIp && x.private.port === internalPort);
44
42
  return {
45
43
  upnp: Boolean(res),
46
44
  localIp,
47
45
  gatewayIp,
48
- publicIp: await gettingIp || await exports.externalIp,
49
- externalIp: await exports.externalIp,
46
+ publicIps: await gettingIps,
47
+ externalIp: exports.externalIp,
50
48
  mapped,
51
49
  internalPort,
52
50
  externalPort: mapped === null || mapped === void 0 ? void 0 : mapped.public.port,
53
51
  };
54
52
  });
55
- async function getPublicIp() {
56
- const prjInfo = await (0, github_1.getProjectInfo)();
57
- for (const urls of lodash_1.default.chunk(lodash_1.default.shuffle(prjInfo.publicIpServices), 2)) // small parallelization
58
- try {
59
- return await Promise.any(urls.map(url => (0, util_http_1.httpString)(url).then(res => {
60
- var _a;
61
- const ip = (_a = res.body) === null || _a === void 0 ? void 0 : _a.trim();
62
- if (!/[.:0-9a-fA-F]/.test(ip))
63
- throw Error("bad result: " + ip);
64
- return ip;
65
- })));
66
- }
67
- catch (e) {
68
- console.debug(String(e));
69
- }
53
+ async function getPublicIps() {
54
+ const res = await (0, github_1.getProjectInfo)();
55
+ const groupedByVersion = Object.values(lodash_1.default.groupBy(res.publicIpServices, x => { var _a; return (_a = x.v) !== null && _a !== void 0 ? _a : 4; }));
56
+ const ips = await (0, misc_1.promiseBestEffort)(groupedByVersion.map(singleVersion => Promise.any(singleVersion.map(async (svc) => {
57
+ if (typeof svc === 'string')
58
+ svc = { type: 'http', url: svc };
59
+ console.debug("trying ip service", svc.url || svc.name);
60
+ if (svc.type === 'http')
61
+ return (0, util_http_1.httpString)(svc.url);
62
+ if (svc.type !== 'dns')
63
+ throw "unsupported";
64
+ const resolver = new promises_2.Resolver({ timeout: 2000 });
65
+ resolver.setServers(svc.ips);
66
+ return resolver.resolve(svc.name, svc.dnsRecord);
67
+ }).map(async (ret) => {
68
+ const validIps = (0, misc_1.wantArray)(await ret).map(x => x.trim()).filter(net_1.isIP);
69
+ if (!validIps.length)
70
+ throw "no good";
71
+ return validIps;
72
+ }))));
73
+ return lodash_1.default.uniq(ips.flat());
70
74
  }
71
75
  function findGateway() {
72
76
  return new Promise((resolve, reject) => (0, child_process_1.exec)(const_1.IS_WINDOWS || const_1.IS_MAC ? 'netstat -rn' : 'route -n', (err, out) => {
@@ -105,12 +109,16 @@ async function checkDomain(domain) {
105
109
  (0, promises_2.lookup)(domain).then(x => [x.address]),
106
110
  ]);
107
111
  // merge all results
108
- const ips = lodash_1.default.uniq((0, misc_1.onlyTruthy)(settled.map(x => x.status === 'fulfilled' && x.value)).flat());
109
- if (!ips.length)
112
+ const domainIps = lodash_1.default.uniq((0, misc_1.onlyTruthy)(settled.map(x => x.status === 'fulfilled' && x.value)).flat());
113
+ if (!domainIps.length)
110
114
  throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, "domain not working");
111
- const { publicIp } = await getNatInfo(); // do this before stopping the server
112
- if (!ips.includes(publicIp))
113
- throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, `please configure your domain to point to ${publicIp} (currently on ${ips[0]}) --- a change can take hours to be effective`);
115
+ const { publicIps } = await getNatInfo(); // do this before stopping the server
116
+ for (const v6 of [false, true]) {
117
+ const domainIpsThisVersion = domainIps.filter(x => (0, net_1.isIPv6)(x) === v6);
118
+ const ipsThisVersion = publicIps.filter(x => (0, net_1.isIPv6)(x) === v6);
119
+ if (domainIpsThisVersion.length && ipsThisVersion.length && !lodash_1.default.intersection(domainIpsThisVersion, ipsThisVersion).length)
120
+ throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, `configure your domain to point to ${ipsThisVersion} (currently on ${domainIpsThisVersion[0]}) – a change can take hours to be effective`);
121
+ }
114
122
  }
115
123
  async function generateSSLCert(domain, email) {
116
124
  await checkDomain(domain);
@@ -129,19 +137,19 @@ async function generateSSLCert(domain, email) {
129
137
  let check = await checkPort(domain, 80); // some check services may not consider the domain, but we already verified that
130
138
  if (check && !check.success && upnp && externalPort !== 80) { // consider a short-lived mapping
131
139
  // @ts-ignore
132
- await client.createMapping({ private: 80, public: { host: '', port: 80 }, description: 'hfs temporary', ttl: 30 }).catch(() => { });
140
+ await upnpClient.createMapping({ private: 80, public: { host: '', port: 80 }, description: 'hfs temporary', ttl: 30 }).catch(() => { });
133
141
  check = await checkPort(domain, 80); // repeat test
134
142
  }
135
143
  if (!check)
136
144
  throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, "couldn't test port 80");
137
145
  if (!check.success)
138
146
  throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, "port 80 is not working on the specified domain");
139
- const client = new acme_client_1.default.Client({
147
+ const acmeClient = new acme_client_1.default.Client({
140
148
  accountKey: await acme_client_1.default.crypto.createPrivateKey(),
141
149
  directoryUrl: acme_client_1.default.directory.letsencrypt.production
142
150
  });
143
151
  const [key, csr] = await acme_client_1.default.crypto.createCsr({ commonName: domain });
144
- const cert = await client.auto({
152
+ const cert = await acmeClient.auto({
145
153
  csr,
146
154
  email,
147
155
  challengePriority: ['http-01'],
@@ -166,28 +174,26 @@ async function generateSSLCert(domain, email) {
166
174
  }
167
175
  async function checkPort(ip, port) {
168
176
  const prjInfo = await (0, github_1.getProjectInfo)();
177
+ console.log(`checking server ${ip}:${port}`);
169
178
  for (const services of lodash_1.default.chunk(lodash_1.default.shuffle(prjInfo.checkServerServices), 2)) {
170
179
  try {
171
- return Promise.any(services.map(async (svc) => {
172
- var _a, _b;
173
- const service = new URL(svc.url).hostname;
180
+ return Promise.any(services.map(async ({ url, body, selector, regexpSuccess, regexpFailure, ...rest }) => {
181
+ const service = new URL(url).hostname;
174
182
  console.log('trying service', service);
175
- const api = axios_1.default[svc.method];
176
- const body = ((_a = svc.body) === null || _a === void 0 ? void 0 : _a.replace('$IP', ip).replace('$PORT', String(port))) || '';
177
- const res = await api(svc.url, body, { headers: svc.headers });
178
- const parsed = (_b = (0, node_html_parser_1.parse)(res.data).querySelector(svc.selector)) === null || _b === void 0 ? void 0 : _b.innerText;
179
- if (!parsed)
180
- throw console.debug('empty:' + service);
181
- const success = new RegExp(svc.regexpSuccess).test(parsed);
182
- const failure = new RegExp(svc.regexpFailure).test(parsed);
183
+ const res = await (0, util_http_1.httpString)(applySymbols(url), { family: (0, net_1.isIPv6)(ip) ? 6 : 4, body: applySymbols(body), ...rest });
184
+ const success = new RegExp(regexpSuccess).test(res);
185
+ const failure = new RegExp(regexpFailure).test(res);
183
186
  if (success === failure)
184
187
  throw console.debug('inconsistent:' + service); // this result cannot be trusted
185
188
  console.debug(service, 'responded', success);
186
- return { success, service };
189
+ return { success, service, ip, port };
187
190
  }));
188
191
  }
189
192
  catch (_a) { }
190
193
  }
194
+ function applySymbols(s) {
195
+ return s === null || s === void 0 ? void 0 : s.replace('$IP', ip).replace('$PORT', String(port));
196
+ }
191
197
  }
192
198
  const apis = {
193
199
  get_nat: getNatInfo,
@@ -203,40 +209,59 @@ const apis = {
203
209
  return new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, 'no internal port');
204
210
  if (externalPort)
205
211
  try {
206
- await client.removeMapping({ public: { host: '', port: externalPort } });
212
+ await upnpClient.removeMapping({ public: { host: '', port: externalPort } });
207
213
  }
208
214
  catch (e) {
209
215
  return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, 'removeMapping failed: ' + String(e));
210
216
  }
211
217
  if (external) // must use the object form of 'public' to work around a bug of the library
212
- await client.createMapping({ private: internal || internalPort, public: { host: '', port: external }, description: 'hfs', ttl: 0 });
218
+ await upnpClient.createMapping({ private: internal || internalPort, public: { host: '', port: external }, description: 'hfs', ttl: 0 });
213
219
  return {};
214
220
  },
215
221
  async check_server({ port }) {
216
- const { publicIp, internalPort, externalPort } = await getNatInfo();
217
- if (!publicIp)
218
- return new apiMiddleware_1.ApiError(const_1.HTTP_SERVICE_UNAVAILABLE, 'cannot detect public ip');
222
+ const { publicIps, internalPort, externalPort } = await getNatInfo();
223
+ if (!publicIps.length)
224
+ return new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, 'cannot detect public ip');
219
225
  if (!internalPort)
220
226
  return new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, 'no internal port');
221
227
  port || (port = externalPort || internalPort);
222
- console.log(`checking server ${publicIp}:${port}`);
223
- return await checkPort(publicIp, port)
224
- || new apiMiddleware_1.ApiError(const_1.HTTP_SERVICE_UNAVAILABLE);
228
+ const res = await (0, misc_1.promiseBestEffort)(publicIps.map(ip => checkPort(ip, port)));
229
+ return res.length ? res : new apiMiddleware_1.ApiError(const_1.HTTP_SERVICE_UNAVAILABLE);
225
230
  },
226
231
  async make_cert({ domain, email }) {
227
- if (!domain)
228
- return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad params');
229
- const res = await generateSSLCert(domain, email);
230
- const CERT_FILE = 'acme.cert';
231
- const KEY_FILE = 'acme.key';
232
- await promises_1.default.writeFile(CERT_FILE, res.cert);
233
- await promises_1.default.writeFile(KEY_FILE, res.key);
234
- listen_1.cert.set(CERT_FILE); // update config
235
- listen_1.privateKey.set(KEY_FILE);
232
+ await (0, exports.makeCert)(domain, email);
236
233
  return {};
237
234
  },
238
235
  get_cert() {
239
236
  return (0, misc_1.objSameKeys)(lodash_1.default.pick((0, listen_1.getCertObject)(), ['subject', 'issuer', 'validFrom', 'validTo']), v => v);
240
237
  }
241
238
  };
239
+ exports.acme_domain = (0, config_1.defineConfig)('acme_domain', '');
240
+ exports.acme_email = (0, config_1.defineConfig)('acme_email', '');
241
+ exports.makeCert = (0, misc_1.debounceAsync)(async (domain, email) => {
242
+ if (!domain)
243
+ return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad params');
244
+ const res = await generateSSLCert(domain, email);
245
+ const CERT_FILE = 'acme.cert';
246
+ const KEY_FILE = 'acme.key';
247
+ await promises_1.default.writeFile(CERT_FILE, res.cert);
248
+ await promises_1.default.writeFile(KEY_FILE, res.key);
249
+ listen_1.cert.set(CERT_FILE); // update config
250
+ listen_1.privateKey.set(KEY_FILE);
251
+ }, 0);
252
+ (0, config_1.defineConfig)('acme_renew', false); // handle config changes
253
+ events_1.default.once('https ready', () => (0, misc_1.repeat)(misc_1.HOUR, renewCert));
254
+ // checks if the cert is near expiration date, and if so renews it
255
+ const renewCert = (0, misc_1.debounceAsync)(async () => {
256
+ const cert = (0, listen_1.getCertObject)();
257
+ if (!cert)
258
+ return;
259
+ const now = new Date();
260
+ const validTo = new Date(cert.validTo);
261
+ // not expiring in a month
262
+ if (now > new Date(cert.validFrom) && now < validTo && validTo.getTime() - now.getTime() >= 30 * misc_1.DAY)
263
+ return console.log("certificate still good");
264
+ await (0, exports.makeCert)(exports.acme_domain.get(), exports.acme_email.get())
265
+ .catch(e => console.log("error renewing certificate: ", String(e)));
266
+ }, 0, { retain: misc_1.DAY, retainFailure: misc_1.HOUR });
242
267
  exports.default = apis;
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.checkDependencies = void 0;
7
8
  const plugins_1 = require("./plugins");
8
9
  const lodash_1 = __importDefault(require("lodash"));
9
10
  const assert_1 = __importDefault(require("assert"));
@@ -15,7 +16,7 @@ const github_1 = require("./github");
15
16
  const const_1 = require("./const");
16
17
  const apis = {
17
18
  get_plugins({}, ctx) {
18
- const list = new apiMiddleware_1.SendListReadable({ addAtStart: [...(0, plugins_1.mapPlugins)(serialize), ...(0, plugins_1.getAvailablePlugins)().map(serialize)] });
19
+ const list = new apiMiddleware_1.SendListReadable({ addAtStart: [...(0, plugins_1.mapPlugins)(serialize, false), ...(0, plugins_1.getAvailablePlugins)().map(serialize)] });
19
20
  return list.events(ctx, {
20
21
  pluginInstalled: p => list.add(serialize(p)),
21
22
  'pluginStarted pluginStopped pluginUpdated': p => {
@@ -28,7 +29,8 @@ const apis = {
28
29
  async get_plugin_updates() {
29
30
  return new apiMiddleware_1.SendListReadable({
30
31
  async doAtStart(list) {
31
- const errs = await Promise.all(lodash_1.default.map((0, github_1.getFolder2repo)(), async (repo, folder) => {
32
+ const errs = [];
33
+ await Promise.allSettled(lodash_1.default.map((0, github_1.getFolder2repo)(), async (repo, folder) => {
32
34
  try {
33
35
  if (!repo)
34
36
  return;
@@ -46,12 +48,11 @@ const apis = {
46
48
  }
47
49
  }
48
50
  catch (err) {
49
- if (err.message === '404') // the plugin is declaring a wrong repo
50
- return;
51
- return err.code || err.message;
51
+ if (err.message !== '404') // the plugin is declaring a wrong repo
52
+ errs.push(err.code || err.message);
52
53
  }
53
54
  }));
54
- for (const x of lodash_1.default.uniq((0, misc_1.onlyTruthy)(errs)))
55
+ for (const x of lodash_1.default.uniq(errs))
55
56
  list.error(x);
56
57
  list.close();
57
58
  }
@@ -124,18 +125,16 @@ const apis = {
124
125
  });
125
126
  },
126
127
  async download_plugin({ id, branch }) {
127
- await checkDependencies(id, branch);
128
- const res = await (0, github_1.downloadPlugin)(id, { branch });
129
- if (typeof res !== 'string')
130
- return res;
131
- return (await (0, misc_1.waitFor)(() => (0, plugins_1.getPluginInfo)(res), { timeout: 5000 }))
128
+ await checkDependencies(await (0, github_1.readOnlinePlugin)(id, branch));
129
+ const folder = await (0, github_1.downloadPlugin)(id, { branch });
130
+ return (await (0, misc_1.waitFor)(() => (0, plugins_1.getPluginInfo)(folder), { timeout: 5000 }))
132
131
  || new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
133
132
  },
134
133
  async update_plugin({ id }) {
135
134
  const found = (0, plugins_1.getPluginInfo)(id);
136
135
  if (!found)
137
136
  return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
138
- await checkDependencies(found.repo);
137
+ await checkDependencies(found);
139
138
  const enabled = (0, plugins_1.isPluginEnabled)(id);
140
139
  await (0, plugins_1.stopPlugin)(id);
141
140
  await (0, github_1.downloadPlugin)(found.repo, { overwrite: true });
@@ -157,19 +156,9 @@ function serialize(p) {
157
156
  o.repo = o.repo.web;
158
157
  return lodash_1.default.defaults(o, { started: null, badApi: null }); // nulls should be used to be sure to overwrite previous values,
159
158
  }
160
- async function checkDependencies(repo, branch) {
161
- const rec = await (0, github_1.readOnlinePlugin)(repo, branch);
162
- if (!rec)
163
- return;
164
- const miss = rec.depend && rec.depend.map((dep) => {
165
- const res = (0, plugins_1.findPluginByRepo)(dep.repo);
166
- const error = !res ? 'missing'
167
- : (res.version || 0) < dep.version ? 'version'
168
- : !(0, plugins_1.isPluginEnabled)(res.id) ? 'disabled'
169
- : !(0, plugins_1.isPluginRunning)(res.id) ? 'stopped'
170
- : '';
171
- return error && { repo: dep.repo, error, id: res === null || res === void 0 ? void 0 : res.id };
172
- }).filter(Boolean);
173
- if (miss === null || miss === void 0 ? void 0 : miss.length)
159
+ async function checkDependencies(plugin) {
160
+ const miss = await (0, plugins_1.getMissingDependencies)(plugin);
161
+ if (miss.length)
174
162
  throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, miss);
175
163
  }
164
+ exports.checkDependencies = checkDependencies;
package/src/api.vfs.js CHANGED
@@ -19,10 +19,7 @@ async function urlToNodeOriginal(uri) {
19
19
  }
20
20
  const apis = {
21
21
  async get_vfs() {
22
- return {
23
- root: await recur(),
24
- defaultPerms: vfs_1.defaultPerms,
25
- };
22
+ return { root: await recur() };
26
23
  async function recur(node = vfs_1.vfs) {
27
24
  var _a;
28
25
  const { source } = node;
@@ -32,19 +29,19 @@ const apis = {
32
29
  : { size: source ? -1 : undefined };
33
30
  if (copyStats.mtime && Number(copyStats.mtime) === Number(copyStats.ctime))
34
31
  delete copyStats.mtime;
35
- let byMasks = node.original && lodash_1.default.pickBy(node, (v, k) => v !== node.original[k] // something is changing me...
36
- && v !== node.parent[k] // ...and it's not inheritance...
37
- && vfs_1.PERM_KEYS.includes(k)); // ...must be masks. Please limit this to perms
38
- if (lodash_1.default.isEmpty(byMasks))
39
- byMasks = undefined;
32
+ const inherited = node.parent && (0, vfs_1.permsFromParent)(node.parent, node.original || node);
33
+ const byMasks = node.original && lodash_1.default.pickBy(node, (v, k) => v !== node.original[k] // something is changing me...
34
+ && !(inherited && k in inherited) // ...and it's not inheritance...
35
+ && misc_1.PERM_KEYS.includes(k)); // ...must be masks. Please limit this to perms
40
36
  return {
41
37
  ...copyStats,
42
38
  ...node.original || node,
43
- byMasks,
39
+ inherited,
40
+ byMasks: lodash_1.default.isEmpty(byMasks) ? undefined : byMasks,
44
41
  website: Boolean((_a = node.children) === null || _a === void 0 ? void 0 : _a.find((0, vfs_1.isSameFilenameAs)('index.html')))
45
42
  || isDir && source && await (0, promises_1.stat)((0, path_1.join)(source, 'index.html')).then(() => true, () => undefined)
46
43
  || undefined,
47
- name: node === vfs_1.vfs ? undefined : (0, vfs_1.getNodeName)(node),
44
+ name: node === vfs_1.vfs ? '' : (0, vfs_1.getNodeName)(node),
48
45
  type: isDir ? 'folder' : undefined,
49
46
  children: node.children && await Promise.all(node.children.map(child => recur((0, vfs_1.applyParentToChild)(child, node))))
50
47
  };
@@ -78,7 +75,7 @@ const apis = {
78
75
  const n = await urlToNodeOriginal(uri);
79
76
  if (!n)
80
77
  return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'path not found');
81
- props = pickProps(props, ['name', 'source', 'masks', 'default', 'accept', ...vfs_1.PERM_KEYS]); // sanitize
78
+ props = pickProps(props, ['name', 'source', 'masks', 'default', 'accept', ...misc_1.PERM_KEYS]); // sanitize
82
79
  if (props.name && props.name !== (0, vfs_1.getNodeName)(n)) {
83
80
  const parent = await urlToNodeOriginal((0, path_1.dirname)(uri));
84
81
  if ((_a = parent === null || parent === void 0 ? void 0 : parent.children) === null || _a === void 0 ? void 0 : _a.find(x => (0, vfs_1.getNodeName)(x) === props.name))