@decentnetwork/peer 0.1.141 → 0.1.143
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/peer.d.ts +2 -0
- package/dist/peer.js +73 -3
- package/dist/store/friends.d.ts +4 -0
- package/dist/types/peer.d.ts +24 -0
- package/package.json +1 -1
package/dist/peer.d.ts
CHANGED
|
@@ -335,11 +335,13 @@ export declare class Peer {
|
|
|
335
335
|
setUserInfo(info: {
|
|
336
336
|
name?: string;
|
|
337
337
|
description?: string;
|
|
338
|
+
punkId?: number | null;
|
|
338
339
|
}): void;
|
|
339
340
|
/** Our current display name + status-message description. */
|
|
340
341
|
userInfo(): {
|
|
341
342
|
name: string;
|
|
342
343
|
description: string;
|
|
344
|
+
punkId?: number;
|
|
343
345
|
};
|
|
344
346
|
/**
|
|
345
347
|
* Tell a friend our public UDP endpoint over the (already-working,
|
package/dist/peer.js
CHANGED
|
@@ -286,6 +286,19 @@ const RECV_REORDER_WINDOW = 8192;
|
|
|
286
286
|
// Minimum gap between PACKET_ID_REQUEST packets we send for our own receive gaps
|
|
287
287
|
// (toxcore paces requests ~ once per RTT; this floor avoids flooding on a burst).
|
|
288
288
|
const RECV_REQUEST_MIN_INTERVAL_MS = 200;
|
|
289
|
+
/** Read a CryptoPunks id out of the userinfo `gender` field.
|
|
290
|
+
*
|
|
291
|
+
* Beagle writes the bare nft id there. Anything else — an empty string, or a
|
|
292
|
+
* real gender from a non-Beagle Carrier client — is not a punk and must not
|
|
293
|
+
* be rendered as one. */
|
|
294
|
+
function parsePunkField(value) {
|
|
295
|
+
if (!value)
|
|
296
|
+
return undefined;
|
|
297
|
+
const n = Number(value.trim());
|
|
298
|
+
if (!Number.isInteger(n) || n < 0 || n > 9999)
|
|
299
|
+
return undefined;
|
|
300
|
+
return n;
|
|
301
|
+
}
|
|
289
302
|
export class Peer {
|
|
290
303
|
#opts;
|
|
291
304
|
#events = new EventEmitter();
|
|
@@ -885,6 +898,41 @@ export class Peer {
|
|
|
885
898
|
if (!this.#bootstrap) {
|
|
886
899
|
throw new Error("Peer is not started");
|
|
887
900
|
}
|
|
901
|
+
// A browser has no UDP and never will. The DHT bootstrap is a getnodes
|
|
902
|
+
// datagram waiting for a sendnodes reply, so there it can only ever time
|
|
903
|
+
// out — 30 seconds, then an error that reads like a network fault.
|
|
904
|
+
//
|
|
905
|
+
// That cost far more than a slow start. Everything after the await lives
|
|
906
|
+
// downstream of it: the self-announce (without which nobody can FIND this
|
|
907
|
+
// peer), the announce loop, and the express pull loop (without which
|
|
908
|
+
// offline messages are never collected). start() launches only the
|
|
909
|
+
// friend-connection loop, so a browser could talk to a friend who was
|
|
910
|
+
// already online and whose endpoint it had cached — and nothing else.
|
|
911
|
+
// That is exactly the half-working shape this looked like from outside.
|
|
912
|
+
//
|
|
913
|
+
// With tcpOnlyBootstrap we skip the datagram round trip and seed the node
|
|
914
|
+
// list from the configured bootstrap nodes, which is what the TCP relay
|
|
915
|
+
// path uses anyway. Announce and both loops then run normally.
|
|
916
|
+
if (this.#opts.tcpOnlyBootstrap) {
|
|
917
|
+
const seed = this.#opts.bootstrapNodes ?? [];
|
|
918
|
+
if (seed.length === 0) {
|
|
919
|
+
throw new Error("tcpOnlyBootstrap needs at least one bootstrap node to seed from");
|
|
920
|
+
}
|
|
921
|
+
this.#knownNodes = dedupeNodes([...seed]);
|
|
922
|
+
await this.#runSelfAnnounce(false, Date.now() + JOIN_ANNOUNCE_TIMEOUT_MS);
|
|
923
|
+
this.#ensureSelfAnnounceLoop();
|
|
924
|
+
this.#ensureDhtMaintenanceLoop();
|
|
925
|
+
this.#ensureFriendConnectionLoop();
|
|
926
|
+
this.#ensureExpressPullLoop();
|
|
927
|
+
void this.#doFriendConnections().catch((error) => {
|
|
928
|
+
this.#debugLog(`initial friend connection cycle failed: ${error.message}`);
|
|
929
|
+
});
|
|
930
|
+
const first = seed[0];
|
|
931
|
+
return {
|
|
932
|
+
respondingNode: first,
|
|
933
|
+
discoveredNodes: []
|
|
934
|
+
};
|
|
935
|
+
}
|
|
888
936
|
const result = await this.#bootstrap.join();
|
|
889
937
|
this.#recordNodeSuccess(`${result.respondingNode.host}:${result.respondingNode.port}`);
|
|
890
938
|
const discovered = result.discoveredNodes.map((node) => ({
|
|
@@ -4250,12 +4298,17 @@ export class Peer {
|
|
|
4250
4298
|
// back to plain UTF-8 for compatibility.
|
|
4251
4299
|
let userInfoName;
|
|
4252
4300
|
let userInfoDescr;
|
|
4301
|
+
let userInfoPunk;
|
|
4253
4302
|
let clientMeta;
|
|
4254
4303
|
try {
|
|
4255
4304
|
const decoded = decodeCarrierPacket(inner);
|
|
4256
4305
|
if (decoded.type === PACKET_TYPE_USERINFO) {
|
|
4257
4306
|
userInfoName = decoded.name;
|
|
4258
4307
|
userInfoDescr = decoded.descr;
|
|
4308
|
+
// The friend's avatar. `gender` is where Beagle puts the punk id —
|
|
4309
|
+
// it was decoded here and thrown away, which is the whole reason a
|
|
4310
|
+
// friend's picture could only ever come from beagles.eth.
|
|
4311
|
+
userInfoPunk = parsePunkField(decoded.gender);
|
|
4259
4312
|
// AgentNet appended fields — present only for updated peers.
|
|
4260
4313
|
if (decoded.protoVersion || decoded.platform || decoded.appVersion) {
|
|
4261
4314
|
clientMeta = {
|
|
@@ -4274,19 +4327,23 @@ export class Peer {
|
|
|
4274
4327
|
const friend = this.#friends.get(friendId);
|
|
4275
4328
|
const newName = userInfoName && userInfoName.length > 0 ? userInfoName : friend?.name;
|
|
4276
4329
|
const newDescr = userInfoDescr ?? friend?.description;
|
|
4330
|
+
// Keep what we had when this packet carried no punk: a peer that
|
|
4331
|
+
// stops sending one has not taken their avatar off.
|
|
4332
|
+
const newPunk = userInfoPunk ?? friend?.punkId;
|
|
4277
4333
|
const metaChanged = friend != null && clientMeta != null &&
|
|
4278
4334
|
(friend.protoVersion !== clientMeta.protoVersion ||
|
|
4279
4335
|
friend.platform !== clientMeta.platform ||
|
|
4280
4336
|
friend.osVersion !== clientMeta.osVersion ||
|
|
4281
4337
|
friend.appVersion !== clientMeta.appVersion);
|
|
4282
|
-
if (friend && (friend.name !== newName || friend.description !== newDescr || metaChanged)) {
|
|
4283
|
-
this.#friends.set(friendId, { ...friend, name: newName, description: newDescr, ...(clientMeta ?? {}) });
|
|
4338
|
+
if (friend && (friend.name !== newName || friend.description !== newDescr || friend.punkId !== newPunk || metaChanged)) {
|
|
4339
|
+
this.#friends.set(friendId, { ...friend, name: newName, description: newDescr, punkId: newPunk, ...(clientMeta ?? {}) });
|
|
4284
4340
|
this.#persistFriends();
|
|
4285
4341
|
this.#events.emit("friendInfo", {
|
|
4286
4342
|
pubkey: friendId,
|
|
4287
4343
|
userid: friend.userid ?? friendId,
|
|
4288
4344
|
name: newName,
|
|
4289
4345
|
description: newDescr,
|
|
4346
|
+
punkId: newPunk,
|
|
4290
4347
|
...(clientMeta ?? {})
|
|
4291
4348
|
});
|
|
4292
4349
|
}
|
|
@@ -4958,6 +5015,11 @@ export class Peer {
|
|
|
4958
5015
|
opts.nickname = info.name;
|
|
4959
5016
|
if (info.description !== undefined)
|
|
4960
5017
|
opts.statusMessage = info.description;
|
|
5018
|
+
// null clears it explicitly; undefined means "not changing it". Everything
|
|
5019
|
+
// below re-sends the profile to every established friend, which is what
|
|
5020
|
+
// makes an avatar change visible without a reconnect.
|
|
5021
|
+
if (info.punkId !== undefined)
|
|
5022
|
+
opts.punkId = info.punkId ?? undefined;
|
|
4961
5023
|
this.#profileSentTo.clear();
|
|
4962
5024
|
for (const timer of this.#profileRetryTimers.values())
|
|
4963
5025
|
clearTimeout(timer);
|
|
@@ -4974,6 +5036,9 @@ export class Peer {
|
|
|
4974
5036
|
return {
|
|
4975
5037
|
name: opts.nickname ?? PEER_NICKNAME,
|
|
4976
5038
|
description: opts.statusMessage ?? PEER_STATUS_MESSAGE,
|
|
5039
|
+
// Reported so "what am I actually advertising" is answerable without
|
|
5040
|
+
// reading a packet capture — the UI surfaces this as `me.advertised`.
|
|
5041
|
+
punkId: opts.punkId,
|
|
4977
5042
|
};
|
|
4978
5043
|
}
|
|
4979
5044
|
#scheduleProfileRetry(friendId) {
|
|
@@ -5030,7 +5095,12 @@ export class Peer {
|
|
|
5030
5095
|
protoVersion: AGENTNET_PROTO_VERSION,
|
|
5031
5096
|
platform: this.#opts.platform ?? process.platform,
|
|
5032
5097
|
osVersion: this.#opts.osVersion ?? `node-${process.versions?.node ?? ""}`,
|
|
5033
|
-
appVersion: this.#opts.appVersion ?? `peer-${PEER_PKG_VERSION}
|
|
5098
|
+
appVersion: this.#opts.appVersion ?? `peer-${PEER_PKG_VERSION}`,
|
|
5099
|
+
// The avatar, in the field Beagle uses for it (see punkId on
|
|
5100
|
+
// FriendInfoEvent). OMITTED when we have none, never sent as "":
|
|
5101
|
+
// an empty string would clear an avatar the friend already
|
|
5102
|
+
// resolved. Without this, iOS shows us no picture at all.
|
|
5103
|
+
...(this.#opts.punkId != null ? { gender: String(this.#opts.punkId) } : {})
|
|
5034
5104
|
});
|
|
5035
5105
|
await this.#sendMessengerPacket(friendId, PACKET_ID_STATUSMESSAGE, userInfo);
|
|
5036
5106
|
this.#debugLog(`userinfo sent to ${friendId} (name="${nick}", descr="${descr}", proto=${AGENTNET_PROTO_VERSION}, platform=${this.#opts.platform ?? process.platform})`);
|
package/dist/store/friends.d.ts
CHANGED
|
@@ -5,6 +5,10 @@ export type FriendRecord = {
|
|
|
5
5
|
nospam?: number;
|
|
6
6
|
name?: string;
|
|
7
7
|
description?: string;
|
|
8
|
+
/** The friend's CryptoPunks id, from the userinfo `gender` field. Persisted
|
|
9
|
+
* so their avatar survives a restart instead of waiting for the next
|
|
10
|
+
* profile packet. See FriendInfoEvent.punkId. */
|
|
11
|
+
punkId?: number;
|
|
8
12
|
status: "requested" | "offline" | "online";
|
|
9
13
|
remoteHost?: string;
|
|
10
14
|
remotePort?: number;
|
package/dist/types/peer.d.ts
CHANGED
|
@@ -88,6 +88,18 @@ export type PeerOptions = {
|
|
|
88
88
|
* version.
|
|
89
89
|
*/
|
|
90
90
|
appVersion?: string;
|
|
91
|
+
/** Our own CryptoPunks id, advertised to friends in the userinfo `gender`
|
|
92
|
+
* field — the only way an avatar travels over Carrier itself. Leave unset
|
|
93
|
+
* and the field is omitted entirely (never sent as ""), so a friend keeps
|
|
94
|
+
* whatever picture they already have for us. */
|
|
95
|
+
punkId?: number;
|
|
96
|
+
/** Skip the UDP DHT bootstrap and seed the node list from bootstrapNodes.
|
|
97
|
+
*
|
|
98
|
+
* For hosts with no UDP — a browser above all. Without it joinNetwork()
|
|
99
|
+
* waits 30s for a datagram that cannot arrive, and everything downstream
|
|
100
|
+
* of it never runs: self-announce (so nobody can find this peer) and the
|
|
101
|
+
* express pull loop (so offline messages are never collected). */
|
|
102
|
+
tcpOnlyBootstrap?: boolean;
|
|
91
103
|
compatibilityMode?: CompatibilityMode;
|
|
92
104
|
debugLabel?: string;
|
|
93
105
|
};
|
|
@@ -110,6 +122,18 @@ export type FriendInfoEvent = {
|
|
|
110
122
|
userid?: string;
|
|
111
123
|
name?: string;
|
|
112
124
|
description?: string;
|
|
125
|
+
/** The friend's CryptoPunks id — their avatar.
|
|
126
|
+
*
|
|
127
|
+
* Carried in the userinfo packet's `gender` field, which is not a mistake
|
|
128
|
+
* and not ours to rename. The protocol has no avatar field: `has_avatar`
|
|
129
|
+
* is a bool in all four implementations and the image exchange was never
|
|
130
|
+
* built. Beagle borrowed `gender` because a punk carries a gender anyway.
|
|
131
|
+
* Defined in WalletForBeagleV2/docs/profile.md — set with
|
|
132
|
+
* userinfo.setGender(nftid), read back off friendInfo.gender.
|
|
133
|
+
*
|
|
134
|
+
* Undefined when the friend sent no punk, or sent something that is not a
|
|
135
|
+
* punk id (a real gender from a non-Beagle Carrier client). */
|
|
136
|
+
punkId?: number;
|
|
113
137
|
/** Peer's advertised client metadata (from the userinfo profile). Present
|
|
114
138
|
* only once the friend has sent a profile with these fields — a legacy
|
|
115
139
|
* peer (native C SDK before the extension, or an old JS build) leaves them
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.143",
|
|
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",
|