@decentnetwork/peer 0.1.81 → 0.1.83

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.
@@ -1,6 +1,9 @@
1
1
  export declare const PACKET_TYPE_USERINFO = 3;
2
2
  export declare const PACKET_TYPE_FRIEND_REQUEST = 6;
3
3
  export declare const PACKET_TYPE_MESSAGE = 33;
4
+ export declare const PACKET_TYPE_BULKMSG = 36;
5
+ export declare const CARRIER_MAX_APP_MESSAGE_LEN = 1024;
6
+ export declare const CARRIER_MAX_APP_BULKMSG_LEN: number;
4
7
  export type UserInfoPacket = {
5
8
  type: typeof PACKET_TYPE_USERINFO;
6
9
  avatar: boolean;
@@ -22,7 +25,16 @@ export type FriendMessagePacket = {
22
25
  ext?: string;
23
26
  data: Uint8Array;
24
27
  };
25
- export type CarrierPacket = UserInfoPacket | FriendRequestPacket | FriendMessagePacket;
28
+ export type BulkMsgPacket = {
29
+ type: typeof PACKET_TYPE_BULKMSG;
30
+ ext?: string;
31
+ /** Full message length — set on the FIRST fragment only, 0 on the rest. */
32
+ totalsz: number;
33
+ /** Shared id linking all fragments of one message. */
34
+ tid: bigint;
35
+ data: Uint8Array;
36
+ };
37
+ export type CarrierPacket = UserInfoPacket | FriendRequestPacket | FriendMessagePacket | BulkMsgPacket;
26
38
  /**
27
39
  * Encode a Carrier `userinfo` packet (FB type 3). The Carrier C SDK sends
28
40
  * this every time the local user's name / status message / profile fields
@@ -52,4 +64,15 @@ export declare function encodeFriendRequestPacket(opts: {
52
64
  hello?: string;
53
65
  }): Uint8Array;
54
66
  export declare function encodeFriendMessagePacket(data: Uint8Array | string, ext?: string): Uint8Array;
67
+ /**
68
+ * Encode one Carrier `bulkmsg` fragment (PACKET_TYPE_BULKMSG = 36). Field
69
+ * layout per src/carrier/packet.fbs: 0=ext string, 1=totalsz uint,
70
+ * 2=tid long, 3=data [ubyte]. Mirrors the C SDK's send_bulk_message.
71
+ */
72
+ export declare function encodeBulkMsgPacket(opts: {
73
+ ext?: string;
74
+ totalsz: number;
75
+ tid: bigint;
76
+ data: Uint8Array;
77
+ }): Uint8Array;
55
78
  export declare function decodeCarrierPacket(bytes: Uint8Array): CarrierPacket;
@@ -2,12 +2,20 @@ import { Builder, ByteBuffer, Encoding } from "flatbuffers";
2
2
  export const PACKET_TYPE_USERINFO = 3;
3
3
  export const PACKET_TYPE_FRIEND_REQUEST = 6;
4
4
  export const PACKET_TYPE_MESSAGE = 33;
5
+ export const PACKET_TYPE_BULKMSG = 36;
6
+ // Carrier C SDK message-size constants (carrier.h). A single friendmsg may
7
+ // carry at most 1024 bytes; anything larger is split into BULKMSG fragments
8
+ // of <=1024 raw bytes sharing one tid (totalsz set on the FIRST fragment
9
+ // only), reassembled in arrival order by the receiver.
10
+ export const CARRIER_MAX_APP_MESSAGE_LEN = 1024;
11
+ export const CARRIER_MAX_APP_BULKMSG_LEN = 5 * 1024 * 1024;
5
12
  // Body type indices into FlatBuffers `anybody` union (carrier/packet.fbs).
6
13
  // The .fbs declares: userinfo, friendreq, friendmsg, invitereq, invitersp,
7
14
  // bulkmsg — flatbuffers maps those to 1, 2, 3, 4, 5, 6 (NONE = 0).
8
15
  const ANYBODY_USERINFO = 1;
9
16
  const ANYBODY_FRIENDREQ = 2;
10
17
  const ANYBODY_FRIENDMSG = 3;
18
+ const ANYBODY_BULKMSG = 6;
11
19
  /**
12
20
  * Encode a Carrier `userinfo` packet (FB type 3). The Carrier C SDK sends
13
21
  * this every time the local user's name / status message / profile fields
@@ -69,6 +77,29 @@ export function encodeFriendMessagePacket(data, ext) {
69
77
  const friendMsg = builder.endObject();
70
78
  return finishCarrierPacket(builder, PACKET_TYPE_MESSAGE, ANYBODY_FRIENDMSG, friendMsg);
71
79
  }
80
+ /**
81
+ * Encode one Carrier `bulkmsg` fragment (PACKET_TYPE_BULKMSG = 36). Field
82
+ * layout per src/carrier/packet.fbs: 0=ext string, 1=totalsz uint,
83
+ * 2=tid long, 3=data [ubyte]. Mirrors the C SDK's send_bulk_message.
84
+ */
85
+ export function encodeBulkMsgPacket(opts) {
86
+ const builder = new Builder(opts.data.length + 128);
87
+ const dataOffset = builder.createByteVector(opts.data);
88
+ const extOffset = opts.ext ? builder.createString(opts.ext) : 0;
89
+ builder.startObject(4);
90
+ builder.addFieldOffset(3, dataOffset, 0);
91
+ if (opts.tid !== 0n) {
92
+ builder.addFieldInt64(2, opts.tid, BigInt(0));
93
+ }
94
+ if (opts.totalsz) {
95
+ builder.addFieldInt32(1, opts.totalsz, 0);
96
+ }
97
+ if (extOffset) {
98
+ builder.addFieldOffset(0, extOffset, 0);
99
+ }
100
+ const bulk = builder.endObject();
101
+ return finishCarrierPacket(builder, PACKET_TYPE_BULKMSG, ANYBODY_BULKMSG, bulk);
102
+ }
72
103
  export function decodeCarrierPacket(bytes) {
73
104
  const bb = new ByteBuffer(bytes);
74
105
  const root = bb.readInt32(bb.position()) + bb.position();
@@ -106,6 +137,15 @@ export function decodeCarrierPacket(bytes) {
106
137
  data: readByteVectorField(body, 1) ?? new Uint8Array()
107
138
  };
108
139
  }
140
+ if (type === PACKET_TYPE_BULKMSG && bodyType === ANYBODY_BULKMSG) {
141
+ return {
142
+ type,
143
+ ext: readStringField(body, 0) ?? undefined,
144
+ totalsz: readUint32Field(body, 1),
145
+ tid: readInt64Field(body, 2),
146
+ data: readByteVectorField(body, 3) ?? new Uint8Array()
147
+ };
148
+ }
109
149
  throw new Error(`unsupported carrier packet type/body: ${type}/${bodyType}`);
110
150
  }
111
151
  function finishCarrierPacket(builder, type, bodyType, bodyOffset) {
@@ -125,6 +165,14 @@ function readUint8Field(table, field) {
125
165
  const offset = table.bb.__offset(table.bb_pos, 4 + field * 2);
126
166
  return offset ? table.bb.readUint8(table.bb_pos + offset) : 0;
127
167
  }
168
+ function readUint32Field(table, field) {
169
+ const offset = table.bb.__offset(table.bb_pos, 4 + field * 2);
170
+ return offset ? table.bb.readUint32(table.bb_pos + offset) : 0;
171
+ }
172
+ function readInt64Field(table, field) {
173
+ const offset = table.bb.__offset(table.bb_pos, 4 + field * 2);
174
+ return offset ? table.bb.readInt64(table.bb_pos + offset) : BigInt(0);
175
+ }
128
176
  function readStringField(table, field) {
129
177
  const offset = table.bb.__offset(table.bb_pos, 4 + field * 2);
130
178
  if (!offset) {
package/dist/peer.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type BootstrapResult } from "./compat/bootstrap.js";
2
2
  import type { FriendRecord } from "./store/friends.js";
3
- import type { CustomPacketEvent, FriendConnectionEvent, FriendRequest, FriendInfoEvent, LookupResult, NetworkNode, PeerOptions, TextMessage } from "./types/peer.js";
3
+ import type { CustomPacketEvent, FriendConnectionEvent, FriendRequest, FriendInfoEvent, InlineFileEvent, LookupResult, NetworkNode, PeerOptions, TextMessage } from "./types/peer.js";
4
4
  export declare class Peer {
5
5
  #private;
6
6
  private constructor();
@@ -82,6 +82,23 @@ export declare class Peer {
82
82
  waitForFriendConnected(pubkey: string, timeoutMs?: number): Promise<boolean>;
83
83
  onFriendRequest(cb: (req: FriendRequest) => void): void;
84
84
  onText(cb: (msg: TextMessage) => void): void;
85
+ /** Files received inline over (bulk)messages — the iOS/C Carrier apps'
86
+ * native way of sending images/audio online (FileModel JSON envelope). */
87
+ onInlineFile(cb: (evt: InlineFileEvent) => void): void;
88
+ /**
89
+ * Send a file INLINE over the message channel the way iOS/C Carrier apps
90
+ * do: a FileModel JSON envelope ({data: base64, fileExtension, fileName,
91
+ * type}) carried as a (bulk)message. This is the ONLY file path a native
92
+ * Beagle client can receive online — the toxcore file transfer (80-82)
93
+ * used by sendFile() is invisible to the Carrier C SDK. Best for images
94
+ * and small files; the hard protocol cap is ~5MB (base64-inflated), keep
95
+ * real use well below it.
96
+ */
97
+ sendInlineFile(pubkey: string, opts: {
98
+ name: string;
99
+ data: Uint8Array;
100
+ fileType?: "image" | "audio" | "text" | "unknown";
101
+ }): Promise<void>;
85
102
  /**
86
103
  * Send an application-defined custom packet to a friend. `id` selects the
87
104
  * channel and its delivery class by toxcore range: **160–191 lossless**
package/dist/peer.js CHANGED
@@ -5,7 +5,7 @@ import { dirname, join } from "node:path";
5
5
  import nacl from "tweetnacl";
6
6
  import { carrierAddressFromPublicKey, carrierIdFromAddress, carrierIdFromPublicKey, parseCarrierAddress } from "./compat/address.js";
7
7
  import { LegacyBootstrapClient } from "./compat/bootstrap.js";
8
- import { PACKET_TYPE_MESSAGE, PACKET_TYPE_FRIEND_REQUEST, PACKET_TYPE_USERINFO, encodeUserInfoPacket, decodeCarrierPacket, encodeFriendMessagePacket, encodeFriendRequestPacket } from "./compat/packet.js";
8
+ import { PACKET_TYPE_MESSAGE, PACKET_TYPE_FRIEND_REQUEST, PACKET_TYPE_USERINFO, PACKET_TYPE_BULKMSG, CARRIER_MAX_APP_MESSAGE_LEN, CARRIER_MAX_APP_BULKMSG_LEN, encodeUserInfoPacket, decodeCarrierPacket, encodeFriendMessagePacket, encodeFriendRequestPacket, encodeBulkMsgPacket } from "./compat/packet.js";
9
9
  import { NET_PACKET_ONION_ANNOUNCE_RESPONSE, NET_PACKET_ONION_DATA_RESPONSE, ONION_FRIEND_REQUEST_ID, createOnionAnnounceRequest, createOnionDataPacket, createOnionDataRequest, createOnionRequest0, openOnionAnnounceResponse, openOnionDataPacket, openOnionDataResponse } from "./compat/tox-onion.js";
10
10
  import { CRYPTO_PACKET_DHTPK, CRYPTO_PACKET_FRIEND_REQ, NET_PACKET_CRYPTO, createToxDhtCryptoRequest, openToxDhtCryptoRequest } from "./compat/tox-dht-crypto.js";
11
11
  import { NET_PACKET_PING_REQUEST, NET_PACKET_PING_RESPONSE, NET_PACKET_GET_NODES, NET_PACKET_SEND_NODES, encodeDhtRpc, decodeDhtRpc, buildPingPlain, parsePingPlain, buildGetNodesPlain, parseGetNodesPlain, buildSendNodesPlain, parseSendNodesNodeBytes, packUdpNodeV4 } from "./compat/dht-rpc.js";
@@ -306,6 +306,11 @@ export class Peer {
306
306
  // #initiateSession every few seconds for every friend; without this
307
307
  // dedupe, lower-pubkey side floods the log with "deferring to peer".
308
308
  #initiateSkipLogged = new Set();
309
+ // In-flight Carrier BULKMSG reassemblies, keyed `${friendId}:${tid}`.
310
+ // Fragments are appended in arrival order (the lossless stream keeps
311
+ // them ordered) and the message completes when totalsz bytes arrived.
312
+ // Mirrors the C SDK's bulkmsgs hashtable with its 60s assembly timeout.
313
+ #bulkAssembly = new Map();
309
314
  // When we FIRST deferred initiation to a higher-pubkey peer (per friend).
310
315
  // If the peer hasn't established within the grace window we break the
311
316
  // defer and initiate ourselves (see #initiateSession). Cleared whenever a
@@ -955,6 +960,30 @@ export class Peer {
955
960
  }
956
961
  }
957
962
  const packet = encodeFriendMessagePacket(text);
963
+ // Carrier C peers accept at most 1024 raw bytes per friendmsg; larger
964
+ // messages MUST be split into BULKMSG fragments (shared tid, totalsz on
965
+ // the first fragment) or a native receiver drops them. The express
966
+ // fallback below still posts the single big MESSAGE packet — that's
967
+ // what the C SDK does offline too.
968
+ const rawText = new TextEncoder().encode(text);
969
+ let livePackets;
970
+ if (rawText.length > CARRIER_MAX_APP_MESSAGE_LEN) {
971
+ if (rawText.length > CARRIER_MAX_APP_BULKMSG_LEN) {
972
+ throw new Error(`message exceeds bulk limit (${rawText.length} > ${CARRIER_MAX_APP_BULKMSG_LEN})`);
973
+ }
974
+ const tid = (BigInt(Date.now()) << 20n) ^ BigInt(Math.floor(Math.random() * 0xfffff));
975
+ livePackets = [];
976
+ for (let off = 0; off < rawText.length; off += CARRIER_MAX_APP_MESSAGE_LEN) {
977
+ livePackets.push(encodeBulkMsgPacket({
978
+ totalsz: off === 0 ? rawText.length : 0,
979
+ tid,
980
+ data: rawText.subarray(off, Math.min(off + CARRIER_MAX_APP_MESSAGE_LEN, rawText.length))
981
+ }));
982
+ }
983
+ }
984
+ else {
985
+ livePackets = [packet];
986
+ }
958
987
  // Try the live in-session path first when any transport is up
959
988
  // (UDP via session.remote, or TCP relay via session.hasTcpRoute).
960
989
  // #sendMessengerPacket internally fans out to both transports and
@@ -980,7 +1009,9 @@ export class Peer {
980
1009
  && provenInbound;
981
1010
  if (liveSession) {
982
1011
  try {
983
- await this.#sendMessengerPacket(pubkey, PACKET_ID_MESSAGE, packet);
1012
+ for (const p of livePackets) {
1013
+ await this.#sendMessengerPacket(pubkey, PACKET_ID_MESSAGE, p);
1014
+ }
984
1015
  return;
985
1016
  }
986
1017
  catch (error) {
@@ -1060,6 +1091,40 @@ export class Peer {
1060
1091
  onText(cb) {
1061
1092
  this.#events.on("text", cb);
1062
1093
  }
1094
+ /** Files received inline over (bulk)messages — the iOS/C Carrier apps'
1095
+ * native way of sending images/audio online (FileModel JSON envelope). */
1096
+ onInlineFile(cb) {
1097
+ this.#events.on("inlineFile", cb);
1098
+ }
1099
+ /**
1100
+ * Send a file INLINE over the message channel the way iOS/C Carrier apps
1101
+ * do: a FileModel JSON envelope ({data: base64, fileExtension, fileName,
1102
+ * type}) carried as a (bulk)message. This is the ONLY file path a native
1103
+ * Beagle client can receive online — the toxcore file transfer (80-82)
1104
+ * used by sendFile() is invisible to the Carrier C SDK. Best for images
1105
+ * and small files; the hard protocol cap is ~5MB (base64-inflated), keep
1106
+ * real use well below it.
1107
+ */
1108
+ async sendInlineFile(pubkey, opts) {
1109
+ const dot = opts.name.lastIndexOf(".");
1110
+ const ext = dot > 0 ? opts.name.slice(dot).toLowerCase() : "";
1111
+ const base = dot > 0 ? opts.name.slice(0, dot) : opts.name;
1112
+ const type = opts.fileType ?? ([".png", ".jpg", ".jpeg", ".gif", ".webp", ".heic"].includes(ext) ? "image" :
1113
+ [".m4a", ".mp3", ".aac", ".wav", ".ogg"].includes(ext) ? "audio" :
1114
+ [".txt", ".md", ".log"].includes(ext) ? "text" : "unknown");
1115
+ // Key order matters for byte-identical envelopes with iOS (JSONEncoder
1116
+ // .sortedKeys): data, fileExtension, fileName, type — alphabetical.
1117
+ const envelope = JSON.stringify({
1118
+ data: Buffer.from(opts.data).toString("base64"),
1119
+ fileExtension: ext,
1120
+ fileName: base,
1121
+ type
1122
+ });
1123
+ if (envelope.length > CARRIER_MAX_APP_BULKMSG_LEN) {
1124
+ throw new Error(`inline file too large for the message channel (${envelope.length} bytes encoded, max ${CARRIER_MAX_APP_BULKMSG_LEN})`);
1125
+ }
1126
+ await this.sendText(pubkey, envelope);
1127
+ }
1063
1128
  /**
1064
1129
  * Send an application-defined custom packet to a friend. `id` selects the
1065
1130
  * channel and its delivery class by toxcore range: **160–191 lossless**
@@ -1994,14 +2059,43 @@ export class Peer {
1994
2059
  return;
1995
2060
  }
1996
2061
  if (kind === PACKET_ID_MESSAGE || kind === PACKET_ID_ACTION) {
1997
- // Plain text inside (no Carrier FlatBuffers wrapper). Some peers
1998
- // send raw UTF-8, others send a Carrier message packet — try both.
1999
- let text = tryDecodeCarrierMessagePacket(inner) ?? decodeUtf8Best(inner);
2062
+ this.#setFriendOnline(friendId, remote.address, remote.port);
2063
+ // Decode the Carrier FlatBuffers wrapper when present. A BULKMSG
2064
+ // fragment (Carrier C splits any message >1024 bytes this way —
2065
+ // e.g. iOS Beagle images, which are FileModel JSON envelopes)
2066
+ // goes to the reassembler and only emits once complete; before
2067
+ // this, every fragment surfaced as a separate garbage "text".
2068
+ let text;
2069
+ let carrier;
2070
+ try {
2071
+ carrier = decodeCarrierPacket(inner);
2072
+ }
2073
+ catch { /* raw utf-8 peer */ }
2074
+ if (carrier?.type === PACKET_TYPE_BULKMSG) {
2075
+ const complete = this.#assembleBulkMsg(friendId, carrier);
2076
+ if (!complete)
2077
+ return; // more fragments pending
2078
+ text = decodeUtf8Best(complete);
2079
+ this.#debugLog(`bulkmsg complete from ${friendId} (${complete.length} bytes)`);
2080
+ }
2081
+ else if (carrier?.type === PACKET_TYPE_MESSAGE) {
2082
+ text = new TextDecoder().decode(carrier.data);
2083
+ }
2084
+ else {
2085
+ text = decodeUtf8Best(inner);
2086
+ }
2087
+ // Carrier C peers terminate strings with a NUL — strip it so chat
2088
+ // text doesn't carry an invisible trailing byte.
2089
+ text = text?.replace(/\0+$/u, "");
2000
2090
  if (!text) {
2001
2091
  this.#debugLog(`crypto message packet decode failed from ${friendId}`);
2002
2092
  return;
2003
2093
  }
2004
- this.#setFriendOnline(friendId, remote.address, remote.port);
2094
+ // iOS Beagle sends files inline as a FileModel JSON envelope
2095
+ // ({data: base64, fileExtension, fileName, type}) — surface those
2096
+ // as files, not as a wall of base64 text.
2097
+ if (this.#tryEmitInlineFile(friendId, text, "online"))
2098
+ return;
2005
2099
  this.#events.emit("text", {
2006
2100
  pubkey: friendId,
2007
2101
  text,
@@ -2370,6 +2464,10 @@ export class Peer {
2370
2464
  // Malformed pubkey, fall through.
2371
2465
  }
2372
2466
  }
2467
+ // Offline images/audio arrive as the same FileModel JSON envelope
2468
+ // (iOS posts the whole thing as one express MESSAGE packet).
2469
+ if (this.#tryEmitInlineFile(fromUserId, text, "offline"))
2470
+ return;
2373
2471
  this.#events.emit("text", { pubkey: fromUserId, text, via: "offline" });
2374
2472
  }
2375
2473
  catch {
@@ -3113,6 +3211,72 @@ export class Peer {
3113
3211
  });
3114
3212
  }
3115
3213
  }
3214
+ /** Append a Carrier BULKMSG fragment; returns the full reassembled
3215
+ * message when the last fragment lands, undefined while pending.
3216
+ * Mirrors the C SDK's handle_friend_bulkmsg: totalsz on the first
3217
+ * fragment sizes the buffer; fragments append in arrival order. */
3218
+ #assembleBulkMsg(friendId, frag) {
3219
+ const now = Date.now();
3220
+ // Lazy expiry sweep (C uses a 60s assembly timeout).
3221
+ for (const [key, entry] of this.#bulkAssembly) {
3222
+ if (entry.expireAtMs < now)
3223
+ this.#bulkAssembly.delete(key);
3224
+ }
3225
+ const key = `${friendId}:${frag.tid.toString()}`;
3226
+ let entry = this.#bulkAssembly.get(key);
3227
+ if (!entry) {
3228
+ if (!frag.totalsz || frag.totalsz > CARRIER_MAX_APP_BULKMSG_LEN) {
3229
+ this.#debugLog(`bulkmsg from ${friendId} with invalid/missing totalsz ${frag.totalsz} — dropped`);
3230
+ return undefined;
3231
+ }
3232
+ entry = { total: frag.totalsz, chunks: [], got: 0, expireAtMs: now + 60_000 };
3233
+ this.#bulkAssembly.set(key, entry);
3234
+ }
3235
+ if (frag.data.length === 0 || entry.got + frag.data.length > entry.total) {
3236
+ this.#debugLog(`bulkmsg fragment from ${friendId} overflows totalsz — dropped`);
3237
+ this.#bulkAssembly.delete(key);
3238
+ return undefined;
3239
+ }
3240
+ entry.chunks.push(frag.data);
3241
+ entry.got += frag.data.length;
3242
+ if (entry.got < entry.total)
3243
+ return undefined;
3244
+ this.#bulkAssembly.delete(key);
3245
+ return concatBytes(entry.chunks);
3246
+ }
3247
+ /** iOS Beagle sends files inline as a JSON envelope over (bulk)messages:
3248
+ * {"data":"<base64>","fileExtension":".jpg","fileName":"…","type":"image"}.
3249
+ * Detect it, decode, and emit an inlineFile event instead of dumping
3250
+ * base64 into the chat. Returns true when handled. */
3251
+ #tryEmitInlineFile(friendId, text, via) {
3252
+ // The Carrier C SDK sends messages with a trailing C-string NUL, which
3253
+ // JSON.parse rejects. iOS's own decoder strips NULs before parsing
3254
+ // (CodableUtil.decode: replacingOccurrences(of: "\0")) — mirror that.
3255
+ const trimmed = text.replaceAll("\0", "").trim();
3256
+ if (!trimmed.startsWith("{") || !trimmed.includes("\"fileName\"") || !trimmed.includes("\"data\"")) {
3257
+ return false;
3258
+ }
3259
+ try {
3260
+ const parsed = JSON.parse(trimmed);
3261
+ if (typeof parsed.data !== "string" || typeof parsed.fileName !== "string")
3262
+ return false;
3263
+ const data = Uint8Array.from(Buffer.from(parsed.data, "base64"));
3264
+ const ext = parsed.fileExtension ?? "";
3265
+ const name = parsed.fileName.endsWith(ext) ? parsed.fileName : parsed.fileName + ext;
3266
+ this.#events.emit("inlineFile", {
3267
+ pubkey: friendId,
3268
+ name,
3269
+ fileType: parsed.type,
3270
+ data,
3271
+ via
3272
+ });
3273
+ this.#debugLog(`inline file from ${friendId}: "${name}" (${data.length} bytes, ${via})`);
3274
+ return true;
3275
+ }
3276
+ catch {
3277
+ return false;
3278
+ }
3279
+ }
3116
3280
  /** Toxcore handle_request_packet: parse the peer's PACKET_ID_REQUEST and
3117
3281
  * (a) implicitly ACK every walked packet number that isn't requested,
3118
3282
  * (b) retransmit the requested ones from the send buffer. */
@@ -114,6 +114,22 @@ export type CustomPacketEvent = {
114
114
  id: number;
115
115
  data: Uint8Array;
116
116
  };
117
+ /**
118
+ * A file received INLINE over the message channel — the way iOS/C Carrier
119
+ * apps send images/audio online: a FileModel JSON envelope
120
+ * ({data: base64, fileExtension, fileName, type}) carried as a
121
+ * (bulk)message. Distinct from the toxcore file-transfer path (sendFile),
122
+ * which native Carrier clients cannot see.
123
+ */
124
+ export type InlineFileEvent = {
125
+ pubkey: string;
126
+ /** File name including extension. */
127
+ name: string;
128
+ /** The sender's declared type ("image" | "audio" | "text" | …), if any. */
129
+ fileType?: string;
130
+ data: Uint8Array;
131
+ via: "online" | "offline";
132
+ };
117
133
  export type LookupResult = {
118
134
  pubkey: string;
119
135
  status: "unknown";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/peer",
3
- "version": "0.1.81",
3
+ "version": "0.1.83",
4
4
  "description": "Pure TypeScript port of Elastos Carrier (toxcore-derived) P2P messaging. DHT, onion routing, TCP relay, FlatBuffers app payloads, Express offline relay. Wire-compatible with iOS Beagle and the Carrier C SDK.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",