@decentnetwork/lan 0.1.156 → 0.1.157

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
@@ -62,6 +62,14 @@ export declare class PeerManager extends EventEmitter {
62
62
  sign(message: Uint8Array): Uint8Array;
63
63
  /** Offer a file to a friend (toxcore-standard transfer). Returns the fileId. */
64
64
  sendFile(userid: string, data: Uint8Array, name: string): string | null;
65
+ /** Send a file INLINE over the message channel (FileModel JSON envelope,
66
+ * bulkmsg-split) — the only file path native iOS/C Carrier clients can
67
+ * receive online. Use for native friends; JS friends keep sendFile(). */
68
+ sendInlineFile(userid: string, data: Uint8Array, name: string): Promise<void>;
69
+ /** True when the friend is a NATIVE Carrier client (iOS/Android/C): its
70
+ * relay/DHT key differs from its identity key. JS peers announce their
71
+ * identity key as their DHT key, so the two match. */
72
+ isNativeFriend(userid: string): boolean;
65
73
  /** Accept an incoming file offer. */
66
74
  acceptFile(userid: string, fileNumber: number): void;
67
75
  /** Cancel an in-flight send by content fileId. Returns true if it was found. */
@@ -121,6 +121,21 @@ export class PeerManager extends EventEmitter {
121
121
  throw new Error("Peer not created. Call create() first.");
122
122
  return this.peer.sendFile(userid, data, { name });
123
123
  }
124
+ /** Send a file INLINE over the message channel (FileModel JSON envelope,
125
+ * bulkmsg-split) — the only file path native iOS/C Carrier clients can
126
+ * receive online. Use for native friends; JS friends keep sendFile(). */
127
+ async sendInlineFile(userid, data, name) {
128
+ if (!this.peer)
129
+ throw new Error("Peer not created. Call create() first.");
130
+ await this.peer.sendInlineFile(userid, { name, data });
131
+ }
132
+ /** True when the friend is a NATIVE Carrier client (iOS/Android/C): its
133
+ * relay/DHT key differs from its identity key. JS peers announce their
134
+ * identity key as their DHT key, so the two match. */
135
+ isNativeFriend(userid) {
136
+ const rec = this.peer?.friends().find((f) => f.pubkey === userid);
137
+ return !!(rec?.dhtPubkey && rec.dhtPubkey !== rec.pubkey);
138
+ }
124
139
  /** Accept an incoming file offer. */
125
140
  acceptFile(userid, fileNumber) {
126
141
  this.peer?.acceptFile(userid, fileNumber);
@@ -516,6 +531,9 @@ export class PeerManager extends EventEmitter {
516
531
  }
517
532
  this.emit("message", message.pubkey, message.text, message.via);
518
533
  });
534
+ // Inline files (iOS/C Carrier style: FileModel JSON over bulkmsg).
535
+ // The SDK reassembles + decodes; forward to the daemon to save + log.
536
+ this.peer.onInlineFile((f) => this.emit("inline-file", f));
519
537
  // Toxcore-standard file transfer (native-compatible). Forward offers,
520
538
  // progress, and completions to the daemon, which auto-accepts and saves.
521
539
  this.peer.onFile((o) => this.emit("file-offer", o));
@@ -455,6 +455,23 @@ export class DaemonServer {
455
455
  this.logger.info(`Queued file "${name}" (${data.length}B) for offline ${userid.slice(0, 8)} (delivers on reconnect)`);
456
456
  return { queued: true, name: safe, size: data.length };
457
457
  }
458
+ // NATIVE friends (iOS/Android/C Carrier) cannot see the toxcore
459
+ // file-transfer protocol at all — the only online file path they
460
+ // receive is the inline FileModel-JSON-over-bulkmsg envelope the
461
+ // Beagle apps themselves use. Detected via the DHT-key signature.
462
+ if (this.peerManager?.isNativeFriend(userid)) {
463
+ const INLINE_MAX = 2 * 1024 * 1024; // base64 inflates ~1.37x; protocol cap is 5MB
464
+ if (data.length > INLINE_MAX) {
465
+ throw new Error(`This friend is a native (iOS/Android) client — files are sent inline and limited to ` +
466
+ `${(INLINE_MAX / 1024 / 1024) | 0} MB (this is ${(data.length / 1024 / 1024).toFixed(1)} MB).`);
467
+ }
468
+ await this.peerManager.sendInlineFile(userid, new Uint8Array(data), name);
469
+ this.logger.info(`Sent inline file "${name}" (${data.length}B) to native peer ${userid.slice(0, 8)}`);
470
+ this.messageStore?.appendFile(userid, "out", { name: sanitizeFileName(name), size: data.length, status: "sent", sent: data.length });
471
+ this.friendMeta?.ensure(userid);
472
+ this.ipcEvents.emit("event", { type: "chat", userid, dir: "out" });
473
+ return { inline: true, name, size: data.length };
474
+ }
458
475
  const fileId = this.peerManager?.sendFile(userid, new Uint8Array(data), name);
459
476
  if (!fileId)
460
477
  throw new Error("No free transfer slot (or friend unknown)");
@@ -898,6 +915,22 @@ export class DaemonServer {
898
915
  this.ipcEvents.emit("event", { type: "chat", userid: p.friendId, dir: "in" });
899
916
  })().catch((e) => this.logger.warn(`Failed to save file: ${e.message}`));
900
917
  });
918
+ // Inline files (iOS/C Carrier style — FileModel JSON over bulkmsg,
919
+ // reassembled + decoded by the SDK). Save like a completed transfer so
920
+ // the chat shows a media chip instead of a wall of base64 "text".
921
+ this.peerManager.on("inline-file", (f) => {
922
+ void (async () => {
923
+ const dir = resolve(this.configDir, "downloads");
924
+ const fs = await import("fs/promises");
925
+ await fs.mkdir(dir, { recursive: true });
926
+ const safe = sanitizeFileName(f.name);
927
+ await fs.writeFile(resolve(dir, safe), Buffer.from(f.data));
928
+ this.logger.info(`Saved inline file "${f.name}" (${f.data.length}B, ${f.via ?? "online"}) from ${f.pubkey.slice(0, 8)} → ${resolve(dir, safe)}`);
929
+ this.messageStore?.appendFile(f.pubkey, "in", { name: safe, size: f.data.length });
930
+ this.friendMeta?.ensure(f.pubkey);
931
+ this.ipcEvents.emit("event", { type: "chat", userid: f.pubkey, dir: "in" });
932
+ })().catch((e) => this.logger.warn(`Failed to save inline file: ${e.message}`));
933
+ });
901
934
  this.peerManager.on("friend-request", (req) => {
902
935
  void pubkeyHexToUserid(req.pubkey).then((userid) => {
903
936
  const who = `${req.name || "(unnamed)"} ${userid}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/lan",
3
- "version": "0.1.156",
3
+ "version": "0.1.157",
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",
@@ -79,7 +79,7 @@
79
79
  },
80
80
  "dependencies": {
81
81
  "@decentnetwork/dora": "^0.1.13",
82
- "@decentnetwork/peer": "^0.1.81",
82
+ "@decentnetwork/peer": "^0.1.82",
83
83
  "ink": "^5.2.1",
84
84
  "js-yaml": "^4.1.0",
85
85
  "node-forge": "^1.4.0",