@decentnetwork/lan 0.1.256 → 0.1.257
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/dist/carrier/peer-manager.d.ts +12 -0
- package/dist/carrier/peer-manager.js +27 -1
- package/dist/carrier/types.d.ts +4 -0
- package/dist/carrier/types.js +20 -0
- package/dist/daemon/ipc.d.ts +11 -2
- package/dist/daemon/ipc.js +18 -0
- package/dist/daemon/server.js +17 -0
- package/dist/ui/desktop/app.js +1 -1
- package/package.json +1 -1
|
@@ -81,6 +81,18 @@ export declare class PeerManager extends EventEmitter {
|
|
|
81
81
|
sign(message: Uint8Array): Uint8Array;
|
|
82
82
|
/** Offer a file to a friend (toxcore-standard transfer). Returns the fileId. */
|
|
83
83
|
sendFile(userid: string, data: Uint8Array, name: string): string | null;
|
|
84
|
+
/**
|
|
85
|
+
* Send an APPLICATION packet to a friend. The payload is opaque to
|
|
86
|
+
* decentlan — this is the extension point that lets an app (beagle, an
|
|
87
|
+
* agent, any IPC client) run its own protocol over our Carrier session
|
|
88
|
+
* without a change in here.
|
|
89
|
+
*
|
|
90
|
+
* `id` must be in PACKET_ID_APP_MIN..MAX. Rejecting anything else is a
|
|
91
|
+
* security boundary, not tidiness: our own 161-164 carry session handshakes,
|
|
92
|
+
* dora control and IP frames, so a caller allowed to pick those ids could
|
|
93
|
+
* forge a handshake or inject a packet onto a peer's TUN.
|
|
94
|
+
*/
|
|
95
|
+
sendAppPacket(userid: string, id: number, data: Uint8Array): Promise<void>;
|
|
84
96
|
/** Send a file INLINE over the message channel (FileModel JSON envelope,
|
|
85
97
|
* bulkmsg-split) — the only file path native iOS/C Carrier clients can
|
|
86
98
|
* receive online. Use for native friends; JS friends keep sendFile(). */
|
|
@@ -7,7 +7,7 @@ import { EventEmitter } from "events";
|
|
|
7
7
|
import { OfflineCallBridge } from "./offline-call.js";
|
|
8
8
|
import { PacketSession } from "./packet-session.js";
|
|
9
9
|
import { FrameCodec } from "./frame.js";
|
|
10
|
-
import { FRAME_OPCODE_HANDSHAKE_ACK, PACKET_ID_DL_SESSION, PACKET_ID_DL_DORA, PACKET_ID_DL_IP, PACKET_ID_DL_RATE, } from "./types.js";
|
|
10
|
+
import { FRAME_OPCODE_HANDSHAKE_ACK, PACKET_ID_DL_SESSION, PACKET_ID_DL_DORA, PACKET_ID_DL_IP, PACKET_ID_DL_RATE, isAppPacketId, } from "./types.js";
|
|
11
11
|
import { Logger } from "../utils/logger.js";
|
|
12
12
|
/**
|
|
13
13
|
* True when a Carrier invite send failed for a reason that will recur
|
|
@@ -165,6 +165,25 @@ export class PeerManager extends EventEmitter {
|
|
|
165
165
|
throw new Error("Peer not created. Call create() first.");
|
|
166
166
|
return this.peer.sendFile(userid, data, { name });
|
|
167
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Send an APPLICATION packet to a friend. The payload is opaque to
|
|
170
|
+
* decentlan — this is the extension point that lets an app (beagle, an
|
|
171
|
+
* agent, any IPC client) run its own protocol over our Carrier session
|
|
172
|
+
* without a change in here.
|
|
173
|
+
*
|
|
174
|
+
* `id` must be in PACKET_ID_APP_MIN..MAX. Rejecting anything else is a
|
|
175
|
+
* security boundary, not tidiness: our own 161-164 carry session handshakes,
|
|
176
|
+
* dora control and IP frames, so a caller allowed to pick those ids could
|
|
177
|
+
* forge a handshake or inject a packet onto a peer's TUN.
|
|
178
|
+
*/
|
|
179
|
+
async sendAppPacket(userid, id, data) {
|
|
180
|
+
if (!this.peer)
|
|
181
|
+
throw new Error("Peer not created. Call create() first.");
|
|
182
|
+
if (!isAppPacketId(id)) {
|
|
183
|
+
throw new Error(`packet id ${id} is outside the application range 165-191`);
|
|
184
|
+
}
|
|
185
|
+
await this.peer.sendCustomPacket(userid, id, data);
|
|
186
|
+
}
|
|
168
187
|
/** Send a file INLINE over the message channel (FileModel JSON envelope,
|
|
169
188
|
* bulkmsg-split) — the only file path native iOS/C Carrier clients can
|
|
170
189
|
* receive online. Use for native friends; JS friends keep sendFile(). */
|
|
@@ -687,6 +706,13 @@ export class PeerManager extends EventEmitter {
|
|
|
687
706
|
return;
|
|
688
707
|
}
|
|
689
708
|
this.handleDecodedFrame(pubkey, frame);
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
// Application range: hand the bytes up untouched. We do not parse them,
|
|
712
|
+
// so a malformed app payload can never reach any decentlan code path —
|
|
713
|
+
// validating it is the receiving app's job, at its own trust boundary.
|
|
714
|
+
if (isAppPacketId(id)) {
|
|
715
|
+
this.emit("app-packet", pubkey, id, data);
|
|
690
716
|
}
|
|
691
717
|
}
|
|
692
718
|
catch (error) {
|
package/dist/carrier/types.d.ts
CHANGED
|
@@ -12,3 +12,7 @@ export declare const PACKET_ID_DL_SESSION = 161;
|
|
|
12
12
|
export declare const PACKET_ID_DL_DORA = 162;
|
|
13
13
|
export declare const PACKET_ID_DL_IP = 163;
|
|
14
14
|
export declare const PACKET_ID_DL_RATE = 164;
|
|
15
|
+
export declare const PACKET_ID_APP_MIN = 165;
|
|
16
|
+
export declare const PACKET_ID_APP_MAX = 191;
|
|
17
|
+
/** True when `id` is in the application range apps may send and receive on. */
|
|
18
|
+
export declare function isAppPacketId(id: unknown): id is number;
|
package/dist/carrier/types.js
CHANGED
|
@@ -31,3 +31,23 @@ export const PACKET_ID_DL_IP = 163; // lossless: IP data frames (must traverse r
|
|
|
31
31
|
// so it stops overrunning the path and inducing congestion drops. Payload: a
|
|
32
32
|
// single uint32be = delivered bytes/sec. Rare + tiny, so lossless is free.
|
|
33
33
|
export const PACKET_ID_DL_RATE = 164;
|
|
34
|
+
// --- application packet range -----------------------------------------------
|
|
35
|
+
// 165-191 is handed to APPLICATIONS built on this daemon (beagle, agents, any
|
|
36
|
+
// IPC client). decentlan carries these bytes and never interprets them, so an
|
|
37
|
+
// app can add a protocol of its own without a change here — the reason this
|
|
38
|
+
// range exists at all is so no app ever has to modify the SDK again.
|
|
39
|
+
//
|
|
40
|
+
// Lossless (160-191), like everything else we own: an app-level control message
|
|
41
|
+
// that vanishes on a relay-only session is worse than useless. See the note on
|
|
42
|
+
// PACKET_ID_DL_IP for why lossy is not an option on GFW-crossing paths.
|
|
43
|
+
//
|
|
44
|
+
// The range deliberately STARTS above our own ids. An IPC client is local, but
|
|
45
|
+
// it is not necessarily this daemon's code, and letting it emit on 161-164
|
|
46
|
+
// would let it forge session handshakes, dora control, or IP frames at a peer —
|
|
47
|
+
// i.e. inject onto someone's TUN. The bound is enforced on send AND on receive.
|
|
48
|
+
export const PACKET_ID_APP_MIN = 165;
|
|
49
|
+
export const PACKET_ID_APP_MAX = 191;
|
|
50
|
+
/** True when `id` is in the application range apps may send and receive on. */
|
|
51
|
+
export function isAppPacketId(id) {
|
|
52
|
+
return typeof id === "number" && Number.isInteger(id) && id >= PACKET_ID_APP_MIN && id <= PACKET_ID_APP_MAX;
|
|
53
|
+
}
|
package/dist/daemon/ipc.d.ts
CHANGED
|
@@ -94,6 +94,12 @@ export interface IpcHandlers {
|
|
|
94
94
|
* Carrier "carrier" friend-invite extension — the channel iOS/Android Beagle
|
|
95
95
|
* listen on for calls. Backs the desktop UI's audio/video calls. */
|
|
96
96
|
callSignal: (userid: string, data: string) => Promise<void>;
|
|
97
|
+
/** Send an application packet (opaque bytes on a Carrier custom-packet id in
|
|
98
|
+
* the 165-191 app range) to a friend. The extension point for apps built on
|
|
99
|
+
* this daemon: beagle's chat components ride it, and so can anything else,
|
|
100
|
+
* without adding an op per feature. Inbound ones arrive as
|
|
101
|
+
* `{type:"app", userid, packetId, data}` events on `subscribe`. */
|
|
102
|
+
appSend: (userid: string, packetId: number, data: Uint8Array) => Promise<void>;
|
|
97
103
|
/** Long-poll for inbound call-signaling payloads. Resolves with any queued
|
|
98
104
|
* signals immediately, otherwise holds the connection until one arrives or a
|
|
99
105
|
* ~20s timeout elapses (then resolves empty). The UI re-polls to get
|
|
@@ -121,7 +127,7 @@ export interface IpcHandlers {
|
|
|
121
127
|
selfRestart: () => Promise<Record<string, unknown>>;
|
|
122
128
|
}
|
|
123
129
|
export interface IpcRequest {
|
|
124
|
-
op: "friend-request" | "ping" | "diag" | "friends-pending" | "friends-accept" | "friends-reject" | "chat-send" | "chat-log-local" | "file-log-local" | "chat-history" | "friends-list" | "friend-remove" | "friend-set-alias" | "friends-autoaccept" | "set-profile" | "file-send" | "file-delete" | "file-cancel" | "file-retry" | "chat-mark-read" | "subscribe" | "sign" | "call-signal" | "call-poll" | "proxy-reload" | "proxy-access" | "self-restart";
|
|
130
|
+
op: "friend-request" | "ping" | "diag" | "friends-pending" | "friends-accept" | "friends-reject" | "chat-send" | "chat-log-local" | "file-log-local" | "chat-history" | "friends-list" | "friend-remove" | "friend-set-alias" | "friends-autoaccept" | "set-profile" | "file-send" | "file-delete" | "file-cancel" | "file-retry" | "chat-mark-read" | "subscribe" | "sign" | "call-signal" | "call-poll" | "proxy-reload" | "proxy-access" | "app-send" | "self-restart";
|
|
125
131
|
address?: string;
|
|
126
132
|
hello?: string;
|
|
127
133
|
userid?: string;
|
|
@@ -137,8 +143,11 @@ export interface IpcRequest {
|
|
|
137
143
|
ts?: number;
|
|
138
144
|
enabled?: boolean;
|
|
139
145
|
ids?: string[];
|
|
140
|
-
/** RtcSignal JSON payload for the "call-signal" op
|
|
146
|
+
/** RtcSignal JSON payload for the "call-signal" op; base64 payload for
|
|
147
|
+
* "app-send". */
|
|
141
148
|
data?: string;
|
|
149
|
+
/** Carrier custom-packet id for the "app-send" op (165-191). */
|
|
150
|
+
packetId?: number;
|
|
142
151
|
}
|
|
143
152
|
export interface IpcResponseOk {
|
|
144
153
|
ok: true;
|
package/dist/daemon/ipc.js
CHANGED
|
@@ -276,6 +276,24 @@ export class IpcServer {
|
|
|
276
276
|
await this.handlers.callSignal(req.userid, req.data);
|
|
277
277
|
return;
|
|
278
278
|
}
|
|
279
|
+
case "app-send": {
|
|
280
|
+
if (!req.userid)
|
|
281
|
+
throw new Error("userid is required");
|
|
282
|
+
if (typeof req.packetId !== "number")
|
|
283
|
+
throw new Error("packetId is required");
|
|
284
|
+
if (typeof req.data !== "string")
|
|
285
|
+
throw new Error("data is required (base64)");
|
|
286
|
+
// Base64 is the only sane way to carry bytes through a newline-delimited
|
|
287
|
+
// JSON socket. Buffer.from is lenient — it silently skips invalid chars
|
|
288
|
+
// rather than throwing — so re-encode and compare: a caller that sends
|
|
289
|
+
// corrupt base64 must be told, not have truncated bytes put on the wire.
|
|
290
|
+
const bytes = Buffer.from(req.data, "base64");
|
|
291
|
+
if (bytes.toString("base64").replace(/=+$/, "") !== req.data.replace(/=+$/, "")) {
|
|
292
|
+
throw new Error("data is not valid base64");
|
|
293
|
+
}
|
|
294
|
+
await this.handlers.appSend(req.userid, req.packetId, new Uint8Array(bytes));
|
|
295
|
+
return { sent: bytes.length };
|
|
296
|
+
}
|
|
279
297
|
case "call-poll":
|
|
280
298
|
return await this.handlers.callPoll();
|
|
281
299
|
case "proxy-reload":
|
package/dist/daemon/server.js
CHANGED
|
@@ -698,6 +698,11 @@ export class DaemonServer {
|
|
|
698
698
|
// session (throws if the peer is unreachable, so the UI can surface it).
|
|
699
699
|
await this.peerManager.sendCallSignal(userid, data);
|
|
700
700
|
},
|
|
701
|
+
appSend: async (userid, packetId, data) => {
|
|
702
|
+
// Opaque passthrough. The range check lives in PeerManager so it
|
|
703
|
+
// applies to every caller, not just this socket.
|
|
704
|
+
await this.peerManager.sendAppPacket(userid, packetId, data);
|
|
705
|
+
},
|
|
701
706
|
callPoll: async () => {
|
|
702
707
|
// Drain immediately if signals are queued; otherwise hold up to ~20s
|
|
703
708
|
// for one to arrive (near-instant delivery without a WebSocket). The
|
|
@@ -1196,6 +1201,18 @@ export class DaemonServer {
|
|
|
1196
1201
|
for (const wake of waiters)
|
|
1197
1202
|
wake();
|
|
1198
1203
|
});
|
|
1204
|
+
// Application packets (165-191) — push straight to IPC subscribers as
|
|
1205
|
+
// base64. The daemon never parses them: whatever protocol an app runs up
|
|
1206
|
+
// there is the app's business, and keeping it opaque is what lets apps
|
|
1207
|
+
// evolve without touching this SDK.
|
|
1208
|
+
this.peerManager.on("app-packet", (pubkey, packetId, data) => {
|
|
1209
|
+
this.ipcEvents.emit("event", {
|
|
1210
|
+
type: "app",
|
|
1211
|
+
userid: pubkey,
|
|
1212
|
+
packetId,
|
|
1213
|
+
data: Buffer.from(data).toString("base64"),
|
|
1214
|
+
});
|
|
1215
|
+
});
|
|
1199
1216
|
this.peerManager.on("friend-request", (req) => {
|
|
1200
1217
|
void pubkeyHexToUserid(req.pubkey).then((userid) => {
|
|
1201
1218
|
const who = `${req.name || "(unnamed)"} ${userid}`;
|
package/dist/ui/desktop/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__DK_UI_VERSION="0.1.
|
|
1
|
+
window.__DK_UI_VERSION="0.1.257";
|
|
2
2
|
const ICON_PATHS = {
|
|
3
3
|
// ---- tab bar (the four must feel like one set) ----
|
|
4
4
|
users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.257",
|
|
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",
|