@decentnetwork/lan 0.1.87 → 0.1.88
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.
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
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,24 @@ 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
|
-
|
|
263
|
+
document.getElementById('friends').innerHTML = fr.length ? fr.map(f => {
|
|
264
|
+
const uid = f.userid||f.carrierId;
|
|
265
|
+
const nm = f.alias || f.name || short(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">
|
|
227
270
|
<span class="dot \${esc(f.status||'offline')}"></span>
|
|
228
|
-
<div class="meta"><div class="name">\${esc(
|
|
229
|
-
<div class="sub">\${esc(
|
|
230
|
-
|
|
271
|
+
<div class="meta"><div class="name">\${esc(nm)}\${badge}</div>
|
|
272
|
+
<div class="sub">\${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>';
|
|
231
277
|
}
|
|
278
|
+
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
|
+
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
|
+
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
281
|
async function act(kind, userid){ const r = await api('/api/'+kind, {userid}); toast(r.ok? (kind==='accept'?'Accepted':'Rejected') : (r.error||'failed')); refresh(); }
|
|
233
282
|
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
283
|
document.getElementById('addr').addEventListener('keydown', e=>{ if(e.key==='Enter') addFriend(); });
|
|
@@ -237,9 +286,10 @@ let chatWith = null, chatTimer = null;
|
|
|
237
286
|
async function openChat(userid){
|
|
238
287
|
chatWith = userid;
|
|
239
288
|
const f = window.friendsById[userid] || {};
|
|
240
|
-
document.getElementById('chatName').textContent = f.name ||
|
|
289
|
+
document.getElementById('chatName').textContent = f.alias || f.name || short(userid);
|
|
241
290
|
document.getElementById('chatSub').textContent = (f.status||'') + ' · ' + short(userid);
|
|
242
291
|
document.getElementById('chat').style.display = 'block';
|
|
292
|
+
api('/api/chat-mark-read', {userid}); // clears the unread badge
|
|
243
293
|
await renderChat();
|
|
244
294
|
clearInterval(chatTimer); chatTimer = setInterval(renderChat, 2000);
|
|
245
295
|
document.getElementById('chatInput').focus();
|
|
@@ -247,7 +297,7 @@ async function openChat(userid){
|
|
|
247
297
|
function closeChat(){ chatWith = null; clearInterval(chatTimer); document.getElementById('chat').style.display='none'; refresh(); }
|
|
248
298
|
async function renderChat(){
|
|
249
299
|
if(!chatWith) return;
|
|
250
|
-
let h; try { h = await api('/api/chat-history'); } catch(e){ return; }
|
|
300
|
+
let h; try { h = await api('/api/chat-history?peer='+encodeURIComponent(chatWith)); } catch(e){ return; }
|
|
251
301
|
const msgs = (h.chats && h.chats[chatWith]) || [];
|
|
252
302
|
const log = document.getElementById('chatLog');
|
|
253
303
|
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.88",
|
|
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",
|