@decentnetwork/lan 0.1.88 → 0.1.90
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.
- package/bin/tun-helper-darwin-amd64 +0 -0
- package/bin/tun-helper-darwin-arm64 +0 -0
- package/bin/tun-helper-linux-amd64 +0 -0
- package/bin/tun-helper-linux-arm64 +0 -0
- package/dist/carrier/peer-manager.d.ts +3 -0
- package/dist/carrier/peer-manager.js +1 -0
- package/dist/cli/commands.js +30 -1
- package/dist/cli/index.js +0 -0
- package/dist/daemon/server.js +8 -1
- package/dist/ui/desktop/app.js +1443 -0
- package/dist/ui/desktop/index.html +36 -0
- package/dist/ui/desktop/vendor/react-dom.production.min.js +267 -0
- package/dist/ui/desktop/vendor/react.production.min.js +31 -0
- package/dist/ui/server.d.ts +9 -0
- package/dist/ui/server.js +164 -4
- package/package.json +6 -3
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license React
|
|
3
|
+
* react.production.min.js
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
6
|
+
*
|
|
7
|
+
* This source code is licensed under the MIT license found in the
|
|
8
|
+
* LICENSE file in the root directory of this source tree.
|
|
9
|
+
*/
|
|
10
|
+
(function(){'use strict';(function(c,x){"object"===typeof exports&&"undefined"!==typeof module?x(exports):"function"===typeof define&&define.amd?define(["exports"],x):(c=c||self,x(c.React={}))})(this,function(c){function x(a){if(null===a||"object"!==typeof a)return null;a=V&&a[V]||a["@@iterator"];return"function"===typeof a?a:null}function w(a,b,e){this.props=a;this.context=b;this.refs=W;this.updater=e||X}function Y(){}function K(a,b,e){this.props=a;this.context=b;this.refs=W;this.updater=e||X}function Z(a,b,
|
|
11
|
+
e){var m,d={},c=null,h=null;if(null!=b)for(m in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(c=""+b.key),b)aa.call(b,m)&&!ba.hasOwnProperty(m)&&(d[m]=b[m]);var l=arguments.length-2;if(1===l)d.children=e;else if(1<l){for(var f=Array(l),k=0;k<l;k++)f[k]=arguments[k+2];d.children=f}if(a&&a.defaultProps)for(m in l=a.defaultProps,l)void 0===d[m]&&(d[m]=l[m]);return{$$typeof:y,type:a,key:c,ref:h,props:d,_owner:L.current}}function oa(a,b){return{$$typeof:y,type:a.type,key:b,ref:a.ref,props:a.props,_owner:a._owner}}
|
|
12
|
+
function M(a){return"object"===typeof a&&null!==a&&a.$$typeof===y}function pa(a){var b={"=":"=0",":":"=2"};return"$"+a.replace(/[=:]/g,function(a){return b[a]})}function N(a,b){return"object"===typeof a&&null!==a&&null!=a.key?pa(""+a.key):b.toString(36)}function B(a,b,e,m,d){var c=typeof a;if("undefined"===c||"boolean"===c)a=null;var h=!1;if(null===a)h=!0;else switch(c){case "string":case "number":h=!0;break;case "object":switch(a.$$typeof){case y:case qa:h=!0}}if(h)return h=a,d=d(h),a=""===m?"."+
|
|
13
|
+
N(h,0):m,ca(d)?(e="",null!=a&&(e=a.replace(da,"$&/")+"/"),B(d,b,e,"",function(a){return a})):null!=d&&(M(d)&&(d=oa(d,e+(!d.key||h&&h.key===d.key?"":(""+d.key).replace(da,"$&/")+"/")+a)),b.push(d)),1;h=0;m=""===m?".":m+":";if(ca(a))for(var l=0;l<a.length;l++){c=a[l];var f=m+N(c,l);h+=B(c,b,e,f,d)}else if(f=x(a),"function"===typeof f)for(a=f.call(a),l=0;!(c=a.next()).done;)c=c.value,f=m+N(c,l++),h+=B(c,b,e,f,d);else if("object"===c)throw b=String(a),Error("Objects are not valid as a React child (found: "+
|
|
14
|
+
("[object Object]"===b?"object with keys {"+Object.keys(a).join(", ")+"}":b)+"). If you meant to render a collection of children, use an array instead.");return h}function C(a,b,e){if(null==a)return a;var c=[],d=0;B(a,c,"","",function(a){return b.call(e,a,d++)});return c}function ra(a){if(-1===a._status){var b=a._result;b=b();b.then(function(b){if(0===a._status||-1===a._status)a._status=1,a._result=b},function(b){if(0===a._status||-1===a._status)a._status=2,a._result=b});-1===a._status&&(a._status=
|
|
15
|
+
0,a._result=b)}if(1===a._status)return a._result.default;throw a._result;}function O(a,b){var e=a.length;a.push(b);a:for(;0<e;){var c=e-1>>>1,d=a[c];if(0<D(d,b))a[c]=b,a[e]=d,e=c;else break a}}function p(a){return 0===a.length?null:a[0]}function E(a){if(0===a.length)return null;var b=a[0],e=a.pop();if(e!==b){a[0]=e;a:for(var c=0,d=a.length,k=d>>>1;c<k;){var h=2*(c+1)-1,l=a[h],f=h+1,g=a[f];if(0>D(l,e))f<d&&0>D(g,l)?(a[c]=g,a[f]=e,c=f):(a[c]=l,a[h]=e,c=h);else if(f<d&&0>D(g,e))a[c]=g,a[f]=e,c=f;else break a}}return b}
|
|
16
|
+
function D(a,b){var c=a.sortIndex-b.sortIndex;return 0!==c?c:a.id-b.id}function P(a){for(var b=p(r);null!==b;){if(null===b.callback)E(r);else if(b.startTime<=a)E(r),b.sortIndex=b.expirationTime,O(q,b);else break;b=p(r)}}function Q(a){z=!1;P(a);if(!u)if(null!==p(q))u=!0,R(S);else{var b=p(r);null!==b&&T(Q,b.startTime-a)}}function S(a,b){u=!1;z&&(z=!1,ea(A),A=-1);F=!0;var c=k;try{P(b);for(n=p(q);null!==n&&(!(n.expirationTime>b)||a&&!fa());){var m=n.callback;if("function"===typeof m){n.callback=null;
|
|
17
|
+
k=n.priorityLevel;var d=m(n.expirationTime<=b);b=v();"function"===typeof d?n.callback=d:n===p(q)&&E(q);P(b)}else E(q);n=p(q)}if(null!==n)var g=!0;else{var h=p(r);null!==h&&T(Q,h.startTime-b);g=!1}return g}finally{n=null,k=c,F=!1}}function fa(){return v()-ha<ia?!1:!0}function R(a){G=a;H||(H=!0,I())}function T(a,b){A=ja(function(){a(v())},b)}function ka(a){throw Error("act(...) is not supported in production builds of React.");}var y=Symbol.for("react.element"),qa=Symbol.for("react.portal"),sa=Symbol.for("react.fragment"),
|
|
18
|
+
ta=Symbol.for("react.strict_mode"),ua=Symbol.for("react.profiler"),va=Symbol.for("react.provider"),wa=Symbol.for("react.context"),xa=Symbol.for("react.forward_ref"),ya=Symbol.for("react.suspense"),za=Symbol.for("react.memo"),Aa=Symbol.for("react.lazy"),V=Symbol.iterator,X={isMounted:function(a){return!1},enqueueForceUpdate:function(a,b,c){},enqueueReplaceState:function(a,b,c,m){},enqueueSetState:function(a,b,c,m){}},la=Object.assign,W={};w.prototype.isReactComponent={};w.prototype.setState=function(a,
|
|
19
|
+
b){if("object"!==typeof a&&"function"!==typeof a&&null!=a)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,a,b,"setState")};w.prototype.forceUpdate=function(a){this.updater.enqueueForceUpdate(this,a,"forceUpdate")};Y.prototype=w.prototype;var t=K.prototype=new Y;t.constructor=K;la(t,w.prototype);t.isPureReactComponent=!0;var ca=Array.isArray,aa=Object.prototype.hasOwnProperty,L={current:null},
|
|
20
|
+
ba={key:!0,ref:!0,__self:!0,__source:!0},da=/\/+/g,g={current:null},J={transition:null};if("object"===typeof performance&&"function"===typeof performance.now){var Ba=performance;var v=function(){return Ba.now()}}else{var ma=Date,Ca=ma.now();v=function(){return ma.now()-Ca}}var q=[],r=[],Da=1,n=null,k=3,F=!1,u=!1,z=!1,ja="function"===typeof setTimeout?setTimeout:null,ea="function"===typeof clearTimeout?clearTimeout:null,na="undefined"!==typeof setImmediate?setImmediate:null;"undefined"!==typeof navigator&&
|
|
21
|
+
void 0!==navigator.scheduling&&void 0!==navigator.scheduling.isInputPending&&navigator.scheduling.isInputPending.bind(navigator.scheduling);var H=!1,G=null,A=-1,ia=5,ha=-1,U=function(){if(null!==G){var a=v();ha=a;var b=!0;try{b=G(!0,a)}finally{b?I():(H=!1,G=null)}}else H=!1};if("function"===typeof na)var I=function(){na(U)};else if("undefined"!==typeof MessageChannel){t=new MessageChannel;var Ea=t.port2;t.port1.onmessage=U;I=function(){Ea.postMessage(null)}}else I=function(){ja(U,0)};t={ReactCurrentDispatcher:g,
|
|
22
|
+
ReactCurrentOwner:L,ReactCurrentBatchConfig:J,Scheduler:{__proto__:null,unstable_ImmediatePriority:1,unstable_UserBlockingPriority:2,unstable_NormalPriority:3,unstable_IdlePriority:5,unstable_LowPriority:4,unstable_runWithPriority:function(a,b){switch(a){case 1:case 2:case 3:case 4:case 5:break;default:a=3}var c=k;k=a;try{return b()}finally{k=c}},unstable_next:function(a){switch(k){case 1:case 2:case 3:var b=3;break;default:b=k}var c=k;k=b;try{return a()}finally{k=c}},unstable_scheduleCallback:function(a,
|
|
23
|
+
b,c){var e=v();"object"===typeof c&&null!==c?(c=c.delay,c="number"===typeof c&&0<c?e+c:e):c=e;switch(a){case 1:var d=-1;break;case 2:d=250;break;case 5:d=1073741823;break;case 4:d=1E4;break;default:d=5E3}d=c+d;a={id:Da++,callback:b,priorityLevel:a,startTime:c,expirationTime:d,sortIndex:-1};c>e?(a.sortIndex=c,O(r,a),null===p(q)&&a===p(r)&&(z?(ea(A),A=-1):z=!0,T(Q,c-e))):(a.sortIndex=d,O(q,a),u||F||(u=!0,R(S)));return a},unstable_cancelCallback:function(a){a.callback=null},unstable_wrapCallback:function(a){var b=
|
|
24
|
+
k;return function(){var c=k;k=b;try{return a.apply(this,arguments)}finally{k=c}}},unstable_getCurrentPriorityLevel:function(){return k},unstable_shouldYield:fa,unstable_requestPaint:function(){},unstable_continueExecution:function(){u||F||(u=!0,R(S))},unstable_pauseExecution:function(){},unstable_getFirstCallbackNode:function(){return p(q)},get unstable_now(){return v},unstable_forceFrameRate:function(a){0>a||125<a?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):
|
|
25
|
+
ia=0<a?Math.floor(1E3/a):5},unstable_Profiling:null}};c.Children={map:C,forEach:function(a,b,c){C(a,function(){b.apply(this,arguments)},c)},count:function(a){var b=0;C(a,function(){b++});return b},toArray:function(a){return C(a,function(a){return a})||[]},only:function(a){if(!M(a))throw Error("React.Children.only expected to receive a single React element child.");return a}};c.Component=w;c.Fragment=sa;c.Profiler=ua;c.PureComponent=K;c.StrictMode=ta;c.Suspense=ya;c.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=
|
|
26
|
+
t;c.act=ka;c.cloneElement=function(a,b,c){if(null===a||void 0===a)throw Error("React.cloneElement(...): The argument must be a React element, but you passed "+a+".");var e=la({},a.props),d=a.key,k=a.ref,h=a._owner;if(null!=b){void 0!==b.ref&&(k=b.ref,h=L.current);void 0!==b.key&&(d=""+b.key);if(a.type&&a.type.defaultProps)var l=a.type.defaultProps;for(f in b)aa.call(b,f)&&!ba.hasOwnProperty(f)&&(e[f]=void 0===b[f]&&void 0!==l?l[f]:b[f])}var f=arguments.length-2;if(1===f)e.children=c;else if(1<f){l=
|
|
27
|
+
Array(f);for(var g=0;g<f;g++)l[g]=arguments[g+2];e.children=l}return{$$typeof:y,type:a.type,key:d,ref:k,props:e,_owner:h}};c.createContext=function(a){a={$$typeof:wa,_currentValue:a,_currentValue2:a,_threadCount:0,Provider:null,Consumer:null,_defaultValue:null,_globalName:null};a.Provider={$$typeof:va,_context:a};return a.Consumer=a};c.createElement=Z;c.createFactory=function(a){var b=Z.bind(null,a);b.type=a;return b};c.createRef=function(){return{current:null}};c.forwardRef=function(a){return{$$typeof:xa,
|
|
28
|
+
render:a}};c.isValidElement=M;c.lazy=function(a){return{$$typeof:Aa,_payload:{_status:-1,_result:a},_init:ra}};c.memo=function(a,b){return{$$typeof:za,type:a,compare:void 0===b?null:b}};c.startTransition=function(a,b){b=J.transition;J.transition={};try{a()}finally{J.transition=b}};c.unstable_act=ka;c.useCallback=function(a,b){return g.current.useCallback(a,b)};c.useContext=function(a){return g.current.useContext(a)};c.useDebugValue=function(a,b){};c.useDeferredValue=function(a){return g.current.useDeferredValue(a)};
|
|
29
|
+
c.useEffect=function(a,b){return g.current.useEffect(a,b)};c.useId=function(){return g.current.useId()};c.useImperativeHandle=function(a,b,c){return g.current.useImperativeHandle(a,b,c)};c.useInsertionEffect=function(a,b){return g.current.useInsertionEffect(a,b)};c.useLayoutEffect=function(a,b){return g.current.useLayoutEffect(a,b)};c.useMemo=function(a,b){return g.current.useMemo(a,b)};c.useReducer=function(a,b,c){return g.current.useReducer(a,b,c)};c.useRef=function(a){return g.current.useRef(a)};
|
|
30
|
+
c.useState=function(a){return g.current.useState(a)};c.useSyncExternalStore=function(a,b,c){return g.current.useSyncExternalStore(a,b,c)};c.useTransition=function(){return g.current.useTransition()};c.version="18.3.1"});
|
|
31
|
+
})();
|
package/dist/ui/server.d.ts
CHANGED
|
@@ -8,6 +8,15 @@ export interface FriendUiOptions {
|
|
|
8
8
|
/** If this node also runs a dora server, the path to its roster.yaml — the
|
|
9
9
|
* UI shows the allocation table (userid → IP → name). Undefined = no dora. */
|
|
10
10
|
doraRosterPath?: string;
|
|
11
|
+
/** Version/wire strings for the desktop "my node" panel — the daemon's diag
|
|
12
|
+
* doesn't carry them, so the CLI reads them off the installed packages and
|
|
13
|
+
* passes them in. */
|
|
14
|
+
meExtra?: {
|
|
15
|
+
lanVer?: string;
|
|
16
|
+
peerVer?: string;
|
|
17
|
+
wire?: string;
|
|
18
|
+
channel?: string;
|
|
19
|
+
};
|
|
11
20
|
listenHost?: string;
|
|
12
21
|
listenPort?: number;
|
|
13
22
|
log?: (msg: string) => void;
|
package/dist/ui/server.js
CHANGED
|
@@ -14,7 +14,30 @@
|
|
|
14
14
|
//
|
|
15
15
|
import http from "node:http";
|
|
16
16
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
18
|
+
import { dirname, join } from "node:path";
|
|
17
19
|
import yaml from "js-yaml";
|
|
20
|
+
// Directory holding the built desktop UI bundle (index.html, app.js, vendor/).
|
|
21
|
+
// scripts/build-ui.mjs emits it next to this compiled module at dist/ui/desktop/.
|
|
22
|
+
const DESKTOP_DIR = join(dirname(fileURLToPath(import.meta.url)), "desktop");
|
|
23
|
+
// ---- shapes the desktop UI (src/ui/desktop) consumes (DK_* in the design) ----
|
|
24
|
+
const fmtTime = (ts) => {
|
|
25
|
+
if (!ts)
|
|
26
|
+
return "";
|
|
27
|
+
const d = new Date(ts);
|
|
28
|
+
const today = new Date();
|
|
29
|
+
const sameDay = d.toDateString() === today.toDateString();
|
|
30
|
+
if (sameDay)
|
|
31
|
+
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
32
|
+
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
33
|
+
const yest = new Date(today.getTime() - 86400000);
|
|
34
|
+
if (d.toDateString() === yest.toDateString())
|
|
35
|
+
return "Yest";
|
|
36
|
+
if (today.getTime() - d.getTime() < 7 * 86400000)
|
|
37
|
+
return days[d.getDay()];
|
|
38
|
+
return `${d.getDate()} ${["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][d.getMonth()]}`;
|
|
39
|
+
};
|
|
40
|
+
const viaFromTransport = (t) => t === "udp" || t === "both" ? "direct" : t === "tcp-relay" ? "relay" : null;
|
|
18
41
|
export function startFriendUi(opts) {
|
|
19
42
|
const host = opts.listenHost ?? "127.0.0.1";
|
|
20
43
|
const port = opts.listenPort ?? 8765;
|
|
@@ -38,11 +61,38 @@ export function startFriendUi(opts) {
|
|
|
38
61
|
const server = http.createServer(async (req, res) => {
|
|
39
62
|
try {
|
|
40
63
|
const url = (req.url || "/").split("?")[0];
|
|
64
|
+
// Desktop UI bundle (the design). Falls back to the classic page when
|
|
65
|
+
// the bundle hasn't been built (dist/ui/desktop missing).
|
|
66
|
+
const desktopIndex = join(DESKTOP_DIR, "index.html");
|
|
41
67
|
if (req.method === "GET" && (url === "/" || url === "/index.html")) {
|
|
68
|
+
if (existsSync(desktopIndex)) {
|
|
69
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
70
|
+
res.end(readFileSync(desktopIndex));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
42
73
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
43
74
|
res.end(PAGE);
|
|
44
75
|
return;
|
|
45
76
|
}
|
|
77
|
+
if (req.method === "GET" && (url === "/classic" || url === "/classic.html")) {
|
|
78
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
79
|
+
res.end(PAGE);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// Static assets for the desktop bundle (app.js + vendored react UMD).
|
|
83
|
+
if (req.method === "GET" && (url === "/app.js" || url.startsWith("/vendor/"))) {
|
|
84
|
+
const rel = url === "/app.js" ? "app.js" : url.slice(1); // strip leading /
|
|
85
|
+
const file = join(DESKTOP_DIR, rel);
|
|
86
|
+
// Guard against path traversal: resolved file must stay under DESKTOP_DIR.
|
|
87
|
+
if (existsSync(file) && file.startsWith(DESKTOP_DIR)) {
|
|
88
|
+
res.writeHead(200, { "content-type": "application/javascript; charset=utf-8" });
|
|
89
|
+
res.end(readFileSync(file));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
res.writeHead(404);
|
|
93
|
+
res.end("not found");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
46
96
|
if (req.method === "GET" && url === "/api/state") {
|
|
47
97
|
const [diag, pending] = await Promise.all([
|
|
48
98
|
opts.call({ op: "diag" }),
|
|
@@ -75,6 +125,101 @@ export function startFriendUi(opts) {
|
|
|
75
125
|
sendJson(res, 200, { friends });
|
|
76
126
|
return;
|
|
77
127
|
}
|
|
128
|
+
// One bootstrap call for the desktop UI: composes the design's DK_*
|
|
129
|
+
// shapes (me / peers / requests / exits) from diag + friends-list +
|
|
130
|
+
// ipam + routes, so the client data layer is a single poll.
|
|
131
|
+
if (req.method === "GET" && url === "/api/desktop") {
|
|
132
|
+
const [diag, pending, flist] = await Promise.all([
|
|
133
|
+
opts.call({ op: "diag" }),
|
|
134
|
+
opts.call({ op: "friends-pending" }),
|
|
135
|
+
opts.call({ op: "friends-list" }),
|
|
136
|
+
]);
|
|
137
|
+
const d = (diag.ok ? diag.data : {}) ?? {};
|
|
138
|
+
const identity = d.identity ?? {};
|
|
139
|
+
const tun = d.tun ?? {};
|
|
140
|
+
const node = d.node ?? {};
|
|
141
|
+
const diagFriends = d.friends ?? [];
|
|
142
|
+
const ipamList = d.ipam ?? [];
|
|
143
|
+
const ipByUserid = new Map();
|
|
144
|
+
for (const p of ipamList)
|
|
145
|
+
if (p.carrierId)
|
|
146
|
+
ipByUserid.set(p.carrierId, p.virtualIp);
|
|
147
|
+
const sessByUserid = new Map();
|
|
148
|
+
for (const f of diagFriends) {
|
|
149
|
+
const uid = f.carrierId || f.pubkey || "";
|
|
150
|
+
if (uid && f.session)
|
|
151
|
+
sessByUserid.set(uid, f.session);
|
|
152
|
+
}
|
|
153
|
+
const me = {
|
|
154
|
+
name: node.name || (identity.userid ?? "").slice(0, 8),
|
|
155
|
+
handle: node.name ? `@decentnetwork/${node.name}` : "@decentnetwork/peer",
|
|
156
|
+
userId: identity.userid ?? "",
|
|
157
|
+
carrier: identity.address ?? "",
|
|
158
|
+
netKey: identity.userid ?? "",
|
|
159
|
+
ip: d.allocatedIp ?? tun.ip ?? "",
|
|
160
|
+
online: !!tun.ip,
|
|
161
|
+
lanVer: opts.meExtra?.lanVer ?? "",
|
|
162
|
+
peerVer: opts.meExtra?.peerVer ?? "",
|
|
163
|
+
channel: opts.meExtra?.channel ?? "@next",
|
|
164
|
+
wire: opts.meExtra?.wire ?? "163",
|
|
165
|
+
};
|
|
166
|
+
const fl = (flist.ok ? (flist.data?.friends ?? []) : []);
|
|
167
|
+
const peers = fl.map((f) => {
|
|
168
|
+
const uid = f.userid ?? "";
|
|
169
|
+
const sess = sessByUserid.get(uid);
|
|
170
|
+
const status = f.status;
|
|
171
|
+
const online = status === "connected" || status === "online";
|
|
172
|
+
const realName = typeof f.name === "string" && f.name !== uid ? f.name : undefined;
|
|
173
|
+
const lm = f.lastMessage;
|
|
174
|
+
return {
|
|
175
|
+
id: uid,
|
|
176
|
+
alias: f.alias || realName || null,
|
|
177
|
+
userId: uid,
|
|
178
|
+
online,
|
|
179
|
+
via: online ? viaFromTransport(sess?.transport) : null,
|
|
180
|
+
ping: null,
|
|
181
|
+
ip: ipByUserid.get(uid) ?? "",
|
|
182
|
+
unread: f.unread ?? 0,
|
|
183
|
+
agent: false,
|
|
184
|
+
lastMsg: lm ? (lm.dir === "out" ? "you: " : "") + (lm.text ?? "") : "",
|
|
185
|
+
lastTime: fmtTime(lm?.ts),
|
|
186
|
+
wire: "163",
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
const pend = pending.ok ? (pending.data?.pending ?? []) : [];
|
|
190
|
+
const requests = pend.map((p, i) => ({
|
|
191
|
+
id: p.userid || p.address || `r${i}`,
|
|
192
|
+
carrier: p.address || p.userid || "",
|
|
193
|
+
userid: p.userid || "",
|
|
194
|
+
via: "lan",
|
|
195
|
+
time: "",
|
|
196
|
+
}));
|
|
197
|
+
// Exit nodes from routes.yaml regions, online-status resolved via ipam.
|
|
198
|
+
let routes = { regions: [], default: "direct" };
|
|
199
|
+
if (existsSync(opts.routesPath)) {
|
|
200
|
+
routes = yaml.load(readFileSync(opts.routesPath, "utf-8")) ?? routes;
|
|
201
|
+
}
|
|
202
|
+
const onlineIps = new Set();
|
|
203
|
+
for (const [uid, ip] of ipByUserid) {
|
|
204
|
+
const s = sessByUserid.get(uid);
|
|
205
|
+
if (s && s.transport && s.transport !== "none")
|
|
206
|
+
onlineIps.add(ip);
|
|
207
|
+
}
|
|
208
|
+
const exits = (routes.regions ?? []).map((r) => ({
|
|
209
|
+
region: r.name,
|
|
210
|
+
flag: (r.name || "?").slice(0, 2).toUpperCase(),
|
|
211
|
+
label: r.name,
|
|
212
|
+
nodes: (r.exits ?? []).map((ip, i) => ({
|
|
213
|
+
ip,
|
|
214
|
+
online: onlineIps.has(ip),
|
|
215
|
+
ping: null,
|
|
216
|
+
host: `${(r.name || "ex").slice(0, 2)}-${String(i + 1).padStart(2, "0")}`,
|
|
217
|
+
})),
|
|
218
|
+
}));
|
|
219
|
+
const activeExit = routes.default && routes.default !== "direct" ? routes.default : null;
|
|
220
|
+
sendJson(res, 200, { me, peers, requests, exits, activeExit });
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
78
223
|
if (req.method === "GET" && url === "/api/chat-history") {
|
|
79
224
|
const peer = new URL(req.url || "/", "http://x").searchParams.get("peer") || undefined;
|
|
80
225
|
const r = await opts.call({ op: "chat-history", userid: peer });
|
|
@@ -262,19 +407,34 @@ async function refresh(){
|
|
|
262
407
|
</div>\`).join('') : '<div class="empty">No pending requests.</div>';
|
|
263
408
|
document.getElementById('friends').innerHTML = fr.length ? fr.map(f => {
|
|
264
409
|
const uid = f.userid||f.carrierId;
|
|
265
|
-
const nm = f
|
|
410
|
+
const nm = friendName(f, uid);
|
|
266
411
|
const lm = f.lastMessage;
|
|
267
412
|
const preview = lm ? ((lm.dir==='out'?'You: ':'') + lm.text) : (f.status||'offline');
|
|
268
413
|
const badge = f.unread ? '<span style="background:#e74c3c;color:#fff;border-radius:10px;padding:0 .45rem;font-size:.7rem;margin-left:.4rem">'+f.unread+'</span>' : '';
|
|
269
414
|
return \`<div class="row" style="cursor:pointer" onclick="openChat('\${esc(uid)}')" title="open chat">
|
|
270
|
-
|
|
415
|
+
\${avatar(uid, nm, f.status)}
|
|
271
416
|
<div class="meta"><div class="name">\${esc(nm)}\${badge}</div>
|
|
272
|
-
<div class="sub"
|
|
417
|
+
<div class="sub"><span class="dot \${esc(f.status||'offline')}" style="display:inline-block;vertical-align:middle"></span> \${esc(preview)}</div></div>
|
|
273
418
|
<button onclick="event.stopPropagation();editAlias('\${esc(uid)}')" title="rename" style="padding:.2rem .55rem">✎</button>
|
|
274
419
|
<button class="reject" onclick="event.stopPropagation();delFriend('\${esc(uid)}')" title="remove friend" style="padding:.2rem .55rem">×</button>
|
|
275
420
|
</div>\`;
|
|
276
421
|
}).join('') : '<div class="empty">No friends yet.</div>';
|
|
277
422
|
}
|
|
423
|
+
// The build default "@decentnetwork/peer" is useless (every node sends it) —
|
|
424
|
+
// treat it as no-name and fall back to alias / short userid.
|
|
425
|
+
function friendName(f, uid){
|
|
426
|
+
const n = f.alias || (f.name && f.name !== '@decentnetwork/peer' ? f.name : '');
|
|
427
|
+
return n || short(uid);
|
|
428
|
+
}
|
|
429
|
+
// Deterministic colored-initial avatar from the userid (no protocol/avatar
|
|
430
|
+
// exchange needed yet — same userid always gets the same color + letter).
|
|
431
|
+
function avatar(uid, nm, status){
|
|
432
|
+
const colors=['#e74c3c','#e67e22','#f39c12','#16a085','#27ae60','#2980b9','#8e44ad','#2c3e50','#d35400','#c0392b'];
|
|
433
|
+
let h=0; for(let i=0;i<String(uid).length;i++) h=(h*31+uid.charCodeAt(i))>>>0;
|
|
434
|
+
const c=colors[h%colors.length];
|
|
435
|
+
const ch=((nm||'?').trim().charAt(0)||'?').toUpperCase();
|
|
436
|
+
return '<span style="display:inline-flex;width:2rem;height:2rem;border-radius:50%;background:'+c+';color:#fff;align-items:center;justify-content:center;font-weight:700;flex:0 0 auto">'+esc(ch)+'</span>';
|
|
437
|
+
}
|
|
278
438
|
function copyId(){ const id=(window.me&&(window.me.address||window.me.userid))||''; if(id&&navigator.clipboard){ navigator.clipboard.writeText(id); toast('Your address copied — share it so others can add you'); } }
|
|
279
439
|
async function delFriend(uid){ if(!confirm('Remove this friend and your messages with them?')) return; const r=await api('/api/friend-remove',{userid:uid}); toast(r.ok?'Removed':(r.error||'failed')); if(chatWith===uid) closeChat(); refresh(); }
|
|
280
440
|
async function editAlias(uid){ const cur=(window.friendsById[uid]||{}).alias||''; const a=prompt('Local name for this friend (empty to clear):', cur); if(a===null) return; const r=await api('/api/friend-alias',{userid:uid,alias:a}); toast(r.ok?'Saved':(r.error||'failed')); refresh(); }
|
|
@@ -286,7 +446,7 @@ let chatWith = null, chatTimer = null;
|
|
|
286
446
|
async function openChat(userid){
|
|
287
447
|
chatWith = userid;
|
|
288
448
|
const f = window.friendsById[userid] || {};
|
|
289
|
-
document.getElementById('chatName').textContent = f
|
|
449
|
+
document.getElementById('chatName').textContent = friendName(f, userid);
|
|
290
450
|
document.getElementById('chatSub').textContent = (f.status||'') + ' · ' + short(userid);
|
|
291
451
|
document.getElementById('chat').style.display = 'block';
|
|
292
452
|
api('/api/chat-mark-read', {userid}); // clears the unread badge
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.90",
|
|
4
4
|
"description": "Private virtual LAN for self-hosted services and AI agents, built on Elastos Carrier. NAT-traversal, name service, ACL, all over a peer-to-peer mesh — no public IP required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -58,7 +58,8 @@
|
|
|
58
58
|
"access": "public"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
|
-
"build": "tsc -p tsconfig.json",
|
|
61
|
+
"build": "tsc -p tsconfig.json && chmod +x dist/cli/index.js && node scripts/build-ui.mjs",
|
|
62
|
+
"build:ui": "node scripts/build-ui.mjs",
|
|
62
63
|
"build:helper": "cd helper/tun-helper && go build -o ../../bin/tun-helper-$(go env GOOS)-$(go env GOARCH) .",
|
|
63
64
|
"build:helper:linux-amd64": "cd helper/tun-helper && GOOS=linux GOARCH=amd64 go build -o ../../bin/tun-helper-linux-amd64 .",
|
|
64
65
|
"build:helper:linux-arm64": "cd helper/tun-helper && GOOS=linux GOARCH=arm64 go build -o ../../bin/tun-helper-linux-arm64 .",
|
|
@@ -77,7 +78,7 @@
|
|
|
77
78
|
},
|
|
78
79
|
"dependencies": {
|
|
79
80
|
"@decentnetwork/dora": "^0.1.6",
|
|
80
|
-
"@decentnetwork/peer": "^0.1.
|
|
81
|
+
"@decentnetwork/peer": "^0.1.40",
|
|
81
82
|
"js-yaml": "^4.1.0",
|
|
82
83
|
"yargs": "^17.7.2"
|
|
83
84
|
},
|
|
@@ -89,6 +90,8 @@
|
|
|
89
90
|
"@typescript-eslint/parser": "^7.10.0",
|
|
90
91
|
"@vitest/coverage-v8": "^1.6.0",
|
|
91
92
|
"eslint": "^8.57.0",
|
|
93
|
+
"react": "^18.3.1",
|
|
94
|
+
"react-dom": "^18.3.1",
|
|
92
95
|
"ts-node": "^10.9.2",
|
|
93
96
|
"typescript": "^5.4.5",
|
|
94
97
|
"vitest": "^1.6.0"
|