@decentnetwork/lan 0.1.87 → 0.1.89
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/index.js +0 -0
- package/dist/daemon/server.js +7 -1
- package/dist/ui/server.js +80 -15
- package/package.json +3 -3
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -13,6 +13,9 @@ export interface PeerManagerOptions {
|
|
|
13
13
|
expressNodes?: BootstrapNode[];
|
|
14
14
|
/** Use express only for friend-request bootstrap, never for data-plane sendText. */
|
|
15
15
|
expressControlPlaneOnly?: boolean;
|
|
16
|
+
/** Display name advertised to friends (this node's name, so friend lists show
|
|
17
|
+
* "cn"/"tokyo"/"mac-dev" instead of the generic "@decentnetwork/peer"). */
|
|
18
|
+
nickname?: string;
|
|
16
19
|
}
|
|
17
20
|
export declare class PeerManager extends EventEmitter {
|
|
18
21
|
private peer;
|
|
@@ -26,6 +26,7 @@ export class PeerManager extends EventEmitter {
|
|
|
26
26
|
bootstrapNodes: opts.bootstrapNodes,
|
|
27
27
|
expressNodes: opts.expressNodes,
|
|
28
28
|
expressControlPlaneOnly: opts.expressControlPlaneOnly,
|
|
29
|
+
nickname: opts.nickname,
|
|
29
30
|
// Register our IP channel (163) as the SDK's bulk-data stream so it
|
|
30
31
|
// rides a single transport instead of fanning out over UDP + relay +
|
|
31
32
|
// TCP relay (which delivered 3-4 duplicates of every IP packet).
|
package/dist/cli/index.js
CHANGED
|
File without changes
|
package/dist/daemon/server.js
CHANGED
|
@@ -181,6 +181,9 @@ export class DaemonServer {
|
|
|
181
181
|
bootstrapNodes: this.config.carrier.bootstrapNodes,
|
|
182
182
|
expressNodes: this.config.carrier.expressNodes ?? [],
|
|
183
183
|
expressControlPlaneOnly: true,
|
|
184
|
+
// Advertise this node's name so friends see "cn"/"tokyo"/"mac-dev"
|
|
185
|
+
// instead of the generic "@decentnetwork/peer".
|
|
186
|
+
nickname: this.config.node.name,
|
|
184
187
|
});
|
|
185
188
|
await this.peerManager.start();
|
|
186
189
|
this.logger.info(`Identity: ${this.peerManager.getAddress()}`);
|
|
@@ -235,10 +238,13 @@ export class DaemonServer {
|
|
|
235
238
|
const uid = f.carrierId ?? f.pubkey ?? "";
|
|
236
239
|
const meta = this.friendMeta?.get(uid);
|
|
237
240
|
const lastMsg = last.get(uid);
|
|
241
|
+
// The build-default nickname is useless (every node sends it) —
|
|
242
|
+
// treat it as no-name so the UI falls back to alias/userid.
|
|
243
|
+
const realName = f.name && f.name !== "@decentnetwork/peer" ? f.name : undefined;
|
|
238
244
|
return {
|
|
239
245
|
userid: uid,
|
|
240
246
|
alias: meta?.alias,
|
|
241
|
-
name: meta?.alias ||
|
|
247
|
+
name: meta?.alias || realName || uid,
|
|
242
248
|
status: f.status,
|
|
243
249
|
lastSeen: f.lastSeen,
|
|
244
250
|
pinned: meta?.pinned ?? false,
|
package/dist/ui/server.js
CHANGED
|
@@ -62,8 +62,22 @@ export function startFriendUi(opts) {
|
|
|
62
62
|
sendJson(res, 200, { me, friends, pending: pend });
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
|
+
// Rich friend list (alias / status / unread / last message). Falls back
|
|
66
|
+
// to the diag-derived list on older daemons that lack the op.
|
|
67
|
+
if (req.method === "GET" && url === "/api/friends-list") {
|
|
68
|
+
const r = await opts.call({ op: "friends-list" });
|
|
69
|
+
if (r.ok) {
|
|
70
|
+
sendJson(res, 200, r.data ?? { friends: [] });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const diag = await opts.call({ op: "diag" });
|
|
74
|
+
const friends = diag.ok ? (diag.data?.friends ?? []) : [];
|
|
75
|
+
sendJson(res, 200, { friends });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
65
78
|
if (req.method === "GET" && url === "/api/chat-history") {
|
|
66
|
-
const
|
|
79
|
+
const peer = new URL(req.url || "/", "http://x").searchParams.get("peer") || undefined;
|
|
80
|
+
const r = await opts.call({ op: "chat-history", userid: peer });
|
|
67
81
|
sendJson(res, 200, r.ok ? (r.data ?? { chats: {} }) : { chats: {} });
|
|
68
82
|
return;
|
|
69
83
|
}
|
|
@@ -73,6 +87,24 @@ export function startFriendUi(opts) {
|
|
|
73
87
|
sendJson(res, r.ok ? 200 : 400, r);
|
|
74
88
|
return;
|
|
75
89
|
}
|
|
90
|
+
if (req.method === "POST" && url === "/api/chat-mark-read") {
|
|
91
|
+
const { userid } = await readBody(req);
|
|
92
|
+
const r = await opts.call({ op: "chat-mark-read", userid });
|
|
93
|
+
sendJson(res, r.ok ? 200 : 400, r);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (req.method === "POST" && url === "/api/friend-remove") {
|
|
97
|
+
const { userid } = await readBody(req);
|
|
98
|
+
const r = await opts.call({ op: "friend-remove", userid });
|
|
99
|
+
sendJson(res, r.ok ? 200 : 400, r);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (req.method === "POST" && url === "/api/friend-alias") {
|
|
103
|
+
const { userid, alias } = await readBody(req);
|
|
104
|
+
const r = await opts.call({ op: "friend-set-alias", userid, alias });
|
|
105
|
+
sendJson(res, r.ok ? 200 : 400, r);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
76
108
|
if (req.method === "GET" && url === "/api/routes") {
|
|
77
109
|
let routes = { regions: [], default: "direct" };
|
|
78
110
|
if (existsSync(opts.routesPath)) {
|
|
@@ -205,13 +237,19 @@ function toast(msg){ const t=document.getElementById('toast'); t.textContent=msg
|
|
|
205
237
|
|
|
206
238
|
async function api(path, body){ const r = await fetch(path, body?{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(body)}:{}); return r.json(); }
|
|
207
239
|
|
|
208
|
-
window.friendsById = {};
|
|
240
|
+
window.friendsById = {}; window.me = {};
|
|
209
241
|
async function refresh(){
|
|
210
|
-
let s
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
242
|
+
let s, fl;
|
|
243
|
+
try { [s, fl] = await Promise.all([api('/api/state'), api('/api/friends-list')]); } catch(e){ return; }
|
|
244
|
+
const pend = s.pending || [], me = s.me || {};
|
|
245
|
+
const fr = (fl && fl.friends) || s.friends || [];
|
|
246
|
+
window.me = me;
|
|
247
|
+
const idStr = me.address || me.userid || '';
|
|
248
|
+
document.getElementById('me').innerHTML =
|
|
249
|
+
(me.ip ? ('My IP: <b>' + esc(me.ip) + '</b> · ') : (me.userid ? '' : 'daemon: no identity '))
|
|
250
|
+
+ (me.userid ? ('<span title="'+esc(me.userid)+'">'+esc(short(me.userid))+'</span> ') : '')
|
|
251
|
+
+ (idStr ? '<button onclick="copyId()" style="padding:.1rem .5rem;font-size:.75rem">copy my address</button>' : '')
|
|
252
|
+
+ (me.userid && !me.installed ? ' · <span style="color:#e67e22">lan/TUN not up</span>' : '');
|
|
215
253
|
fr.forEach(f => { window.friendsById[f.userid||f.carrierId] = f; });
|
|
216
254
|
document.getElementById('pcount').textContent = pend.length ? '('+pend.length+')' : '';
|
|
217
255
|
document.getElementById('fcount').textContent = fr.length ? '('+fr.length+')' : '';
|
|
@@ -222,13 +260,39 @@ async function refresh(){
|
|
|
222
260
|
<button class="accept" onclick="act('accept','\${esc(p.userid)}')">Accept</button>
|
|
223
261
|
<button class="reject" onclick="act('reject','\${esc(p.userid)}')">Reject</button>
|
|
224
262
|
</div>\`).join('') : '<div class="empty">No pending requests.</div>';
|
|
225
|
-
document.getElementById('friends').innerHTML = fr.length ? fr.map(f =>
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
263
|
+
document.getElementById('friends').innerHTML = fr.length ? fr.map(f => {
|
|
264
|
+
const uid = f.userid||f.carrierId;
|
|
265
|
+
const nm = friendName(f, uid);
|
|
266
|
+
const lm = f.lastMessage;
|
|
267
|
+
const preview = lm ? ((lm.dir==='out'?'You: ':'') + lm.text) : (f.status||'offline');
|
|
268
|
+
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
|
+
return \`<div class="row" style="cursor:pointer" onclick="openChat('\${esc(uid)}')" title="open chat">
|
|
270
|
+
\${avatar(uid, nm, f.status)}
|
|
271
|
+
<div class="meta"><div class="name">\${esc(nm)}\${badge}</div>
|
|
272
|
+
<div class="sub"><span class="dot \${esc(f.status||'offline')}" style="display:inline-block;vertical-align:middle"></span> \${esc(preview)}</div></div>
|
|
273
|
+
<button onclick="event.stopPropagation();editAlias('\${esc(uid)}')" title="rename" style="padding:.2rem .55rem">✎</button>
|
|
274
|
+
<button class="reject" onclick="event.stopPropagation();delFriend('\${esc(uid)}')" title="remove friend" style="padding:.2rem .55rem">×</button>
|
|
275
|
+
</div>\`;
|
|
276
|
+
}).join('') : '<div class="empty">No friends yet.</div>';
|
|
277
|
+
}
|
|
278
|
+
// The build default "@decentnetwork/peer" is useless (every node sends it) —
|
|
279
|
+
// treat it as no-name and fall back to alias / short userid.
|
|
280
|
+
function friendName(f, uid){
|
|
281
|
+
const n = f.alias || (f.name && f.name !== '@decentnetwork/peer' ? f.name : '');
|
|
282
|
+
return n || short(uid);
|
|
283
|
+
}
|
|
284
|
+
// Deterministic colored-initial avatar from the userid (no protocol/avatar
|
|
285
|
+
// exchange needed yet — same userid always gets the same color + letter).
|
|
286
|
+
function avatar(uid, nm, status){
|
|
287
|
+
const colors=['#e74c3c','#e67e22','#f39c12','#16a085','#27ae60','#2980b9','#8e44ad','#2c3e50','#d35400','#c0392b'];
|
|
288
|
+
let h=0; for(let i=0;i<String(uid).length;i++) h=(h*31+uid.charCodeAt(i))>>>0;
|
|
289
|
+
const c=colors[h%colors.length];
|
|
290
|
+
const ch=((nm||'?').trim().charAt(0)||'?').toUpperCase();
|
|
291
|
+
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>';
|
|
231
292
|
}
|
|
293
|
+
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'); } }
|
|
294
|
+
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(); }
|
|
295
|
+
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(); }
|
|
232
296
|
async function act(kind, userid){ const r = await api('/api/'+kind, {userid}); toast(r.ok? (kind==='accept'?'Accepted':'Rejected') : (r.error||'failed')); refresh(); }
|
|
233
297
|
async function addFriend(){ const a=document.getElementById('addr'); const v=a.value.trim(); if(!v) return; const r=await api('/api/add',{address:v}); toast(r.ok?'Friend-request sent':(r.error||'failed')); if(r.ok) a.value=''; refresh(); }
|
|
234
298
|
document.getElementById('addr').addEventListener('keydown', e=>{ if(e.key==='Enter') addFriend(); });
|
|
@@ -237,9 +301,10 @@ let chatWith = null, chatTimer = null;
|
|
|
237
301
|
async function openChat(userid){
|
|
238
302
|
chatWith = userid;
|
|
239
303
|
const f = window.friendsById[userid] || {};
|
|
240
|
-
document.getElementById('chatName').textContent = f
|
|
304
|
+
document.getElementById('chatName').textContent = friendName(f, userid);
|
|
241
305
|
document.getElementById('chatSub').textContent = (f.status||'') + ' · ' + short(userid);
|
|
242
306
|
document.getElementById('chat').style.display = 'block';
|
|
307
|
+
api('/api/chat-mark-read', {userid}); // clears the unread badge
|
|
243
308
|
await renderChat();
|
|
244
309
|
clearInterval(chatTimer); chatTimer = setInterval(renderChat, 2000);
|
|
245
310
|
document.getElementById('chatInput').focus();
|
|
@@ -247,7 +312,7 @@ async function openChat(userid){
|
|
|
247
312
|
function closeChat(){ chatWith = null; clearInterval(chatTimer); document.getElementById('chat').style.display='none'; refresh(); }
|
|
248
313
|
async function renderChat(){
|
|
249
314
|
if(!chatWith) return;
|
|
250
|
-
let h; try { h = await api('/api/chat-history'); } catch(e){ return; }
|
|
315
|
+
let h; try { h = await api('/api/chat-history?peer='+encodeURIComponent(chatWith)); } catch(e){ return; }
|
|
251
316
|
const msgs = (h.chats && h.chats[chatWith]) || [];
|
|
252
317
|
const log = document.getElementById('chatLog');
|
|
253
318
|
log.innerHTML = msgs.length ? msgs.map(m => \`<div style="align-self:\${m.dir==='out'?'flex-end':'flex-start'};max-width:75%;padding:.4rem .7rem;border-radius:12px;background:\${m.dir==='out'?'#3478f6':'#8883'};color:\${m.dir==='out'?'#fff':'inherit'}">\${esc(m.text)}</div>\`).join('') : '<div class="empty">No messages yet — say hi.</div>';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.89",
|
|
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,7 @@
|
|
|
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",
|
|
62
62
|
"build:helper": "cd helper/tun-helper && go build -o ../../bin/tun-helper-$(go env GOOS)-$(go env GOARCH) .",
|
|
63
63
|
"build:helper:linux-amd64": "cd helper/tun-helper && GOOS=linux GOARCH=amd64 go build -o ../../bin/tun-helper-linux-amd64 .",
|
|
64
64
|
"build:helper:linux-arm64": "cd helper/tun-helper && GOOS=linux GOARCH=arm64 go build -o ../../bin/tun-helper-linux-arm64 .",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
79
|
"@decentnetwork/dora": "^0.1.6",
|
|
80
|
-
"@decentnetwork/peer": "^0.1.
|
|
80
|
+
"@decentnetwork/peer": "^0.1.40",
|
|
81
81
|
"js-yaml": "^4.1.0",
|
|
82
82
|
"yargs": "^17.7.2"
|
|
83
83
|
},
|