@decentnetwork/peer 0.1.82 → 0.1.84
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/compat/packet.d.ts +69 -1
- package/dist/compat/packet.js +112 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/peer.d.ts +29 -1
- package/dist/peer.js +155 -2
- package/dist/types/peer.d.ts +29 -0
- package/package.json +2 -1
package/dist/compat/packet.d.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
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_INVITE_REQUEST = 34;
|
|
5
|
+
export declare const PACKET_TYPE_INVITE_RESPONSE = 35;
|
|
4
6
|
export declare const PACKET_TYPE_BULKMSG = 36;
|
|
5
7
|
export declare const CARRIER_MAX_APP_MESSAGE_LEN = 1024;
|
|
6
8
|
export declare const CARRIER_MAX_APP_BULKMSG_LEN: number;
|
|
9
|
+
export declare const INVITE_DATA_UNIT = 1280;
|
|
10
|
+
export declare const CARRIER_MAX_INVITE_DATA_LEN = 8192;
|
|
11
|
+
export declare const CARRIER_MAX_BUNDLE_LEN = 511;
|
|
12
|
+
export declare const CARRIER_MAX_EXTENSION_NAME_LEN = 31;
|
|
13
|
+
export declare const CARRIER_EXTENSION_NAME = "carrier";
|
|
7
14
|
export type UserInfoPacket = {
|
|
8
15
|
type: typeof PACKET_TYPE_USERINFO;
|
|
9
16
|
avatar: boolean;
|
|
@@ -34,7 +41,39 @@ export type BulkMsgPacket = {
|
|
|
34
41
|
tid: bigint;
|
|
35
42
|
data: Uint8Array;
|
|
36
43
|
};
|
|
37
|
-
|
|
44
|
+
/**
|
|
45
|
+
* One `invitereq` fragment (PACKET_TYPE_INVITE_REQUEST = 34). Field layout per
|
|
46
|
+
* carrier/packet.fbs: 0=ext string, 1=tid long, 2=bundle string, 3=totalsz
|
|
47
|
+
* uint, 4=data [uint8]. Mirrors carrier_invite_friend's per-fragment packet.
|
|
48
|
+
*/
|
|
49
|
+
export type InviteReqPacket = {
|
|
50
|
+
type: typeof PACKET_TYPE_INVITE_REQUEST;
|
|
51
|
+
ext?: string;
|
|
52
|
+
tid: bigint;
|
|
53
|
+
bundle?: string;
|
|
54
|
+
/** Full data length — set on the FIRST fragment only, 0 on the rest. */
|
|
55
|
+
totalsz: number;
|
|
56
|
+
data: Uint8Array;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* One `invitersp` fragment (PACKET_TYPE_INVITE_RESPONSE = 35). Field layout:
|
|
60
|
+
* 0=ext, 1=tid, 2=bundle, 3=totalsz, 4=status int, 5=reason string, 6=data.
|
|
61
|
+
* A non-zero status means "refused" (reason set, no data); status 0 means
|
|
62
|
+
* "confirmed" and carries data. The iOS WebRTC receiver never sends these
|
|
63
|
+
* (each RtcSignal is a one-way invitereq), but we decode them for completeness
|
|
64
|
+
* and can emit them if a peer does reply.
|
|
65
|
+
*/
|
|
66
|
+
export type InviteRspPacket = {
|
|
67
|
+
type: typeof PACKET_TYPE_INVITE_RESPONSE;
|
|
68
|
+
ext?: string;
|
|
69
|
+
tid: bigint;
|
|
70
|
+
bundle?: string;
|
|
71
|
+
totalsz: number;
|
|
72
|
+
status: number;
|
|
73
|
+
reason?: string;
|
|
74
|
+
data: Uint8Array;
|
|
75
|
+
};
|
|
76
|
+
export type CarrierPacket = UserInfoPacket | FriendRequestPacket | FriendMessagePacket | InviteReqPacket | InviteRspPacket | BulkMsgPacket;
|
|
38
77
|
/**
|
|
39
78
|
* Encode a Carrier `userinfo` packet (FB type 3). The Carrier C SDK sends
|
|
40
79
|
* this every time the local user's name / status message / profile fields
|
|
@@ -75,4 +114,33 @@ export declare function encodeBulkMsgPacket(opts: {
|
|
|
75
114
|
tid: bigint;
|
|
76
115
|
data: Uint8Array;
|
|
77
116
|
}): Uint8Array;
|
|
117
|
+
/**
|
|
118
|
+
* Encode ONE `invitereq` fragment (PACKET_TYPE_INVITE_REQUEST = 34). Field
|
|
119
|
+
* layout per packet.fbs invitereq: 0=ext, 1=tid, 2=bundle, 3=totalsz, 4=data.
|
|
120
|
+
* Callers fragment the payload (INVITE_DATA_UNIT) and share one tid; only the
|
|
121
|
+
* first fragment sets totalsz (and the optional bundle). Mirrors the do/while
|
|
122
|
+
* in carrier_invite_friend.
|
|
123
|
+
*/
|
|
124
|
+
export declare function encodeInviteReqPacket(opts: {
|
|
125
|
+
ext?: string;
|
|
126
|
+
tid: bigint;
|
|
127
|
+
bundle?: string;
|
|
128
|
+
totalsz: number;
|
|
129
|
+
data: Uint8Array;
|
|
130
|
+
}): Uint8Array;
|
|
131
|
+
/**
|
|
132
|
+
* Encode ONE `invitersp` fragment (PACKET_TYPE_INVITE_RESPONSE = 35). Field
|
|
133
|
+
* layout: 0=ext, 1=tid, 2=bundle, 3=totalsz, 4=status, 5=reason, 6=data.
|
|
134
|
+
* status !== 0 → refused (reason set, no data); status === 0 → confirmed
|
|
135
|
+
* (carries data). Mirrors carrier_reply_friend_invite.
|
|
136
|
+
*/
|
|
137
|
+
export declare function encodeInviteRspPacket(opts: {
|
|
138
|
+
ext?: string;
|
|
139
|
+
tid: bigint;
|
|
140
|
+
bundle?: string;
|
|
141
|
+
totalsz: number;
|
|
142
|
+
status: number;
|
|
143
|
+
reason?: string;
|
|
144
|
+
data?: Uint8Array;
|
|
145
|
+
}): Uint8Array;
|
|
78
146
|
export declare function decodeCarrierPacket(bytes: Uint8Array): CarrierPacket;
|
package/dist/compat/packet.js
CHANGED
|
@@ -2,6 +2,8 @@ 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_INVITE_REQUEST = 34;
|
|
6
|
+
export const PACKET_TYPE_INVITE_RESPONSE = 35;
|
|
5
7
|
export const PACKET_TYPE_BULKMSG = 36;
|
|
6
8
|
// Carrier C SDK message-size constants (carrier.h). A single friendmsg may
|
|
7
9
|
// carry at most 1024 bytes; anything larger is split into BULKMSG fragments
|
|
@@ -9,12 +11,29 @@ export const PACKET_TYPE_BULKMSG = 36;
|
|
|
9
11
|
// only), reassembled in arrival order by the receiver.
|
|
10
12
|
export const CARRIER_MAX_APP_MESSAGE_LEN = 1024;
|
|
11
13
|
export const CARRIER_MAX_APP_BULKMSG_LEN = 5 * 1024 * 1024;
|
|
14
|
+
// Carrier friend-invite constants (carrier.c / carrier.h). An invite carries
|
|
15
|
+
// up to 8192 bytes of application data, fragmented into INVITE_DATA_UNIT-byte
|
|
16
|
+
// chunks sharing one tid — the first fragment carries totalsz (and the
|
|
17
|
+
// optional bundle), the rest carry totalsz=0. This is exactly the transport
|
|
18
|
+
// the iOS/Android WebRTC SDK uses for call signaling (RtcSignal JSON), sent
|
|
19
|
+
// via the "carrier" extension (CarrierExtension.sendInviteFriendRequest →
|
|
20
|
+
// carrier_invite_friend(to="<userid>:carrier", bundle=NULL, ...)).
|
|
21
|
+
export const INVITE_DATA_UNIT = 1280;
|
|
22
|
+
export const CARRIER_MAX_INVITE_DATA_LEN = 8192;
|
|
23
|
+
export const CARRIER_MAX_BUNDLE_LEN = 511;
|
|
24
|
+
export const CARRIER_MAX_EXTENSION_NAME_LEN = 31;
|
|
25
|
+
// The extension name the native SDK registers/sends under; the `ext` field of
|
|
26
|
+
// every invitereq/invitersp on the wire. WebRTC signaling MUST use this so the
|
|
27
|
+
// remote's registerExtension handler fires.
|
|
28
|
+
export const CARRIER_EXTENSION_NAME = "carrier";
|
|
12
29
|
// Body type indices into FlatBuffers `anybody` union (carrier/packet.fbs).
|
|
13
30
|
// The .fbs declares: userinfo, friendreq, friendmsg, invitereq, invitersp,
|
|
14
31
|
// bulkmsg — flatbuffers maps those to 1, 2, 3, 4, 5, 6 (NONE = 0).
|
|
15
32
|
const ANYBODY_USERINFO = 1;
|
|
16
33
|
const ANYBODY_FRIENDREQ = 2;
|
|
17
34
|
const ANYBODY_FRIENDMSG = 3;
|
|
35
|
+
const ANYBODY_INVITEREQ = 4;
|
|
36
|
+
const ANYBODY_INVITERSP = 5;
|
|
18
37
|
const ANYBODY_BULKMSG = 6;
|
|
19
38
|
/**
|
|
20
39
|
* Encode a Carrier `userinfo` packet (FB type 3). The Carrier C SDK sends
|
|
@@ -100,6 +119,73 @@ export function encodeBulkMsgPacket(opts) {
|
|
|
100
119
|
const bulk = builder.endObject();
|
|
101
120
|
return finishCarrierPacket(builder, PACKET_TYPE_BULKMSG, ANYBODY_BULKMSG, bulk);
|
|
102
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Encode ONE `invitereq` fragment (PACKET_TYPE_INVITE_REQUEST = 34). Field
|
|
124
|
+
* layout per packet.fbs invitereq: 0=ext, 1=tid, 2=bundle, 3=totalsz, 4=data.
|
|
125
|
+
* Callers fragment the payload (INVITE_DATA_UNIT) and share one tid; only the
|
|
126
|
+
* first fragment sets totalsz (and the optional bundle). Mirrors the do/while
|
|
127
|
+
* in carrier_invite_friend.
|
|
128
|
+
*/
|
|
129
|
+
export function encodeInviteReqPacket(opts) {
|
|
130
|
+
const builder = new Builder(opts.data.length + 128);
|
|
131
|
+
const dataOffset = builder.createByteVector(opts.data);
|
|
132
|
+
const bundleOffset = opts.bundle ? builder.createString(opts.bundle) : 0;
|
|
133
|
+
const extOffset = opts.ext ? builder.createString(opts.ext) : 0;
|
|
134
|
+
builder.startObject(5);
|
|
135
|
+
builder.addFieldOffset(4, dataOffset, 0);
|
|
136
|
+
if (opts.totalsz) {
|
|
137
|
+
builder.addFieldInt32(3, opts.totalsz, 0);
|
|
138
|
+
}
|
|
139
|
+
if (bundleOffset) {
|
|
140
|
+
builder.addFieldOffset(2, bundleOffset, 0);
|
|
141
|
+
}
|
|
142
|
+
if (opts.tid !== 0n) {
|
|
143
|
+
builder.addFieldInt64(1, opts.tid, BigInt(0));
|
|
144
|
+
}
|
|
145
|
+
if (extOffset) {
|
|
146
|
+
builder.addFieldOffset(0, extOffset, 0);
|
|
147
|
+
}
|
|
148
|
+
const ireq = builder.endObject();
|
|
149
|
+
return finishCarrierPacket(builder, PACKET_TYPE_INVITE_REQUEST, ANYBODY_INVITEREQ, ireq);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Encode ONE `invitersp` fragment (PACKET_TYPE_INVITE_RESPONSE = 35). Field
|
|
153
|
+
* layout: 0=ext, 1=tid, 2=bundle, 3=totalsz, 4=status, 5=reason, 6=data.
|
|
154
|
+
* status !== 0 → refused (reason set, no data); status === 0 → confirmed
|
|
155
|
+
* (carries data). Mirrors carrier_reply_friend_invite.
|
|
156
|
+
*/
|
|
157
|
+
export function encodeInviteRspPacket(opts) {
|
|
158
|
+
const payload = opts.data ?? new Uint8Array();
|
|
159
|
+
const builder = new Builder(payload.length + 128);
|
|
160
|
+
const dataOffset = payload.length ? builder.createByteVector(payload) : 0;
|
|
161
|
+
const reasonOffset = opts.reason ? builder.createString(opts.reason) : 0;
|
|
162
|
+
const bundleOffset = opts.bundle ? builder.createString(opts.bundle) : 0;
|
|
163
|
+
const extOffset = opts.ext ? builder.createString(opts.ext) : 0;
|
|
164
|
+
builder.startObject(7);
|
|
165
|
+
if (dataOffset) {
|
|
166
|
+
builder.addFieldOffset(6, dataOffset, 0);
|
|
167
|
+
}
|
|
168
|
+
if (reasonOffset) {
|
|
169
|
+
builder.addFieldOffset(5, reasonOffset, 0);
|
|
170
|
+
}
|
|
171
|
+
if (opts.status) {
|
|
172
|
+
builder.addFieldInt32(4, opts.status, 0);
|
|
173
|
+
}
|
|
174
|
+
if (opts.totalsz) {
|
|
175
|
+
builder.addFieldInt32(3, opts.totalsz, 0);
|
|
176
|
+
}
|
|
177
|
+
if (bundleOffset) {
|
|
178
|
+
builder.addFieldOffset(2, bundleOffset, 0);
|
|
179
|
+
}
|
|
180
|
+
if (opts.tid !== 0n) {
|
|
181
|
+
builder.addFieldInt64(1, opts.tid, BigInt(0));
|
|
182
|
+
}
|
|
183
|
+
if (extOffset) {
|
|
184
|
+
builder.addFieldOffset(0, extOffset, 0);
|
|
185
|
+
}
|
|
186
|
+
const irsp = builder.endObject();
|
|
187
|
+
return finishCarrierPacket(builder, PACKET_TYPE_INVITE_RESPONSE, ANYBODY_INVITERSP, irsp);
|
|
188
|
+
}
|
|
103
189
|
export function decodeCarrierPacket(bytes) {
|
|
104
190
|
const bb = new ByteBuffer(bytes);
|
|
105
191
|
const root = bb.readInt32(bb.position()) + bb.position();
|
|
@@ -146,6 +232,28 @@ export function decodeCarrierPacket(bytes) {
|
|
|
146
232
|
data: readByteVectorField(body, 3) ?? new Uint8Array()
|
|
147
233
|
};
|
|
148
234
|
}
|
|
235
|
+
if (type === PACKET_TYPE_INVITE_REQUEST && bodyType === ANYBODY_INVITEREQ) {
|
|
236
|
+
return {
|
|
237
|
+
type,
|
|
238
|
+
ext: readStringField(body, 0) ?? undefined,
|
|
239
|
+
tid: readInt64Field(body, 1),
|
|
240
|
+
bundle: readStringField(body, 2) ?? undefined,
|
|
241
|
+
totalsz: readUint32Field(body, 3),
|
|
242
|
+
data: readByteVectorField(body, 4) ?? new Uint8Array()
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
if (type === PACKET_TYPE_INVITE_RESPONSE && bodyType === ANYBODY_INVITERSP) {
|
|
246
|
+
return {
|
|
247
|
+
type,
|
|
248
|
+
ext: readStringField(body, 0) ?? undefined,
|
|
249
|
+
tid: readInt64Field(body, 1),
|
|
250
|
+
bundle: readStringField(body, 2) ?? undefined,
|
|
251
|
+
totalsz: readUint32Field(body, 3),
|
|
252
|
+
status: readInt32Field(body, 4),
|
|
253
|
+
reason: readStringField(body, 5) ?? undefined,
|
|
254
|
+
data: readByteVectorField(body, 6) ?? new Uint8Array()
|
|
255
|
+
};
|
|
256
|
+
}
|
|
149
257
|
throw new Error(`unsupported carrier packet type/body: ${type}/${bodyType}`);
|
|
150
258
|
}
|
|
151
259
|
function finishCarrierPacket(builder, type, bodyType, bodyOffset) {
|
|
@@ -169,6 +277,10 @@ function readUint32Field(table, field) {
|
|
|
169
277
|
const offset = table.bb.__offset(table.bb_pos, 4 + field * 2);
|
|
170
278
|
return offset ? table.bb.readUint32(table.bb_pos + offset) : 0;
|
|
171
279
|
}
|
|
280
|
+
function readInt32Field(table, field) {
|
|
281
|
+
const offset = table.bb.__offset(table.bb_pos, 4 + field * 2);
|
|
282
|
+
return offset ? table.bb.readInt32(table.bb_pos + offset) : 0;
|
|
283
|
+
}
|
|
172
284
|
function readInt64Field(table, field) {
|
|
173
285
|
const offset = table.bb.__offset(table.bb_pos, 4 + field * 2);
|
|
174
286
|
return offset ? table.bb.readInt64(table.bb_pos + offset) : BigInt(0);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export { Peer } from "./peer.js";
|
|
2
2
|
export { CARRIER_ADDRESS_SIZE, CARRIER_PUBLIC_KEY_SIZE, carrierAddressFromPublicKey, carrierIdFromAddress, carrierIdFromPublicKey, parseCarrierAddress } from "./compat/address.js";
|
|
3
|
-
export { PACKET_TYPE_FRIEND_REQUEST, PACKET_TYPE_MESSAGE, decodeCarrierPacket, encodeFriendMessagePacket, encodeFriendRequestPacket } from "./compat/packet.js";
|
|
3
|
+
export { PACKET_TYPE_FRIEND_REQUEST, PACKET_TYPE_MESSAGE, PACKET_TYPE_INVITE_REQUEST, PACKET_TYPE_INVITE_RESPONSE, INVITE_DATA_UNIT, CARRIER_MAX_INVITE_DATA_LEN, CARRIER_EXTENSION_NAME, decodeCarrierPacket, encodeFriendMessagePacket, encodeFriendRequestPacket, encodeInviteReqPacket, encodeInviteRspPacket } from "./compat/packet.js";
|
|
4
4
|
export { CRYPTO_PACKET_DHTPK, CRYPTO_PACKET_FRIEND_REQ, CRYPTO_PACKET_NAT_PING, NET_PACKET_CRYPTO, createToxDhtCryptoRequest, openToxDhtCryptoRequest } from "./compat/tox-dht-crypto.js";
|
|
5
5
|
export { NET_PACKET_ONION_ANNOUNCE_REQUEST, NET_PACKET_ONION_ANNOUNCE_RESPONSE, NET_PACKET_ONION_DATA_REQUEST, NET_PACKET_ONION_DATA_RESPONSE, ONION_FRIEND_REQUEST_ID } from "./compat/tox-onion.js";
|
|
6
6
|
export { signDetached, verifyDetached, SIGNATURE_LENGTH } from "./crypto/sign.js";
|
|
7
7
|
export { base58ToBytes, bytesToBase58 } from "./utils/base58.js";
|
|
8
8
|
export { LegacyProtocolNotImplementedError } from "./runtime/errors.js";
|
|
9
|
-
export type { CarrierPacket, FriendMessagePacket, FriendRequestPacket } from "./compat/packet.js";
|
|
9
|
+
export type { CarrierPacket, FriendMessagePacket, FriendRequestPacket, InviteReqPacket, InviteRspPacket } from "./compat/packet.js";
|
|
10
10
|
export type { ToxDhtCryptoRequest } from "./compat/tox-dht-crypto.js";
|
|
11
11
|
export type { CarrierAddressParts } from "./compat/address.js";
|
|
12
|
-
export type { CompatibilityMode, CustomPacketEvent, FriendConnectionEvent, FriendConnectionStatus, FriendInfoEvent, FriendRequest, LookupResult, NetworkNode, PeerOptions, TextMessage } from "./types/peer.js";
|
|
12
|
+
export type { CompatibilityMode, CustomPacketEvent, FriendConnectionEvent, FriendConnectionStatus, FriendInfoEvent, FriendRequest, InlineFileEvent, InviteEvent, InviteResponseEvent, LookupResult, NetworkNode, PeerOptions, TextMessage } from "./types/peer.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { Peer } from "./peer.js";
|
|
2
2
|
export { CARRIER_ADDRESS_SIZE, CARRIER_PUBLIC_KEY_SIZE, carrierAddressFromPublicKey, carrierIdFromAddress, carrierIdFromPublicKey, parseCarrierAddress } from "./compat/address.js";
|
|
3
|
-
export { PACKET_TYPE_FRIEND_REQUEST, PACKET_TYPE_MESSAGE, decodeCarrierPacket, encodeFriendMessagePacket, encodeFriendRequestPacket } from "./compat/packet.js";
|
|
3
|
+
export { PACKET_TYPE_FRIEND_REQUEST, PACKET_TYPE_MESSAGE, PACKET_TYPE_INVITE_REQUEST, PACKET_TYPE_INVITE_RESPONSE, INVITE_DATA_UNIT, CARRIER_MAX_INVITE_DATA_LEN, CARRIER_EXTENSION_NAME, decodeCarrierPacket, encodeFriendMessagePacket, encodeFriendRequestPacket, encodeInviteReqPacket, encodeInviteRspPacket } from "./compat/packet.js";
|
|
4
4
|
export { CRYPTO_PACKET_DHTPK, CRYPTO_PACKET_FRIEND_REQ, CRYPTO_PACKET_NAT_PING, NET_PACKET_CRYPTO, createToxDhtCryptoRequest, openToxDhtCryptoRequest } from "./compat/tox-dht-crypto.js";
|
|
5
5
|
export { NET_PACKET_ONION_ANNOUNCE_REQUEST, NET_PACKET_ONION_ANNOUNCE_RESPONSE, NET_PACKET_ONION_DATA_REQUEST, NET_PACKET_ONION_DATA_RESPONSE, ONION_FRIEND_REQUEST_ID } from "./compat/tox-onion.js";
|
|
6
6
|
export { signDetached, verifyDetached, SIGNATURE_LENGTH } from "./crypto/sign.js";
|
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, InlineFileEvent, LookupResult, NetworkNode, PeerOptions, TextMessage } from "./types/peer.js";
|
|
3
|
+
import type { CustomPacketEvent, FriendConnectionEvent, FriendRequest, FriendInfoEvent, InlineFileEvent, InviteEvent, InviteResponseEvent, LookupResult, NetworkNode, PeerOptions, TextMessage } from "./types/peer.js";
|
|
4
4
|
export declare class Peer {
|
|
5
5
|
#private;
|
|
6
6
|
private constructor();
|
|
@@ -99,6 +99,34 @@ export declare class Peer {
|
|
|
99
99
|
data: Uint8Array;
|
|
100
100
|
fileType?: "image" | "audio" | "text" | "unknown";
|
|
101
101
|
}): Promise<void>;
|
|
102
|
+
/** Fired when a peer sends a Carrier friend-invite (PACKET_TYPE_INVITE_REQUEST).
|
|
103
|
+
* This is the channel the iOS/Android WebRTC SDK uses for call signaling —
|
|
104
|
+
* each RtcSignal arrives as an "invite" with ext="carrier" and `data` the
|
|
105
|
+
* reassembled JSON. */
|
|
106
|
+
onInvite(cb: (evt: InviteEvent) => void): void;
|
|
107
|
+
/** Fired when a peer replies to one of our invites (PACKET_TYPE_INVITE_RESPONSE).
|
|
108
|
+
* Not used by the one-way WebRTC signaling flow, but surfaced for peers that
|
|
109
|
+
* do reply. */
|
|
110
|
+
onInviteResponse(cb: (evt: InviteResponseEvent) => void): void;
|
|
111
|
+
/**
|
|
112
|
+
* Send a Carrier friend-invite to a peer — the transport the iOS/Android
|
|
113
|
+
* Elastos WebRTC SDK listens on (CarrierExtension.registerExtension). Used
|
|
114
|
+
* for realtime call signaling: pass the RtcSignal JSON as `data` and the
|
|
115
|
+
* remote's registerExtension handler fires with it.
|
|
116
|
+
*
|
|
117
|
+
* Wire-compatible with carrier_invite_friend: sends PACKET_TYPE_INVITE_REQUEST
|
|
118
|
+
* packets over the reliable message channel, `ext` = "carrier" by default,
|
|
119
|
+
* fragmented into INVITE_DATA_UNIT (1280-byte) chunks sharing one tid (first
|
|
120
|
+
* fragment carries totalsz + optional bundle). Signaling is realtime, so this
|
|
121
|
+
* requires a live session and does NOT fall back to offline express — a call
|
|
122
|
+
* signal to an unreachable peer should fail fast, not queue.
|
|
123
|
+
*
|
|
124
|
+
* @returns the tid used (lets callers correlate a later invite-response).
|
|
125
|
+
*/
|
|
126
|
+
sendInvite(pubkey: string, data: Uint8Array | string, opts?: {
|
|
127
|
+
ext?: string | null;
|
|
128
|
+
bundle?: string;
|
|
129
|
+
}): Promise<bigint>;
|
|
102
130
|
/**
|
|
103
131
|
* Send an application-defined custom packet to a friend. `id` selects the
|
|
104
132
|
* 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, PACKET_TYPE_BULKMSG, CARRIER_MAX_APP_MESSAGE_LEN, CARRIER_MAX_APP_BULKMSG_LEN, encodeUserInfoPacket, decodeCarrierPacket, encodeFriendMessagePacket, encodeFriendRequestPacket, encodeBulkMsgPacket } from "./compat/packet.js";
|
|
8
|
+
import { PACKET_TYPE_MESSAGE, PACKET_TYPE_FRIEND_REQUEST, PACKET_TYPE_USERINFO, PACKET_TYPE_BULKMSG, PACKET_TYPE_INVITE_REQUEST, PACKET_TYPE_INVITE_RESPONSE, CARRIER_MAX_APP_MESSAGE_LEN, CARRIER_MAX_APP_BULKMSG_LEN, INVITE_DATA_UNIT, CARRIER_MAX_INVITE_DATA_LEN, CARRIER_EXTENSION_NAME, encodeUserInfoPacket, decodeCarrierPacket, encodeFriendMessagePacket, encodeFriendRequestPacket, encodeBulkMsgPacket, encodeInviteReqPacket } 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";
|
|
@@ -311,6 +311,10 @@ export class Peer {
|
|
|
311
311
|
// them ordered) and the message completes when totalsz bytes arrived.
|
|
312
312
|
// Mirrors the C SDK's bulkmsgs hashtable with its 60s assembly timeout.
|
|
313
313
|
#bulkAssembly = new Map();
|
|
314
|
+
// Reassembly buffer for multi-fragment Carrier invites (WebRTC signaling).
|
|
315
|
+
// Keyed by `${friendId}:${tid}`; namespaced separately from #bulkAssembly so
|
|
316
|
+
// a bulkmsg and an invite that happen to share a tid can't collide.
|
|
317
|
+
#inviteAssembly = new Map();
|
|
314
318
|
// When we FIRST deferred initiation to a higher-pubkey peer (per friend).
|
|
315
319
|
// If the peer hasn't established within the grace window we break the
|
|
316
320
|
// defer and initiate ourselves (see #initiateSession). Cleared whenever a
|
|
@@ -1125,6 +1129,81 @@ export class Peer {
|
|
|
1125
1129
|
}
|
|
1126
1130
|
await this.sendText(pubkey, envelope);
|
|
1127
1131
|
}
|
|
1132
|
+
/** Fired when a peer sends a Carrier friend-invite (PACKET_TYPE_INVITE_REQUEST).
|
|
1133
|
+
* This is the channel the iOS/Android WebRTC SDK uses for call signaling —
|
|
1134
|
+
* each RtcSignal arrives as an "invite" with ext="carrier" and `data` the
|
|
1135
|
+
* reassembled JSON. */
|
|
1136
|
+
onInvite(cb) {
|
|
1137
|
+
this.#events.on("invite", cb);
|
|
1138
|
+
}
|
|
1139
|
+
/** Fired when a peer replies to one of our invites (PACKET_TYPE_INVITE_RESPONSE).
|
|
1140
|
+
* Not used by the one-way WebRTC signaling flow, but surfaced for peers that
|
|
1141
|
+
* do reply. */
|
|
1142
|
+
onInviteResponse(cb) {
|
|
1143
|
+
this.#events.on("inviteResponse", cb);
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Send a Carrier friend-invite to a peer — the transport the iOS/Android
|
|
1147
|
+
* Elastos WebRTC SDK listens on (CarrierExtension.registerExtension). Used
|
|
1148
|
+
* for realtime call signaling: pass the RtcSignal JSON as `data` and the
|
|
1149
|
+
* remote's registerExtension handler fires with it.
|
|
1150
|
+
*
|
|
1151
|
+
* Wire-compatible with carrier_invite_friend: sends PACKET_TYPE_INVITE_REQUEST
|
|
1152
|
+
* packets over the reliable message channel, `ext` = "carrier" by default,
|
|
1153
|
+
* fragmented into INVITE_DATA_UNIT (1280-byte) chunks sharing one tid (first
|
|
1154
|
+
* fragment carries totalsz + optional bundle). Signaling is realtime, so this
|
|
1155
|
+
* requires a live session and does NOT fall back to offline express — a call
|
|
1156
|
+
* signal to an unreachable peer should fail fast, not queue.
|
|
1157
|
+
*
|
|
1158
|
+
* @returns the tid used (lets callers correlate a later invite-response).
|
|
1159
|
+
*/
|
|
1160
|
+
async sendInvite(pubkey, data, opts = {}) {
|
|
1161
|
+
const friend = this.#friends.get(pubkey);
|
|
1162
|
+
if (!friend) {
|
|
1163
|
+
throw new Error(`Not a friend: ${pubkey}`);
|
|
1164
|
+
}
|
|
1165
|
+
const payload = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
1166
|
+
if (payload.length === 0 || payload.length > CARRIER_MAX_INVITE_DATA_LEN) {
|
|
1167
|
+
throw new Error(`invite data must be 1..${CARRIER_MAX_INVITE_DATA_LEN} bytes (got ${payload.length})`);
|
|
1168
|
+
}
|
|
1169
|
+
// `ext: null` sends a plain invite (no extension); default is the "carrier"
|
|
1170
|
+
// extension the native WebRTC SDK registers under.
|
|
1171
|
+
const ext = opts.ext === null ? undefined : (opts.ext ?? CARRIER_EXTENSION_NAME);
|
|
1172
|
+
const bundle = opts.bundle;
|
|
1173
|
+
let session = this.#friendSessions.get(pubkey);
|
|
1174
|
+
if (!session?.established) {
|
|
1175
|
+
await this.#initiateSession(pubkey).catch(() => { });
|
|
1176
|
+
const established = await this.#waitForFriendConnected(pubkey, 5000).catch(() => false);
|
|
1177
|
+
if (established)
|
|
1178
|
+
session = this.#friendSessions.get(pubkey);
|
|
1179
|
+
}
|
|
1180
|
+
if (!session?.established || !session.sessionSharedKey || !session.ourBaseNonce || !(session.remote || session.hasTcpRoute)) {
|
|
1181
|
+
throw new Error(`cannot send invite: no live session to ${pubkey} (peer offline?)`);
|
|
1182
|
+
}
|
|
1183
|
+
// Fragment exactly like carrier_invite_friend: first fragment carries
|
|
1184
|
+
// totalsz and the optional bundle (which counts against the first unit),
|
|
1185
|
+
// the rest carry totalsz=0. bundle is usually absent for WebRTC.
|
|
1186
|
+
const tid = (BigInt(Date.now()) << 20n) ^ BigInt(Math.floor(Math.random() * 0xfffff)) | 1n;
|
|
1187
|
+
const bundleLen = bundle ? new TextEncoder().encode(bundle).length : 0;
|
|
1188
|
+
let off = 0;
|
|
1189
|
+
let first = true;
|
|
1190
|
+
while (off < payload.length) {
|
|
1191
|
+
const unit = first && bundle ? INVITE_DATA_UNIT - bundleLen : INVITE_DATA_UNIT;
|
|
1192
|
+
const end = Math.min(off + unit, payload.length);
|
|
1193
|
+
const p = encodeInviteReqPacket({
|
|
1194
|
+
ext,
|
|
1195
|
+
tid,
|
|
1196
|
+
bundle: first ? bundle : undefined,
|
|
1197
|
+
totalsz: first ? payload.length : 0,
|
|
1198
|
+
data: payload.subarray(off, end)
|
|
1199
|
+
});
|
|
1200
|
+
await this.#sendMessengerPacket(pubkey, PACKET_ID_MESSAGE, p);
|
|
1201
|
+
off = end;
|
|
1202
|
+
first = false;
|
|
1203
|
+
}
|
|
1204
|
+
this.#debugLog(`invite sent to ${pubkey}: ext="${ext ?? ""}" (${payload.length} bytes, tid=${tid})`);
|
|
1205
|
+
return tid;
|
|
1206
|
+
}
|
|
1128
1207
|
/**
|
|
1129
1208
|
* Send an application-defined custom packet to a friend. `id` selects the
|
|
1130
1209
|
* channel and its delivery class by toxcore range: **160–191 lossless**
|
|
@@ -2071,6 +2150,39 @@ export class Peer {
|
|
|
2071
2150
|
carrier = decodeCarrierPacket(inner);
|
|
2072
2151
|
}
|
|
2073
2152
|
catch { /* raw utf-8 peer */ }
|
|
2153
|
+
// Carrier friend-invite (PACKET_TYPE_INVITE_REQUEST/RESPONSE = 34/35)
|
|
2154
|
+
// rides this same PACKET_ID_MESSAGE channel — it's what the iOS/Android
|
|
2155
|
+
// WebRTC SDK uses for call signaling via the "carrier" extension. Large
|
|
2156
|
+
// signals (SDP offers) fragment by tid like bulkmsg; reassemble then
|
|
2157
|
+
// surface as an "invite" event (NOT chat text).
|
|
2158
|
+
if (carrier?.type === PACKET_TYPE_INVITE_REQUEST) {
|
|
2159
|
+
const complete = this.#assembleInvite(friendId, carrier);
|
|
2160
|
+
if (!complete)
|
|
2161
|
+
return; // more fragments pending
|
|
2162
|
+
this.#events.emit("invite", {
|
|
2163
|
+
pubkey: friendId,
|
|
2164
|
+
ext: carrier.ext,
|
|
2165
|
+
bundle: carrier.bundle,
|
|
2166
|
+
data: complete
|
|
2167
|
+
});
|
|
2168
|
+
this.#debugLog(`invite from ${friendId}: ext="${carrier.ext ?? ""}" (${complete.length} bytes)`);
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
if (carrier?.type === PACKET_TYPE_INVITE_RESPONSE) {
|
|
2172
|
+
const complete = carrier.status === 0 ? this.#assembleInvite(friendId, carrier) : new Uint8Array();
|
|
2173
|
+
if (carrier.status === 0 && !complete)
|
|
2174
|
+
return; // more fragments pending
|
|
2175
|
+
this.#events.emit("inviteResponse", {
|
|
2176
|
+
pubkey: friendId,
|
|
2177
|
+
ext: carrier.ext,
|
|
2178
|
+
bundle: carrier.bundle,
|
|
2179
|
+
status: carrier.status,
|
|
2180
|
+
reason: carrier.reason,
|
|
2181
|
+
data: complete ?? new Uint8Array()
|
|
2182
|
+
});
|
|
2183
|
+
this.#debugLog(`invite-response from ${friendId}: status=${carrier.status} ext="${carrier.ext ?? ""}"`);
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2074
2186
|
if (carrier?.type === PACKET_TYPE_BULKMSG) {
|
|
2075
2187
|
const complete = this.#assembleBulkMsg(friendId, carrier);
|
|
2076
2188
|
if (!complete)
|
|
@@ -2084,6 +2196,9 @@ export class Peer {
|
|
|
2084
2196
|
else {
|
|
2085
2197
|
text = decodeUtf8Best(inner);
|
|
2086
2198
|
}
|
|
2199
|
+
// Carrier C peers terminate strings with a NUL — strip it so chat
|
|
2200
|
+
// text doesn't carry an invisible trailing byte.
|
|
2201
|
+
text = text?.replace(/\0+$/u, "");
|
|
2087
2202
|
if (!text) {
|
|
2088
2203
|
this.#debugLog(`crypto message packet decode failed from ${friendId}`);
|
|
2089
2204
|
return;
|
|
@@ -3241,12 +3356,50 @@ export class Peer {
|
|
|
3241
3356
|
this.#bulkAssembly.delete(key);
|
|
3242
3357
|
return concatBytes(entry.chunks);
|
|
3243
3358
|
}
|
|
3359
|
+
/** Append a Carrier invite fragment (INVITE_REQUEST/RESPONSE); returns the
|
|
3360
|
+
* full reassembled payload when the last fragment lands, undefined while
|
|
3361
|
+
* pending. Mirrors #assembleBulkMsg but caps at CARRIER_MAX_INVITE_DATA_LEN
|
|
3362
|
+
* (8192) and uses a separate buffer. Single-fragment invites (the common
|
|
3363
|
+
* case for small WebRTC signals) complete on the first call. */
|
|
3364
|
+
#assembleInvite(friendId, frag) {
|
|
3365
|
+
const now = Date.now();
|
|
3366
|
+
for (const [key, entry] of this.#inviteAssembly) {
|
|
3367
|
+
if (entry.expireAtMs < now)
|
|
3368
|
+
this.#inviteAssembly.delete(key);
|
|
3369
|
+
}
|
|
3370
|
+
const key = `${friendId}:${frag.tid.toString()}`;
|
|
3371
|
+
let entry = this.#inviteAssembly.get(key);
|
|
3372
|
+
if (!entry) {
|
|
3373
|
+
// First fragment carries totalsz. A lone fragment whose data already
|
|
3374
|
+
// equals totalsz completes immediately below.
|
|
3375
|
+
if (!frag.totalsz || frag.totalsz > CARRIER_MAX_INVITE_DATA_LEN) {
|
|
3376
|
+
this.#debugLog(`invite from ${friendId} with invalid/missing totalsz ${frag.totalsz} — dropped`);
|
|
3377
|
+
return undefined;
|
|
3378
|
+
}
|
|
3379
|
+
entry = { total: frag.totalsz, chunks: [], got: 0, expireAtMs: now + 60_000 };
|
|
3380
|
+
this.#inviteAssembly.set(key, entry);
|
|
3381
|
+
}
|
|
3382
|
+
if (frag.data.length === 0 || entry.got + frag.data.length > entry.total) {
|
|
3383
|
+
this.#debugLog(`invite fragment from ${friendId} overflows totalsz — dropped`);
|
|
3384
|
+
this.#inviteAssembly.delete(key);
|
|
3385
|
+
return undefined;
|
|
3386
|
+
}
|
|
3387
|
+
entry.chunks.push(frag.data);
|
|
3388
|
+
entry.got += frag.data.length;
|
|
3389
|
+
if (entry.got < entry.total)
|
|
3390
|
+
return undefined;
|
|
3391
|
+
this.#inviteAssembly.delete(key);
|
|
3392
|
+
return concatBytes(entry.chunks);
|
|
3393
|
+
}
|
|
3244
3394
|
/** iOS Beagle sends files inline as a JSON envelope over (bulk)messages:
|
|
3245
3395
|
* {"data":"<base64>","fileExtension":".jpg","fileName":"…","type":"image"}.
|
|
3246
3396
|
* Detect it, decode, and emit an inlineFile event instead of dumping
|
|
3247
3397
|
* base64 into the chat. Returns true when handled. */
|
|
3248
3398
|
#tryEmitInlineFile(friendId, text, via) {
|
|
3249
|
-
|
|
3399
|
+
// The Carrier C SDK sends messages with a trailing C-string NUL, which
|
|
3400
|
+
// JSON.parse rejects. iOS's own decoder strips NULs before parsing
|
|
3401
|
+
// (CodableUtil.decode: replacingOccurrences(of: "\0")) — mirror that.
|
|
3402
|
+
const trimmed = text.replaceAll("\0", "").trim();
|
|
3250
3403
|
if (!trimmed.startsWith("{") || !trimmed.includes("\"fileName\"") || !trimmed.includes("\"data\"")) {
|
|
3251
3404
|
return false;
|
|
3252
3405
|
}
|
package/dist/types/peer.d.ts
CHANGED
|
@@ -130,6 +130,35 @@ export type InlineFileEvent = {
|
|
|
130
130
|
data: Uint8Array;
|
|
131
131
|
via: "online" | "offline";
|
|
132
132
|
};
|
|
133
|
+
/**
|
|
134
|
+
* A Carrier friend-invite request received from a peer (PACKET_TYPE_INVITE_REQUEST).
|
|
135
|
+
* This is the transport the iOS/Android Elastos WebRTC SDK uses for call
|
|
136
|
+
* signaling: each RtcSignal (offer/answer/candidate/bye JSON) is sent as a
|
|
137
|
+
* one-way invite under the "carrier" extension. `data` is the reassembled
|
|
138
|
+
* application payload (JSON for WebRTC; NUL-terminated by native C senders).
|
|
139
|
+
*/
|
|
140
|
+
export type InviteEvent = {
|
|
141
|
+
pubkey: string;
|
|
142
|
+
/** Extension name — "carrier" for WebRTC/extension invites, undefined for a plain invite. */
|
|
143
|
+
ext?: string;
|
|
144
|
+
/** Optional bundle string set by the sender (usually absent). */
|
|
145
|
+
bundle?: string;
|
|
146
|
+
data: Uint8Array;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* A Carrier friend-invite response (PACKET_TYPE_INVITE_RESPONSE). The iOS
|
|
150
|
+
* WebRTC receiver never sends these (signaling is one-way invitereq both
|
|
151
|
+
* ways), but a peer that calls reply_friend_invite produces one. status 0 =
|
|
152
|
+
* confirmed (carries data); non-zero = refused (reason set, no data).
|
|
153
|
+
*/
|
|
154
|
+
export type InviteResponseEvent = {
|
|
155
|
+
pubkey: string;
|
|
156
|
+
ext?: string;
|
|
157
|
+
bundle?: string;
|
|
158
|
+
status: number;
|
|
159
|
+
reason?: string;
|
|
160
|
+
data: Uint8Array;
|
|
161
|
+
};
|
|
133
162
|
export type LookupResult = {
|
|
134
163
|
pubkey: string;
|
|
135
164
|
status: "unknown";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.84",
|
|
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",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"demo:js-to-js": "pnpm run build && node scripts/js-to-js-demo.mjs",
|
|
68
68
|
"demo:tox-dht-crypto": "pnpm run build && node scripts/tox-dht-crypto-demo.mjs",
|
|
69
69
|
"test:compat": "pnpm run build && node scripts/compat-selftest.mjs",
|
|
70
|
+
"test:invite": "pnpm run build && node scripts/invite-selftest.mjs",
|
|
70
71
|
"smoke:join": "node scripts/smoke-join.mjs",
|
|
71
72
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
72
73
|
"test:express": "pnpm run build && node scripts/express-scheme-selftest.mjs"
|