@decentnetwork/peer 0.1.141 → 0.1.142

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 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();
@@ -4250,12 +4263,17 @@ export class Peer {
4250
4263
  // back to plain UTF-8 for compatibility.
4251
4264
  let userInfoName;
4252
4265
  let userInfoDescr;
4266
+ let userInfoPunk;
4253
4267
  let clientMeta;
4254
4268
  try {
4255
4269
  const decoded = decodeCarrierPacket(inner);
4256
4270
  if (decoded.type === PACKET_TYPE_USERINFO) {
4257
4271
  userInfoName = decoded.name;
4258
4272
  userInfoDescr = decoded.descr;
4273
+ // The friend's avatar. `gender` is where Beagle puts the punk id —
4274
+ // it was decoded here and thrown away, which is the whole reason a
4275
+ // friend's picture could only ever come from beagles.eth.
4276
+ userInfoPunk = parsePunkField(decoded.gender);
4259
4277
  // AgentNet appended fields — present only for updated peers.
4260
4278
  if (decoded.protoVersion || decoded.platform || decoded.appVersion) {
4261
4279
  clientMeta = {
@@ -4274,19 +4292,23 @@ export class Peer {
4274
4292
  const friend = this.#friends.get(friendId);
4275
4293
  const newName = userInfoName && userInfoName.length > 0 ? userInfoName : friend?.name;
4276
4294
  const newDescr = userInfoDescr ?? friend?.description;
4295
+ // Keep what we had when this packet carried no punk: a peer that
4296
+ // stops sending one has not taken their avatar off.
4297
+ const newPunk = userInfoPunk ?? friend?.punkId;
4277
4298
  const metaChanged = friend != null && clientMeta != null &&
4278
4299
  (friend.protoVersion !== clientMeta.protoVersion ||
4279
4300
  friend.platform !== clientMeta.platform ||
4280
4301
  friend.osVersion !== clientMeta.osVersion ||
4281
4302
  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 ?? {}) });
4303
+ if (friend && (friend.name !== newName || friend.description !== newDescr || friend.punkId !== newPunk || metaChanged)) {
4304
+ this.#friends.set(friendId, { ...friend, name: newName, description: newDescr, punkId: newPunk, ...(clientMeta ?? {}) });
4284
4305
  this.#persistFriends();
4285
4306
  this.#events.emit("friendInfo", {
4286
4307
  pubkey: friendId,
4287
4308
  userid: friend.userid ?? friendId,
4288
4309
  name: newName,
4289
4310
  description: newDescr,
4311
+ punkId: newPunk,
4290
4312
  ...(clientMeta ?? {})
4291
4313
  });
4292
4314
  }
@@ -4958,6 +4980,11 @@ export class Peer {
4958
4980
  opts.nickname = info.name;
4959
4981
  if (info.description !== undefined)
4960
4982
  opts.statusMessage = info.description;
4983
+ // null clears it explicitly; undefined means "not changing it". Everything
4984
+ // below re-sends the profile to every established friend, which is what
4985
+ // makes an avatar change visible without a reconnect.
4986
+ if (info.punkId !== undefined)
4987
+ opts.punkId = info.punkId ?? undefined;
4961
4988
  this.#profileSentTo.clear();
4962
4989
  for (const timer of this.#profileRetryTimers.values())
4963
4990
  clearTimeout(timer);
@@ -4974,6 +5001,9 @@ export class Peer {
4974
5001
  return {
4975
5002
  name: opts.nickname ?? PEER_NICKNAME,
4976
5003
  description: opts.statusMessage ?? PEER_STATUS_MESSAGE,
5004
+ // Reported so "what am I actually advertising" is answerable without
5005
+ // reading a packet capture — the UI surfaces this as `me.advertised`.
5006
+ punkId: opts.punkId,
4977
5007
  };
4978
5008
  }
4979
5009
  #scheduleProfileRetry(friendId) {
@@ -5030,7 +5060,12 @@ export class Peer {
5030
5060
  protoVersion: AGENTNET_PROTO_VERSION,
5031
5061
  platform: this.#opts.platform ?? process.platform,
5032
5062
  osVersion: this.#opts.osVersion ?? `node-${process.versions?.node ?? ""}`,
5033
- appVersion: this.#opts.appVersion ?? `peer-${PEER_PKG_VERSION}`
5063
+ appVersion: this.#opts.appVersion ?? `peer-${PEER_PKG_VERSION}`,
5064
+ // The avatar, in the field Beagle uses for it (see punkId on
5065
+ // FriendInfoEvent). OMITTED when we have none, never sent as "":
5066
+ // an empty string would clear an avatar the friend already
5067
+ // resolved. Without this, iOS shows us no picture at all.
5068
+ ...(this.#opts.punkId != null ? { gender: String(this.#opts.punkId) } : {})
5034
5069
  });
5035
5070
  await this.#sendMessengerPacket(friendId, PACKET_ID_STATUSMESSAGE, userInfo);
5036
5071
  this.#debugLog(`userinfo sent to ${friendId} (name="${nick}", descr="${descr}", proto=${AGENTNET_PROTO_VERSION}, platform=${this.#opts.platform ?? process.platform})`);
@@ -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;
@@ -88,6 +88,11 @@ 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;
91
96
  compatibilityMode?: CompatibilityMode;
92
97
  debugLabel?: string;
93
98
  };
@@ -110,6 +115,18 @@ export type FriendInfoEvent = {
110
115
  userid?: string;
111
116
  name?: string;
112
117
  description?: string;
118
+ /** The friend's CryptoPunks id — their avatar.
119
+ *
120
+ * Carried in the userinfo packet's `gender` field, which is not a mistake
121
+ * and not ours to rename. The protocol has no avatar field: `has_avatar`
122
+ * is a bool in all four implementations and the image exchange was never
123
+ * built. Beagle borrowed `gender` because a punk carries a gender anyway.
124
+ * Defined in WalletForBeagleV2/docs/profile.md — set with
125
+ * userinfo.setGender(nftid), read back off friendInfo.gender.
126
+ *
127
+ * Undefined when the friend sent no punk, or sent something that is not a
128
+ * punk id (a real gender from a non-Beagle Carrier client). */
129
+ punkId?: number;
113
130
  /** Peer's advertised client metadata (from the userinfo profile). Present
114
131
  * only once the friend has sent a profile with these fields — a legacy
115
132
  * 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.141",
3
+ "version": "0.1.142",
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",