hfs 0.48.2 → 0.49.0-alpha1

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.
@@ -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{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)}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}button:hover{outline:1px solid var(--mild-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-comment{display:inline}ul.dir li .entry-comment:before,ul.dir li .entry-comment:after{font-size:1.5em;font-family:serif;line-height:1px;position:relative}ul.dir li .entry-comment:before{content:open-quote;margin-left:.5em;margin-right:.1em;top:.2em}ul.dir li .entry-comment:after{content:"„";top:-.1em;margin-left:.1em}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}#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 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}.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-4cbc2ffc.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-4cbc2ffc.js"></script>
9
+ <link rel="stylesheet" href="/assets/index-cbe043ee.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.2",
3
+ "version": "0.49.0-alpha1",
4
4
  "description": "HTTP File Server",
5
5
  "keywords": [
6
6
  "file server",
@@ -16,7 +16,7 @@
16
16
  "start-frontend": "npm run start --workspace=frontend",
17
17
  "start-admin": "npm run start --workspace=admin",
18
18
  "build-all": "npm audit --omit=dev && rm -rf dist && npm i && npm run build-server && npm run build-frontend && npm run build-admin && echo COMPLETED",
19
- "build-server": "rm -rf dist/src dist/plugins && tsc --target es2018 && touch package.json && cp -v -r package.json README* LICENSE* plugins dist && find dist -name .DS_Store -delete && node afterbuild.js",
19
+ "build-server": "rm -rf dist/src dist/plugins && tsc --target es2018 && touch package.json && cp -v -r package.json central.json README* LICENSE* plugins dist && find dist -name .DS_Store -delete && node afterbuild.js",
20
20
  "build-frontend": "npm run build --workspace=frontend",
21
21
  "build-admin": "npm run build --workspace=admin",
22
22
  "server-for-test": "node dist/src --cwd . --config tests && rm custom.html",
@@ -78,7 +78,7 @@
78
78
  "limiter": "^2.1.0",
79
79
  "lodash": "^4.17.21",
80
80
  "minimist": "^1.2.6",
81
- "nat-upnp": "github:kaden-sharpin/node-nat-upnp",
81
+ "nat-upnp-ts": "^2.0.1",
82
82
  "node-html-parser": "^6.1.5",
83
83
  "open": "^8.4.0",
84
84
  "tssrp6a": "^3.0.0",
@@ -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)(),
@@ -12,6 +12,10 @@ 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 config_1 = require("./config");
16
+ const path_1 = require("path");
17
+ const DESCRIPT_ION = 'descript.ion';
18
+ const descriptIon = (0, config_1.defineConfig)('descript_ion', true);
15
19
  const get_file_list = async ({ uri, offset, limit, search, c }, ctx) => {
16
20
  var _a;
17
21
  const node = await (0, vfs_1.urlToNode)(uri || '/', ctx);
@@ -57,14 +61,19 @@ const get_file_list = async ({ uri, offset, limit, search, c }, ctx) => {
57
61
  for await (const sub of walker) {
58
62
  if (ctx.aborted)
59
63
  break;
60
- if (!filter((0, vfs_1.getNodeName)(sub)))
64
+ const name = (0, vfs_1.getNodeName)(sub);
65
+ if (descriptIon.get() && name === DESCRIPT_ION)
66
+ continue;
67
+ if (!filter(name))
61
68
  continue;
62
69
  const entry = await nodeToDirEntry(ctx, sub);
63
70
  if (!entry)
64
71
  continue;
72
+ entry.comment = await getCommentFor(sub.source);
65
73
  const cbParams = { entry, ctx, listUri: uri, node: sub };
66
74
  try {
67
- if (onDirEntryHandlers.some(cb => cb(cbParams) === false))
75
+ const res = await Promise.all(onDirEntryHandlers.map(cb => cb(cbParams)));
76
+ if (res.some(x => x === false))
68
77
  continue;
69
78
  }
70
79
  catch (e) {
@@ -123,3 +132,18 @@ const get_file_list = async ({ uri, offset, limit, search, c }, ctx) => {
123
132
  }
124
133
  };
125
134
  exports.get_file_list = get_file_list;
135
+ async function getCommentFor(path) {
136
+ return !path || !descriptIon.get() ? undefined
137
+ : readDescription((0, path_1.dirname)(path)).then(x => x.get((0, misc_1.basename)(path)), () => undefined);
138
+ }
139
+ function readDescription(path) {
140
+ return (0, misc_1.parseFile)((0, path_1.join)(path, DESCRIPT_ION), txt => new Map(txt.split('\n').map(line => {
141
+ const quoted = line[0] === '"' ? 1 : 0;
142
+ const i = quoted ? line.indexOf('"', 2) : line.indexOf(' ');
143
+ const fn = line.slice(quoted, i - quoted);
144
+ let comment = line.slice(i + 1);
145
+ if (comment.endsWith('\x04\xc2'))
146
+ comment = comment.slice(0, -2).replaceAll('\\n', '\n');
147
+ return [fn, comment];
148
+ })));
149
+ }
package/src/api.net.js CHANGED
@@ -4,9 +4,9 @@ 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
- const nat_upnp_1 = require("nat-upnp");
9
+ const nat_upnp_ts_1 = require("nat-upnp-ts");
10
10
  const const_1 = require("./const");
11
11
  const axios_1 = __importDefault(require("axios"));
12
12
  const node_html_parser_1 = require("node-html-parser");
@@ -20,22 +20,24 @@ const acme_client_1 = __importDefault(require("acme-client"));
20
20
  const promises_1 = __importDefault(require("fs/promises"));
21
21
  const http_1 = require("http");
22
22
  const promises_2 = require("dns/promises");
23
- const client = new nat_upnp_1.Client({ timeout: 4000 });
24
- const originalMethod = client.getGateway;
23
+ const config_1 = require("./config");
24
+ const events_1 = __importDefault(require("./events"));
25
+ const upnpClient = new nat_upnp_ts_1.Client({ timeout: 4000 });
26
+ const originalMethod = upnpClient.getGateway;
25
27
  // 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
+ upnpClient.getGateway = (0, misc_1.debounceAsync)(() => originalMethod.apply(upnpClient), 0, { retain: misc_1.HOUR, retainFailure: 30000 });
29
+ upnpClient.getGateway().catch(() => { });
28
30
  exports.externalIp = Promise.resolve(''); // poll external ip
29
31
  (0, misc_1.repeat)(10 * misc_1.MINUTE, () => {
30
32
  const was = exports.externalIp;
31
- exports.externalIp = client.getPublicIp().catch(() => was); //fallback to previous value
33
+ exports.externalIp = upnpClient.getPublicIp().catch(() => was); //fallback to previous value
32
34
  });
33
35
  const getNatInfo = (0, misc_1.debounceAsync)(async () => {
34
36
  var _a, _b;
35
37
  const gettingIp = getPublicIp(); // don't wait, do it in parallel
36
- const res = await client.getGateway().catch(() => null);
38
+ const res = await upnpClient.getGateway().catch(() => null);
37
39
  const status = await (0, listen_1.getServerStatus)();
38
- const mappings = res && await (0, misc_1.haveTimeout)(5000, client.getMappings()).catch(() => null);
40
+ const mappings = res && await (0, misc_1.haveTimeout)(5000, upnpClient.getMappings()).catch(() => null);
39
41
  console.debug('mappings found', mappings);
40
42
  const gatewayIp = res ? new URL(res.gateway.description).hostname : await findGateway().catch(() => null);
41
43
  const localIp = (res === null || res === void 0 ? void 0 : res.address) || (await (0, listen_1.getIps)())[0];
@@ -129,19 +131,19 @@ async function generateSSLCert(domain, email) {
129
131
  let check = await checkPort(domain, 80); // some check services may not consider the domain, but we already verified that
130
132
  if (check && !check.success && upnp && externalPort !== 80) { // consider a short-lived mapping
131
133
  // @ts-ignore
132
- await client.createMapping({ private: 80, public: { host: '', port: 80 }, description: 'hfs temporary', ttl: 30 }).catch(() => { });
134
+ await upnpClient.createMapping({ private: 80, public: { host: '', port: 80 }, description: 'hfs temporary', ttl: 30 }).catch(() => { });
133
135
  check = await checkPort(domain, 80); // repeat test
134
136
  }
135
137
  if (!check)
136
138
  throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, "couldn't test port 80");
137
139
  if (!check.success)
138
140
  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({
141
+ const acmeClient = new acme_client_1.default.Client({
140
142
  accountKey: await acme_client_1.default.crypto.createPrivateKey(),
141
143
  directoryUrl: acme_client_1.default.directory.letsencrypt.production
142
144
  });
143
145
  const [key, csr] = await acme_client_1.default.crypto.createCsr({ commonName: domain });
144
- const cert = await client.auto({
146
+ const cert = await acmeClient.auto({
145
147
  csr,
146
148
  email,
147
149
  challengePriority: ['http-01'],
@@ -203,13 +205,13 @@ const apis = {
203
205
  return new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, 'no internal port');
204
206
  if (externalPort)
205
207
  try {
206
- await client.removeMapping({ public: { host: '', port: externalPort } });
208
+ await upnpClient.removeMapping({ public: { host: '', port: externalPort } });
207
209
  }
208
210
  catch (e) {
209
211
  return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, 'removeMapping failed: ' + String(e));
210
212
  }
211
213
  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 });
214
+ await upnpClient.createMapping({ private: internal || internalPort, public: { host: '', port: external }, description: 'hfs', ttl: 0 });
213
215
  return {};
214
216
  },
215
217
  async check_server({ port }) {
@@ -224,19 +226,41 @@ const apis = {
224
226
  || new apiMiddleware_1.ApiError(const_1.HTTP_SERVICE_UNAVAILABLE);
225
227
  },
226
228
  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);
229
+ await (0, exports.makeCert)(domain, email);
236
230
  return {};
237
231
  },
238
232
  get_cert() {
239
233
  return (0, misc_1.objSameKeys)(lodash_1.default.pick((0, listen_1.getCertObject)(), ['subject', 'issuer', 'validFrom', 'validTo']), v => v);
240
234
  }
241
235
  };
236
+ exports.acme_domain = (0, config_1.defineConfig)('acme_domain', '');
237
+ exports.acme_email = (0, config_1.defineConfig)('acme_email', '');
238
+ exports.makeCert = (0, misc_1.debounceAsync)(async (domain, email) => {
239
+ if (!domain)
240
+ return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad params');
241
+ const res = await generateSSLCert(domain, email);
242
+ const CERT_FILE = 'acme.cert';
243
+ const KEY_FILE = 'acme.key';
244
+ await promises_1.default.writeFile(CERT_FILE, res.cert);
245
+ await promises_1.default.writeFile(KEY_FILE, res.key);
246
+ listen_1.cert.set(CERT_FILE); // update config
247
+ listen_1.privateKey.set(KEY_FILE);
248
+ }, 0);
249
+ (0, config_1.defineConfig)('acme_renew', false); // handle config changes
250
+ events_1.default.once('https ready', () => (0, misc_1.repeat)(misc_1.HOUR, renewCert));
251
+ // checks if the cert is near expiration date, and if so renews it
252
+ const renewCert = (0, misc_1.debounceAsync)(async () => {
253
+ const acmeLog = (...args) => console.log('[acme-renew]:', ...args);
254
+ const now = new Date();
255
+ const cert = (0, listen_1.getCertObject)();
256
+ if (!cert)
257
+ return;
258
+ const validTo = new Date(cert.validTo);
259
+ const isValid = now > new Date(cert.validFrom) && now < validTo &&
260
+ validTo.getTime() - now.getTime() >= 30 * misc_1.DAY; // it's not expiring in a month
261
+ if (isValid)
262
+ return acmeLog("certificate is good");
263
+ await (0, exports.makeCert)(exports.acme_domain.get(), exports.acme_email.get())
264
+ .catch(e => acmeLog("error: ", e.toString()));
265
+ }, 0, { retain: misc_1.DAY, retainFailure: misc_1.HOUR });
242
266
  exports.default = apis;
@@ -15,7 +15,7 @@ const github_1 = require("./github");
15
15
  const const_1 = require("./const");
16
16
  const apis = {
17
17
  get_plugins({}, ctx) {
18
- const list = new apiMiddleware_1.SendListReadable({ addAtStart: [...(0, plugins_1.mapPlugins)(serialize), ...(0, plugins_1.getAvailablePlugins)().map(serialize)] });
18
+ const list = new apiMiddleware_1.SendListReadable({ addAtStart: [...(0, plugins_1.mapPlugins)(serialize, false), ...(0, plugins_1.getAvailablePlugins)().map(serialize)] });
19
19
  return list.events(ctx, {
20
20
  pluginInstalled: p => list.add(serialize(p)),
21
21
  'pluginStarted pluginStopped pluginUpdated': p => {
package/src/commands.js CHANGED
@@ -106,7 +106,7 @@ const commands = {
106
106
  },
107
107
  update: {
108
108
  params: '',
109
- cb: update_1.update
109
+ cb: () => (0, update_1.update)()
110
110
  },
111
111
  'check-update': {
112
112
  params: '',
package/src/config.js CHANGED
@@ -89,11 +89,7 @@ function defineConfig(k, defaultValue, compiler) {
89
89
  else
90
90
  setConfig1(k, v);
91
91
  },
92
- compiled: () => {
93
- if (!compiler)
94
- throw "missing compiler";
95
- return compiled;
96
- }
92
+ compiled: () => compiled !== null && compiled !== void 0 ? compiled : (0, misc_1.throw_)("missing compiler"),
97
93
  };
98
94
  if (compiler)
99
95
  ret.sub((...args) => compiled = compiler(...args));
package/src/const.js CHANGED
@@ -38,7 +38,7 @@ exports.DEV = process.env.DEV || exports.argv.dev ? 'DEV' : '';
38
38
  exports.ORIGINAL_CWD = process.cwd();
39
39
  exports.HFS_STARTED = new Date();
40
40
  const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
41
- exports.BUILD_TIMESTAMP = "2023-09-20T22:00:36.550Z";
41
+ exports.BUILD_TIMESTAMP = "2023-09-24T12:10:10.944Z";
42
42
  const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
43
43
  exports.VERSION = pkg.version;
44
44
  exports.RUNNING_BETA = exports.VERSION.includes('-');
package/src/cross.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.xlate = exports.isEqualLax = exports.isWindowsDrive = exports.isIP = exports.isPrimitive = exports.formatTimestamp = exports.repeat = exports.asyncGeneratorToArray = exports.filterMapGenerator = exports.throw_ = exports.hasProp = exports.typedEntries = exports.typedKeys = exports.objRenameKey = exports.randomId = exports.getOrSet = exports.waitFor = exports.newObj = exports.removeStarting = exports.findDefined = exports.isOrderedEqual = exports.swap = exports.tryJson = exports.basename = exports.pendingPromise = exports._log = exports.wantArray = exports.formatPerc = exports.with_ = exports.try_ = exports.setHidden = exports.onlyTruthy = exports.truthy = exports.enforceFinal = exports.objSameKeys = exports.haveTimeout = exports.wait = exports.prefix = exports.formatBytes = exports.MAX_TILES_SIZE = exports.DAY = exports.HOUR = exports.MINUTE = exports.WIKI_URL = exports.REPO_URL = void 0;
6
+ exports.ipForUrl = exports.ipLocalHost = exports.xlate = exports.isEqualLax = exports.isWindowsDrive = exports.isIP = exports.isPrimitive = exports.formatTimestamp = exports.repeat = exports.asyncGeneratorToArray = exports.filterMapGenerator = exports.throw_ = exports.hasProp = exports.typedEntries = exports.typedKeys = exports.objRenameKey = exports.randomId = exports.getOrSet = exports.waitFor = exports.newObj = exports.removeStarting = exports.findDefined = exports.isOrderedEqual = exports.swap = exports.tryJson = exports.basename = exports.pendingPromise = exports._dbg = exports._log = exports.wantArray = exports.formatPerc = exports.with_ = exports.try_ = exports.setHidden = exports.onlyTruthy = exports.truthy = exports.enforceFinal = exports.objSameKeys = exports.haveTimeout = exports.wait = exports.prefix = exports.formatBytes = exports.MAX_TILES_SIZE = exports.DAY = exports.HOUR = exports.MINUTE = exports.WIKI_URL = exports.REPO_URL = void 0;
7
7
  // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
8
8
  // all content here is shared between client and server
9
9
  const lodash_1 = __importDefault(require("lodash"));
@@ -86,6 +86,11 @@ function _log(...args) {
86
86
  return args[args.length - 1];
87
87
  }
88
88
  exports._log = _log;
89
+ function _dbg(x) {
90
+ debugger;
91
+ return x;
92
+ }
93
+ exports._dbg = _dbg;
89
94
  function pendingPromise() {
90
95
  let takeOut;
91
96
  const ret = new Promise((resolve, reject) => takeOut = { resolve, reject });
@@ -225,7 +230,7 @@ async function asyncGeneratorToArray(generator) {
225
230
  }
226
231
  exports.asyncGeneratorToArray = asyncGeneratorToArray;
227
232
  function repeat(every, cb) {
228
- Promise.allSettled([cb()]).then(() => setTimeout(() => repeat(every, cb), every));
233
+ return Promise.allSettled([cb()]).then(() => setTimeout(() => repeat(every, cb), every));
229
234
  }
230
235
  exports.repeat = repeat;
231
236
  function formatTimestamp(x) {
@@ -255,3 +260,11 @@ function xlate(input, table) {
255
260
  return (_a = table[input]) !== null && _a !== void 0 ? _a : input;
256
261
  }
257
262
  exports.xlate = xlate;
263
+ function ipLocalHost(ip) {
264
+ return ip === '::1' || ip.endsWith('127.0.0.1');
265
+ }
266
+ exports.ipLocalHost = ipLocalHost;
267
+ function ipForUrl(ip) {
268
+ return ip.includes(':') ? '[' + ip + ']' : ip;
269
+ }
270
+ exports.ipForUrl = ipForUrl;
package/src/customHtml.js CHANGED
@@ -35,7 +35,7 @@ function watchLoadCustomHtml(folder = '') {
35
35
  exports.watchLoadCustomHtml = watchLoadCustomHtml;
36
36
  function getSection(name) {
37
37
  return (exports.customHtmlState.sections.get(name) || '')
38
- + (0, plugins_1.mapPlugins)(pl => pl.getData().customHtml.get(name)).join('\n');
38
+ + (0, plugins_1.mapPlugins)(pl => { var _a; return (_a = pl.getData().customHtml) === null || _a === void 0 ? void 0 : _a.get(name); }).join('\n');
39
39
  }
40
40
  exports.getSection = getSection;
41
41
  async function saveCustomHtml(sections) {
@@ -14,6 +14,7 @@ function debounceAsync(callback, wait = 100, options = {}) {
14
14
  let lastSince = 0;
15
15
  const interceptingWrapper = (...args) => runningDebouncer = debouncer(...args);
16
16
  return Object.assign(interceptingWrapper, {
17
+ clearRetain: () => last = undefined,
17
18
  flush: () => runningCallback !== null && runningCallback !== void 0 ? runningCallback : exec(),
18
19
  ...cancelable && {
19
20
  cancel() {
package/src/listen.js CHANGED
@@ -43,6 +43,7 @@ const adminApis_1 = require("./adminApis");
43
43
  const lodash_1 = __importDefault(require("lodash"));
44
44
  const crypto_1 = require("crypto");
45
45
  const api_net_1 = require("./api.net");
46
+ const events_1 = __importDefault(require("./events"));
46
47
  let httpSrv;
47
48
  let httpsSrv;
48
49
  const openBrowserAtStart = (0, config_1.defineConfig)('open_browser_at_start', !const_1.DEV);
@@ -52,13 +53,11 @@ function getHttpsWorkingPort() {
52
53
  }
53
54
  exports.getHttpsWorkingPort = getHttpsWorkingPort;
54
55
  const commonOptions = { requestTimeout: 0 };
55
- exports.portCfg = (0, config_1.defineConfig)('port', 80);
56
- exports.portCfg.sub(async (port) => {
57
- while (!index_1.app)
58
- await (0, misc_1.wait)(100);
56
+ const considerHttp = (0, misc_1.debounceAsync)(async () => {
57
+ await (0, misc_1.waitFor)(() => index_1.app);
59
58
  stopServer(httpSrv).then();
60
59
  httpSrv = Object.assign(http.createServer(commonOptions, index_1.app.callback()), { name: 'http' });
61
- port = await startServer(httpSrv, { port });
60
+ const port = await startServer(httpSrv, { port: exports.portCfg.get(), host: listenInterface.get() });
62
61
  if (!port)
63
62
  return;
64
63
  httpSrv.on('connection', connections_1.newConnection);
@@ -66,6 +65,10 @@ exports.portCfg.sub(async (port) => {
66
65
  if (openBrowserAtStart.get() && !const_1.argv.updated)
67
66
  openAdmin();
68
67
  });
68
+ exports.portCfg = (0, config_1.defineConfig)('port', 80);
69
+ const listenInterface = (0, config_1.defineConfig)('listen_interface', '');
70
+ exports.portCfg.sub(considerHttp);
71
+ listenInterface.sub(considerHttp);
69
72
  function openAdmin() {
70
73
  for (const srv of [httpSrv, httpsSrv]) {
71
74
  const a = srv === null || srv === void 0 ? void 0 : srv.address();
@@ -84,6 +87,8 @@ function openAdmin() {
84
87
  }
85
88
  exports.openAdmin = openAdmin;
86
89
  function getCertObject() {
90
+ if (!httpsOptions.cert)
91
+ return;
87
92
  const o = new crypto_1.X509Certificate(httpsOptions.cert);
88
93
  const some = lodash_1.default.pick(o, ['subject', 'issuer', 'validFrom', 'validTo']);
89
94
  return (0, misc_1.objSameKeys)(some, v => (v === null || v === void 0 ? void 0 : v.includes('=')) ? Object.fromEntries(v.split('\n').map(x => x.split('='))) : v);
@@ -99,6 +104,8 @@ const considerHttps = (0, misc_1.debounceAsync)(async () => {
99
104
  httpsSrv = Object.assign(https.createServer(port === PORT_DISABLED ? {} : { ...commonOptions, key: httpsOptions.private_key, cert: httpsOptions.cert }, index_1.app.callback()), { name: 'https' });
100
105
  if (port >= 0) {
101
106
  const cert = getCertObject();
107
+ if (!cert)
108
+ return;
102
109
  const cn = (_a = cert.subject) === null || _a === void 0 ? void 0 : _a.CN;
103
110
  if (cn)
104
111
  console.log("certificate loaded for", cn);
@@ -120,11 +127,12 @@ const considerHttps = (0, misc_1.debounceAsync)(async () => {
120
127
  console.log("failed to create https server: check your private key and certificate", String(e));
121
128
  return;
122
129
  }
123
- port = await startServer(httpsSrv, { port });
130
+ port = await startServer(httpsSrv, { port, host: listenInterface.get() });
124
131
  if (!port)
125
132
  return;
126
133
  httpsSrv.on('connection', connections_1.newConnection);
127
134
  printUrls(httpsSrv.name);
135
+ events_1.default.emit('https ready');
128
136
  });
129
137
  exports.cert = (0, config_1.defineConfig)('cert', '');
130
138
  exports.privateKey = (0, config_1.defineConfig)('private_key', '');
@@ -150,6 +158,7 @@ for (const cfg of httpsNeeds) {
150
158
  const PORT_DISABLED = -1;
151
159
  exports.httpsPortCfg = (0, config_1.defineConfig)('https_port', PORT_DISABLED);
152
160
  exports.httpsPortCfg.sub(considerHttps);
161
+ listenInterface.sub(considerHttps);
153
162
  function startServer(srv, { port, host }) {
154
163
  return new Promise(async (resolve) => {
155
164
  if (!srv)
@@ -194,6 +203,8 @@ function startServer(srv, { port, host }) {
194
203
  srv.error = String(e);
195
204
  srv.busy = undefined;
196
205
  const { code } = e;
206
+ if (code === 'EACCES' && port < 1024)
207
+ srv.error = `lacking permission on port ${port}, try with permission (${const_1.IS_WINDOWS ? 'administrator' : 'sudo'}) or port > 1024`;
197
208
  if (code === 'EADDRINUSE') {
198
209
  srv.busy = (0, find_process_1.default)('port', port).then(res => { var _a; return ((_a = res === null || res === void 0 ? void 0 : res[0]) === null || _a === void 0 ? void 0 : _a.name) || ''; }, () => '');
199
210
  srv.error = `port ${port} busy: ${await srv.busy || "unknown process"}`;
@@ -242,11 +253,11 @@ async function getServerStatus() {
242
253
  }
243
254
  exports.getServerStatus = getServerStatus;
244
255
  const ignore = /^(lo|.*loopback.*|virtualbox.*|.*\(wsl\).*|llw\d|awdl\d|utun\d|anpi\d)$/i; // avoid giving too much information
245
- async function getIps() {
256
+ async function getIps(external = true) {
246
257
  const ips = (0, misc_1.onlyTruthy)(Object.entries((0, os_1.networkInterfaces)()).map(([name, nets]) => nets && !ignore.test(name)
247
258
  && v4first((0, misc_1.onlyTruthy)(nets.map(net => !net.internal && net.address)))[0] // for each interface we consider only 1 address
248
259
  )).flat();
249
- const e = await api_net_1.externalIp;
260
+ const e = external && await api_net_1.externalIp;
250
261
  if (e && !ips.includes(e))
251
262
  ips.unshift(e);
252
263
  return v4first(ips)
@@ -257,21 +268,19 @@ async function getIps() {
257
268
  }
258
269
  exports.getIps = getIps;
259
270
  async function getUrls() {
260
- const ips = (await getIps()).map(ip => ip.includes(':') ? '[' + ip + ']' : ip);
271
+ const on = listenInterface.get();
272
+ const ips = on ? [on] : await getIps();
261
273
  return Object.fromEntries((0, misc_1.onlyTruthy)([httpSrv, httpsSrv].map(srv => {
262
274
  var _a;
263
275
  if (!(srv === null || srv === void 0 ? void 0 : srv.listening))
264
276
  return false;
265
277
  const port = (_a = srv === null || srv === void 0 ? void 0 : srv.address()) === null || _a === void 0 ? void 0 : _a.port;
266
278
  const appendPort = port === (srv.name === 'https' ? 443 : 80) ? '' : ':' + port;
267
- const urls = ips.map(ip => `${srv.name}://${ip}${appendPort}`);
279
+ const urls = ips.map(ip => `${srv.name}://${(0, misc_1.ipForUrl)(ip)}${appendPort}`);
268
280
  return urls.length && [srv.name, urls];
269
281
  })));
270
282
  }
271
283
  exports.getUrls = getUrls;
272
284
  function printUrls(srvName) {
273
- getUrls().then(urls => {
274
- for (const url of urls[srvName])
275
- console.log('serving on', url);
276
- });
285
+ getUrls().then(urls => lodash_1.default.each(urls[srvName], url => console.log('serving on', url)));
277
286
  }
package/src/misc.js CHANGED
@@ -32,6 +32,7 @@ const debounceAsync_1 = __importDefault(require("./debounceAsync"));
32
32
  exports.debounceAsync = debounceAsync_1.default;
33
33
  const apiMiddleware_1 = require("./apiMiddleware");
34
34
  const const_1 = require("./const");
35
+ const cross_1 = require("./cross");
35
36
  const cbs = new Set();
36
37
  function onProcessExit(cb) {
37
38
  cbs.add(cb);
@@ -70,7 +71,7 @@ function onOff(em, events) {
70
71
  exports.onOff = onOff;
71
72
  function isLocalHost(c) {
72
73
  const ip = typeof c === 'string' ? c : c.socket.remoteAddress; // don't use Context.ip as it is subject to proxied ips, and that's no use for localhost detection
73
- return ip && (ip === '::1' || ip.endsWith('127.0.0.1'));
74
+ return ip && (0, cross_1.ipLocalHost)(ip);
74
75
  }
75
76
  exports.isLocalHost = isLocalHost;
76
77
  function makeNetMatcher(mask, emptyMaskReturns = false) {