libp2p-mesh 2026.5.23 → 2026.5.25
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/src/plugin.js +125 -59
- package/package.json +1 -1
- package/src/plugin.ts +147 -67
package/dist/src/plugin.js
CHANGED
|
@@ -11,26 +11,84 @@ export function registerLibp2pMesh(api) {
|
|
|
11
11
|
logger: api.logger,
|
|
12
12
|
});
|
|
13
13
|
// Singleton: maps peerId -> { agentId, channel, accountId, sessionKey }
|
|
14
|
+
// Declared here (not inside start) so tool registration below can attach
|
|
15
|
+
// it to tool objects via closure.
|
|
14
16
|
const peerIdentityMap = createPeerIdentityMap();
|
|
15
|
-
// Helper: build the deps object for the relay-aware inbound handler
|
|
16
|
-
function buildInboundDeps() {
|
|
17
|
-
return {
|
|
18
|
-
peerIdentityMap,
|
|
19
|
-
enqueueNextTurnInjection: (injection) => api.session.workflow.enqueueNextTurnInjection(injection),
|
|
20
|
-
logger: api.logger,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
17
|
// 1. Register Service (manages libp2p node lifecycle)
|
|
24
18
|
api.registerService({
|
|
25
19
|
id: "libp2p-mesh",
|
|
26
20
|
start: async () => {
|
|
27
|
-
|
|
28
|
-
// Load persisted peer identities from disk
|
|
29
|
-
await peerIdentityMap.load();
|
|
30
|
-
// Gather local relay identity info
|
|
21
|
+
// Gather config before starting (needed for onMessage handler)
|
|
31
22
|
const config = api.pluginConfig;
|
|
32
23
|
const relayChannel = config?.relayChannel;
|
|
33
24
|
const relayAccountId = config?.relayAccountId;
|
|
25
|
+
function buildInboundDeps() {
|
|
26
|
+
return {
|
|
27
|
+
peerIdentityMap,
|
|
28
|
+
enqueueNextTurnInjection: (injection) => api.session.workflow.enqueueNextTurnInjection(injection),
|
|
29
|
+
logger: api.logger,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
// Wire up relay-aware message handler BEFORE mesh.start() so that
|
|
33
|
+
// any identity messages arriving during startup are queued by libp2p
|
|
34
|
+
// and processed as soon as the protocol handler is ready.
|
|
35
|
+
mesh.onMessage((msg) => {
|
|
36
|
+
if (msg.type === "identity") {
|
|
37
|
+
const localIdentity = peerIdentityMap.getLocalIdentity();
|
|
38
|
+
handleIdentityMessage(msg, {
|
|
39
|
+
peerIdentityMap,
|
|
40
|
+
localPeerId: localIdentity?.sessionKey ?? "",
|
|
41
|
+
localAgentId: api.name,
|
|
42
|
+
localChannel: relayChannel ?? "",
|
|
43
|
+
localAccountId: relayAccountId ?? "",
|
|
44
|
+
localInstanceId: localIdentity?.instanceId,
|
|
45
|
+
send: async (targetPeerId, replyMsg) => {
|
|
46
|
+
mesh.sendToPeer(targetPeerId, JSON.stringify(replyMsg)).catch(() => { });
|
|
47
|
+
},
|
|
48
|
+
logger: api.logger,
|
|
49
|
+
}).catch((err) => {
|
|
50
|
+
api.logger.error?.(`[libp2p-mesh] Identity exchange error: ${String(err)}`);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
else if (msg.type === "direct") {
|
|
54
|
+
// sendToPeer 包装了一层 direct 外壳,原始 identity 消息藏在 payload 里。
|
|
55
|
+
// 检测并处理:注册对方 + 发送自己的身份回复。
|
|
56
|
+
let handled = false;
|
|
57
|
+
try {
|
|
58
|
+
const raw = JSON.parse(msg.payload);
|
|
59
|
+
if (raw && raw.type === "identity") {
|
|
60
|
+
const localIdentity = peerIdentityMap.getLocalIdentity();
|
|
61
|
+
handleIdentityMessage(msg, {
|
|
62
|
+
peerIdentityMap,
|
|
63
|
+
localPeerId: localIdentity?.sessionKey ?? "",
|
|
64
|
+
localAgentId: api.name,
|
|
65
|
+
localChannel: relayChannel ?? "",
|
|
66
|
+
localAccountId: relayAccountId ?? "",
|
|
67
|
+
localInstanceId: localIdentity?.instanceId,
|
|
68
|
+
send: async (targetPeerId, replyMsg) => {
|
|
69
|
+
mesh.sendToPeer(targetPeerId, JSON.stringify(replyMsg)).catch(() => { });
|
|
70
|
+
},
|
|
71
|
+
logger: api.logger,
|
|
72
|
+
}).catch((err) => {
|
|
73
|
+
api.logger.error?.(`[libp2p-mesh] Identity exchange error: ${String(err)}`);
|
|
74
|
+
});
|
|
75
|
+
handled = true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// payload 不是 JSON,正常走 direct 消息逻辑
|
|
80
|
+
}
|
|
81
|
+
if (!handled) {
|
|
82
|
+
handleP2PInbound(msg, buildInboundDeps());
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
handleP2PInbound(msg, buildInboundDeps());
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
await mesh.start();
|
|
90
|
+
// Load persisted peer identities from disk
|
|
91
|
+
await peerIdentityMap.load();
|
|
34
92
|
const instanceIdentity = mesh.getInstanceIdentity();
|
|
35
93
|
const localInstanceId = instanceIdentity?.id;
|
|
36
94
|
const localPeerId = mesh.getLocalPeerId();
|
|
@@ -42,10 +100,6 @@ export function registerLibp2pMesh(api) {
|
|
|
42
100
|
channel: relayChannel,
|
|
43
101
|
accountId: relayAccountId,
|
|
44
102
|
});
|
|
45
|
-
// setLocalIdentity stores the local identity separately (not in peers
|
|
46
|
-
// Map) to avoid duplication. We also register in peers Map so
|
|
47
|
-
// direct messages addressed to us (msg.to === localPeerId) can be
|
|
48
|
-
// resolved by deps.peerIdentityMap.resolve() in handleP2PInbound.
|
|
49
103
|
peerIdentityMap.setLocalIdentity(localPeerId, {
|
|
50
104
|
agentId: api.name,
|
|
51
105
|
channel: relayChannel,
|
|
@@ -67,53 +121,65 @@ export function registerLibp2pMesh(api) {
|
|
|
67
121
|
try {
|
|
68
122
|
await mesh.sendToPeer(peerId, JSON.stringify(identityMsg));
|
|
69
123
|
}
|
|
70
|
-
catch {
|
|
71
|
-
|
|
124
|
+
catch (err) {
|
|
125
|
+
api.logger.warn?.(`[libp2p-mesh] Failed to send initial identity to ${peerId}: ${String(err)}`);
|
|
72
126
|
}
|
|
73
127
|
}
|
|
74
|
-
// When a new peer connects, send our identity to them
|
|
75
|
-
mesh.onPeerConnect((peerId) => {
|
|
76
|
-
api.logger.info?.(`[libp2p-mesh] Peer connected: ${peerId} — sending identity`);
|
|
77
|
-
const msg = buildIdentityMessage(localPeerId, api.name, relayChannel, relayAccountId, localInstanceId);
|
|
78
|
-
mesh.sendToPeer(peerId, JSON.stringify(msg)).catch(() => {
|
|
79
|
-
// Best-effort
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
// When a peer disconnects, clean up the identity map
|
|
83
|
-
mesh.onPeerDisconnect((peerId) => {
|
|
84
|
-
api.logger.info?.(`[libp2p-mesh] Peer disconnected: ${peerId}`);
|
|
85
|
-
peerIdentityMap.unregister(peerId);
|
|
86
|
-
peerIdentityMap.saveNow().catch(() => { });
|
|
87
|
-
});
|
|
88
128
|
}
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
mesh.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
129
|
+
// Register peer lifecycle handlers AFTER mesh.start() so that
|
|
130
|
+
// sendToPeer can establish streams reliably. Without this deferral,
|
|
131
|
+
// onPeerConnect can fire during mDNS discovery (before node.start()
|
|
132
|
+
// completes), causing dialProtocol to fail because the protocol
|
|
133
|
+
// handler isn't fully wired yet — and the .catch(() => {}) swallows
|
|
134
|
+
// the error silently, breaking identity exchange entirely.
|
|
135
|
+
mesh.onPeerConnect((peerId) => {
|
|
136
|
+
api.logger.info?.(`[libp2p-mesh] Peer connected: ${peerId} — sending identity`);
|
|
137
|
+
// Only send identity if relay config is set (otherwise there's no
|
|
138
|
+
// identity to announce).
|
|
139
|
+
if (!relayChannel || !relayAccountId)
|
|
140
|
+
return;
|
|
141
|
+
const msg = buildIdentityMessage(localPeerId ?? "", api.name, relayChannel, relayAccountId, localInstanceId);
|
|
142
|
+
// Retry up to 3 times with 500ms间隔, log failures
|
|
143
|
+
const maxRetries = 3;
|
|
144
|
+
let attempt = 0;
|
|
145
|
+
const sendWithRetry = async () => {
|
|
146
|
+
try {
|
|
147
|
+
await mesh.sendToPeer(peerId, JSON.stringify(msg));
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
attempt++;
|
|
151
|
+
if (attempt < maxRetries) {
|
|
152
|
+
api.logger.debug?.(`[libp2p-mesh] Identity send to ${peerId} failed (attempt ${attempt}/${maxRetries}), retrying...`);
|
|
153
|
+
setTimeout(sendWithRetry, 500);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
api.logger.warn?.(`[libp2p-mesh] Failed to send identity to ${peerId} after ${maxRetries} attempts: ${String(err)}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
sendWithRetry();
|
|
161
|
+
});
|
|
162
|
+
mesh.onPeerDisconnect((peerId) => {
|
|
163
|
+
api.logger.info?.(`[libp2p-mesh] Peer disconnected: ${peerId}`);
|
|
164
|
+
peerIdentityMap.unregister(peerId);
|
|
165
|
+
peerIdentityMap.saveNow().catch(() => { });
|
|
116
166
|
});
|
|
167
|
+
// Actively announce identity to all currently-connected peers.
|
|
168
|
+
// This covers the race where mDNS discovery fires peer:connect during
|
|
169
|
+
// node.start() before onPeerConnect was registered — those peers are
|
|
170
|
+
// already connected but never received our identity.
|
|
171
|
+
if (relayChannel && relayAccountId) {
|
|
172
|
+
const identityMsg = buildIdentityMessage(localPeerId ?? "", api.name, relayChannel, relayAccountId, localInstanceId);
|
|
173
|
+
const connectedNow = mesh.getConnectedPeers();
|
|
174
|
+
if (connectedNow.length > 0) {
|
|
175
|
+
api.logger.info?.(`[libp2p-mesh] Sending initial identity to ${connectedNow.length} connected peer(s)`);
|
|
176
|
+
for (const peerId of connectedNow) {
|
|
177
|
+
mesh.sendToPeer(peerId, JSON.stringify(identityMsg)).catch((err) => {
|
|
178
|
+
api.logger.debug?.(`[libp2p-mesh] Initial identity send to ${peerId} failed: ${String(err)}`);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
117
183
|
const identity = mesh.getInstanceIdentity();
|
|
118
184
|
api.logger.info?.(`[libp2p-mesh] Service started. Peer ID: ${localPeerId}`);
|
|
119
185
|
if (identity) {
|
package/package.json
CHANGED
package/src/plugin.ts
CHANGED
|
@@ -16,31 +16,88 @@ export function registerLibp2pMesh(api: OpenClawPluginApi) {
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
// Singleton: maps peerId -> { agentId, channel, accountId, sessionKey }
|
|
19
|
+
// Declared here (not inside start) so tool registration below can attach
|
|
20
|
+
// it to tool objects via closure.
|
|
19
21
|
const peerIdentityMap = createPeerIdentityMap();
|
|
20
22
|
|
|
21
|
-
// Helper: build the deps object for the relay-aware inbound handler
|
|
22
|
-
function buildInboundDeps() {
|
|
23
|
-
return {
|
|
24
|
-
peerIdentityMap,
|
|
25
|
-
enqueueNextTurnInjection: (injection: PluginNextTurnInjection) =>
|
|
26
|
-
api.session.workflow.enqueueNextTurnInjection(injection),
|
|
27
|
-
logger: api.logger,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
23
|
// 1. Register Service (manages libp2p node lifecycle)
|
|
32
24
|
api.registerService({
|
|
33
25
|
id: "libp2p-mesh",
|
|
34
26
|
start: async () => {
|
|
27
|
+
// Gather config before starting (needed for onMessage handler)
|
|
28
|
+
const config = api.pluginConfig as MeshConfig | undefined;
|
|
29
|
+
const relayChannel = config?.relayChannel;
|
|
30
|
+
const relayAccountId = config?.relayAccountId;
|
|
31
|
+
|
|
32
|
+
function buildInboundDeps() {
|
|
33
|
+
return {
|
|
34
|
+
peerIdentityMap,
|
|
35
|
+
enqueueNextTurnInjection: (injection: PluginNextTurnInjection) =>
|
|
36
|
+
api.session.workflow.enqueueNextTurnInjection(injection),
|
|
37
|
+
logger: api.logger,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Wire up relay-aware message handler BEFORE mesh.start() so that
|
|
42
|
+
// any identity messages arriving during startup are queued by libp2p
|
|
43
|
+
// and processed as soon as the protocol handler is ready.
|
|
44
|
+
mesh.onMessage((msg) => {
|
|
45
|
+
if (msg.type === "identity") {
|
|
46
|
+
const localIdentity = peerIdentityMap.getLocalIdentity();
|
|
47
|
+
handleIdentityMessage(msg, {
|
|
48
|
+
peerIdentityMap,
|
|
49
|
+
localPeerId: localIdentity?.sessionKey ?? "",
|
|
50
|
+
localAgentId: api.name,
|
|
51
|
+
localChannel: relayChannel ?? "",
|
|
52
|
+
localAccountId: relayAccountId ?? "",
|
|
53
|
+
localInstanceId: localIdentity?.instanceId,
|
|
54
|
+
send: async (targetPeerId: string, replyMsg: any) => {
|
|
55
|
+
mesh.sendToPeer(targetPeerId, JSON.stringify(replyMsg)).catch(() => {});
|
|
56
|
+
},
|
|
57
|
+
logger: api.logger,
|
|
58
|
+
}).catch((err) => {
|
|
59
|
+
api.logger.error?.(`[libp2p-mesh] Identity exchange error: ${String(err)}`);
|
|
60
|
+
});
|
|
61
|
+
} else if (msg.type === "direct") {
|
|
62
|
+
// sendToPeer 包装了一层 direct 外壳,原始 identity 消息藏在 payload 里。
|
|
63
|
+
// 检测并处理:注册对方 + 发送自己的身份回复。
|
|
64
|
+
let handled = false;
|
|
65
|
+
try {
|
|
66
|
+
const raw = JSON.parse(msg.payload);
|
|
67
|
+
if (raw && raw.type === "identity") {
|
|
68
|
+
const localIdentity = peerIdentityMap.getLocalIdentity();
|
|
69
|
+
handleIdentityMessage(msg, {
|
|
70
|
+
peerIdentityMap,
|
|
71
|
+
localPeerId: localIdentity?.sessionKey ?? "",
|
|
72
|
+
localAgentId: api.name,
|
|
73
|
+
localChannel: relayChannel ?? "",
|
|
74
|
+
localAccountId: relayAccountId ?? "",
|
|
75
|
+
localInstanceId: localIdentity?.instanceId,
|
|
76
|
+
send: async (targetPeerId: string, replyMsg: any) => {
|
|
77
|
+
mesh.sendToPeer(targetPeerId, JSON.stringify(replyMsg)).catch(() => {});
|
|
78
|
+
},
|
|
79
|
+
logger: api.logger,
|
|
80
|
+
}).catch((err) => {
|
|
81
|
+
api.logger.error?.(`[libp2p-mesh] Identity exchange error: ${String(err)}`);
|
|
82
|
+
});
|
|
83
|
+
handled = true;
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// payload 不是 JSON,正常走 direct 消息逻辑
|
|
87
|
+
}
|
|
88
|
+
if (!handled) {
|
|
89
|
+
handleP2PInbound(msg, buildInboundDeps());
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
handleP2PInbound(msg, buildInboundDeps());
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
35
96
|
await mesh.start();
|
|
36
97
|
|
|
37
98
|
// Load persisted peer identities from disk
|
|
38
99
|
await peerIdentityMap.load();
|
|
39
100
|
|
|
40
|
-
// Gather local relay identity info
|
|
41
|
-
const config = api.pluginConfig as MeshConfig | undefined;
|
|
42
|
-
const relayChannel = config?.relayChannel;
|
|
43
|
-
const relayAccountId = config?.relayAccountId;
|
|
44
101
|
const instanceIdentity = mesh.getInstanceIdentity();
|
|
45
102
|
const localInstanceId = instanceIdentity?.id;
|
|
46
103
|
const localPeerId = mesh.getLocalPeerId();
|
|
@@ -53,10 +110,6 @@ export function registerLibp2pMesh(api: OpenClawPluginApi) {
|
|
|
53
110
|
channel: relayChannel,
|
|
54
111
|
accountId: relayAccountId,
|
|
55
112
|
});
|
|
56
|
-
// setLocalIdentity stores the local identity separately (not in peers
|
|
57
|
-
// Map) to avoid duplication. We also register in peers Map so
|
|
58
|
-
// direct messages addressed to us (msg.to === localPeerId) can be
|
|
59
|
-
// resolved by deps.peerIdentityMap.resolve() in handleP2PInbound.
|
|
60
113
|
peerIdentityMap.setLocalIdentity(localPeerId, {
|
|
61
114
|
agentId: api.name,
|
|
62
115
|
channel: relayChannel,
|
|
@@ -86,62 +139,89 @@ export function registerLibp2pMesh(api: OpenClawPluginApi) {
|
|
|
86
139
|
for (const peerId of mesh.getConnectedPeers()) {
|
|
87
140
|
try {
|
|
88
141
|
await mesh.sendToPeer(peerId, JSON.stringify(identityMsg));
|
|
89
|
-
} catch {
|
|
90
|
-
|
|
142
|
+
} catch (err) {
|
|
143
|
+
api.logger.warn?.(
|
|
144
|
+
`[libp2p-mesh] Failed to send initial identity to ${peerId}: ${String(err)}`,
|
|
145
|
+
);
|
|
91
146
|
}
|
|
92
147
|
}
|
|
148
|
+
}
|
|
93
149
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
relayChannel,
|
|
101
|
-
relayAccountId,
|
|
102
|
-
localInstanceId,
|
|
103
|
-
);
|
|
104
|
-
mesh.sendToPeer(peerId, JSON.stringify(msg)).catch(() => {
|
|
105
|
-
// Best-effort
|
|
106
|
-
});
|
|
107
|
-
});
|
|
150
|
+
// Register peer lifecycle handlers AFTER mesh.start() so that
|
|
151
|
+
// sendToPeer can establish streams reliably. Without this deferral,
|
|
152
|
+
// onPeerConnect can fire during mDNS discovery (before node.start()
|
|
153
|
+
// completes), causing dialProtocol to fail because the protocol
|
|
154
|
+
// handler isn't fully wired yet — and the .catch(() => {}) swallows
|
|
155
|
+
// the error silently, breaking identity exchange entirely.
|
|
108
156
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
157
|
+
mesh.onPeerConnect((peerId: string) => {
|
|
158
|
+
api.logger.info?.(`[libp2p-mesh] Peer connected: ${peerId} — sending identity`);
|
|
159
|
+
// Only send identity if relay config is set (otherwise there's no
|
|
160
|
+
// identity to announce).
|
|
161
|
+
if (!relayChannel || !relayAccountId) return;
|
|
162
|
+
const msg = buildIdentityMessage(
|
|
163
|
+
localPeerId ?? "",
|
|
164
|
+
api.name,
|
|
165
|
+
relayChannel,
|
|
166
|
+
relayAccountId,
|
|
167
|
+
localInstanceId,
|
|
168
|
+
);
|
|
169
|
+
// Retry up to 3 times with 500ms间隔, log failures
|
|
170
|
+
const maxRetries = 3;
|
|
171
|
+
let attempt = 0;
|
|
172
|
+
const sendWithRetry = async (): Promise<void> => {
|
|
173
|
+
try {
|
|
174
|
+
await mesh.sendToPeer(peerId, JSON.stringify(msg));
|
|
175
|
+
} catch (err) {
|
|
176
|
+
attempt++;
|
|
177
|
+
if (attempt < maxRetries) {
|
|
178
|
+
api.logger.debug?.(
|
|
179
|
+
`[libp2p-mesh] Identity send to ${peerId} failed (attempt ${attempt}/${maxRetries}), retrying...`,
|
|
180
|
+
);
|
|
181
|
+
setTimeout(sendWithRetry, 500);
|
|
182
|
+
} else {
|
|
183
|
+
api.logger.warn?.(
|
|
184
|
+
`[libp2p-mesh] Failed to send identity to ${peerId} after ${maxRetries} attempts: ${String(err)}`,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
sendWithRetry();
|
|
190
|
+
});
|
|
116
191
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// identity exchange even when peer:connect events are one-sided
|
|
122
|
-
// (e.g. NAT/relay scenarios).
|
|
123
|
-
mesh.onMessage((msg) => {
|
|
124
|
-
if (msg.type === "identity") {
|
|
125
|
-
const localIdentity = peerIdentityMap.getLocalIdentity();
|
|
126
|
-
handleIdentityMessage(msg, {
|
|
127
|
-
peerIdentityMap,
|
|
128
|
-
localPeerId,
|
|
129
|
-
localAgentId: api.name,
|
|
130
|
-
localChannel: relayChannel ?? "",
|
|
131
|
-
localAccountId: relayAccountId ?? "",
|
|
132
|
-
localInstanceId,
|
|
133
|
-
send: async (targetPeerId: string, replyMsg: any) => {
|
|
134
|
-
mesh.sendToPeer(targetPeerId, JSON.stringify(replyMsg)).catch(() => {});
|
|
135
|
-
},
|
|
136
|
-
logger: api.logger,
|
|
137
|
-
}).catch((err) => {
|
|
138
|
-
api.logger.error?.(`[libp2p-mesh] Identity exchange error: ${String(err)}`);
|
|
139
|
-
});
|
|
140
|
-
} else {
|
|
141
|
-
handleP2PInbound(msg, buildInboundDeps());
|
|
142
|
-
}
|
|
192
|
+
mesh.onPeerDisconnect((peerId: string) => {
|
|
193
|
+
api.logger.info?.(`[libp2p-mesh] Peer disconnected: ${peerId}`);
|
|
194
|
+
peerIdentityMap.unregister(peerId);
|
|
195
|
+
peerIdentityMap.saveNow().catch(() => {});
|
|
143
196
|
});
|
|
144
197
|
|
|
198
|
+
// Actively announce identity to all currently-connected peers.
|
|
199
|
+
// This covers the race where mDNS discovery fires peer:connect during
|
|
200
|
+
// node.start() before onPeerConnect was registered — those peers are
|
|
201
|
+
// already connected but never received our identity.
|
|
202
|
+
if (relayChannel && relayAccountId) {
|
|
203
|
+
const identityMsg = buildIdentityMessage(
|
|
204
|
+
localPeerId ?? "",
|
|
205
|
+
api.name,
|
|
206
|
+
relayChannel,
|
|
207
|
+
relayAccountId,
|
|
208
|
+
localInstanceId,
|
|
209
|
+
);
|
|
210
|
+
const connectedNow = mesh.getConnectedPeers();
|
|
211
|
+
if (connectedNow.length > 0) {
|
|
212
|
+
api.logger.info?.(
|
|
213
|
+
`[libp2p-mesh] Sending initial identity to ${connectedNow.length} connected peer(s)`,
|
|
214
|
+
);
|
|
215
|
+
for (const peerId of connectedNow) {
|
|
216
|
+
mesh.sendToPeer(peerId, JSON.stringify(identityMsg)).catch((err) => {
|
|
217
|
+
api.logger.debug?.(
|
|
218
|
+
`[libp2p-mesh] Initial identity send to ${peerId} failed: ${String(err)}`,
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
145
225
|
const identity = mesh.getInstanceIdentity();
|
|
146
226
|
api.logger.info?.(`[libp2p-mesh] Service started. Peer ID: ${localPeerId}`);
|
|
147
227
|
if (identity) {
|