reploid 0.0.0-stage → 0.1.1

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.
Files changed (233) hide show
  1. package/apps/poolday/config/chat-files.json +27 -0
  2. package/apps/poolday/config/partition-generation.js +21 -0
  3. package/apps/poolday/config/partition-policy.json +74 -0
  4. package/apps/poolday/config/partition-releases.json +3 -0
  5. package/apps/poolday/config/work-profile.json +122 -0
  6. package/apps/poolday/host/chat-view.js +47 -0
  7. package/apps/poolday/host/earning/client.js +199 -0
  8. package/apps/poolday/host/earning/contribution.js +168 -0
  9. package/apps/poolday/host/partition-release.js +33 -0
  10. package/apps/poolday/host/work-partition-artifact-source.js +37 -0
  11. package/apps/poolday/host/work-partition-capsule.js +39 -0
  12. package/apps/poolday/host/work-partition-plan.js +167 -0
  13. package/apps/poolday/host/work-partition-tokenizer-guard.js +21 -0
  14. package/apps/poolday/infrastructure/model-storage-lock.js +22 -0
  15. package/apps/poolday/infrastructure/pack-release-storage.js +52 -0
  16. package/apps/poolday/pool/pack-operation.js +161 -0
  17. package/apps/poolday/pool/pack-release-policy.js +44 -0
  18. package/apps/poolday/pool/peer-pack-data-channel.js +217 -0
  19. package/apps/poolday/providers/work-adapter.js +67 -0
  20. package/apps/poolday/providers/work-device.js +33 -0
  21. package/apps/poolday/providers/work-generation-completion.js +55 -0
  22. package/apps/poolday/providers/work-provider.js +209 -0
  23. package/apps/poolday/providers/work-resident-provider.js +113 -0
  24. package/deploy/artifacts/doppler-gpu-0.7.1.tgz +0 -0
  25. package/docs/fleet-service.md +214 -0
  26. package/docs/native-participant.md +542 -0
  27. package/native-delivery.json +1174 -0
  28. package/package.json +29 -4
  29. package/packages/reploid/src/artifacts/binary.js +25 -0
  30. package/packages/reploid/src/artifacts/canonical-json.js +23 -0
  31. package/packages/reploid/src/artifacts/custody/exchange.js +402 -0
  32. package/packages/reploid/src/artifacts/custody/piece-acquisition.js +70 -0
  33. package/packages/reploid/src/artifacts/custody/runtime.js +404 -0
  34. package/packages/reploid/src/artifacts/custody/supply-budget.js +19 -0
  35. package/packages/reploid/src/artifacts/custody-certificate.js +123 -0
  36. package/packages/reploid/src/artifacts/delivery-attestation.js +64 -0
  37. package/packages/reploid/src/artifacts/generation-terminal.js +36 -0
  38. package/packages/reploid/src/artifacts/identity.js +91 -0
  39. package/packages/reploid/src/artifacts/participant-proofs.js +159 -0
  40. package/packages/reploid/src/artifacts/receipt.js +131 -0
  41. package/packages/reploid/src/artifacts/signatures.js +94 -0
  42. package/packages/reploid/src/artifacts/whole-model-receipt.js +42 -0
  43. package/packages/reploid/src/cancellation.js +44 -0
  44. package/packages/reploid/src/config/defaults.json +110 -0
  45. package/packages/reploid/src/config/index.js +209 -0
  46. package/packages/reploid/src/config/request-overrides.json +9 -0
  47. package/packages/reploid/src/config/transport-defaults.json +7 -0
  48. package/packages/reploid/src/hashing.js +30 -0
  49. package/packages/reploid/src/measurement/capacity-utilization.js +321 -0
  50. package/packages/reploid/src/measurement/index.js +5 -0
  51. package/packages/reploid/src/measurement/readiness-records.js +310 -0
  52. package/packages/reploid/src/mesh/computer-identity.js +50 -0
  53. package/packages/reploid/src/mesh/computer-identity.policy.json +8 -0
  54. package/packages/reploid/src/mesh/contribution.js +141 -0
  55. package/packages/reploid/src/mesh/generation-recovery.js +58 -0
  56. package/packages/reploid/src/mesh/gpu-signature-shader.js +27 -0
  57. package/packages/reploid/src/mesh/gpu-signature.js +136 -0
  58. package/packages/reploid/src/mesh/gpu-signature.policy.json +11 -0
  59. package/packages/reploid/src/mesh/index.js +66 -0
  60. package/packages/reploid/src/mesh/partitions/automatic-ordered-partitions.js +540 -0
  61. package/packages/reploid/src/mesh/partitions/automatic-partitions.js +298 -0
  62. package/packages/reploid/src/mesh/partitions/partition-attempt-evidence.js +282 -0
  63. package/packages/reploid/src/mesh/partitions/partition-attempt-queue-policy.json +6 -0
  64. package/packages/reploid/src/mesh/partitions/partition-attempt-queue.js +112 -0
  65. package/packages/reploid/src/mesh/partitions/partition-canonical-contract.js +167 -0
  66. package/packages/reploid/src/mesh/partitions/partition-canonical-execution.js +168 -0
  67. package/packages/reploid/src/mesh/partitions/partition-chat-canonical.js +49 -0
  68. package/packages/reploid/src/mesh/partitions/partition-chat.js +125 -0
  69. package/packages/reploid/src/mesh/partitions/partition-contract.js +192 -0
  70. package/packages/reploid/src/mesh/partitions/partition-device-admission.js +96 -0
  71. package/packages/reploid/src/mesh/partitions/partition-discovery.js +96 -0
  72. package/packages/reploid/src/mesh/partitions/partition-entry-publication.js +183 -0
  73. package/packages/reploid/src/mesh/partitions/partition-entry.js +434 -0
  74. package/packages/reploid/src/mesh/partitions/partition-generation-request.js +33 -0
  75. package/packages/reploid/src/mesh/partitions/partition-grants.js +277 -0
  76. package/packages/reploid/src/mesh/partitions/partition-link.js +103 -0
  77. package/packages/reploid/src/mesh/partitions/partition-network.js +109 -0
  78. package/packages/reploid/src/mesh/partitions/partition-ordered-chat.js +290 -0
  79. package/packages/reploid/src/mesh/partitions/partition-ordered-peer.js +415 -0
  80. package/packages/reploid/src/mesh/partitions/partition-ordered-publication.js +100 -0
  81. package/packages/reploid/src/mesh/partitions/partition-peer.js +314 -0
  82. package/packages/reploid/src/mesh/partitions/partition-placement.js +143 -0
  83. package/packages/reploid/src/mesh/partitions/partition-preparation-progress.js +25 -0
  84. package/packages/reploid/src/mesh/partitions/partition-receiver-policy.json +7 -0
  85. package/packages/reploid/src/mesh/partitions/partition-reservations.js +75 -0
  86. package/packages/reploid/src/mesh/partitions/partition-route-envelope-policy.json +16 -0
  87. package/packages/reploid/src/mesh/partitions/partition-route-envelope.js +74 -0
  88. package/packages/reploid/src/mesh/partitions/partition-route-execution.js +409 -0
  89. package/packages/reploid/src/mesh/partitions/partition-route-network.js +179 -0
  90. package/packages/reploid/src/mesh/partitions/partition-route-ranking.js +268 -0
  91. package/packages/reploid/src/mesh/partitions/partition-runner.js +319 -0
  92. package/packages/reploid/src/mesh/partitions/partition-stage-assignment.js +109 -0
  93. package/packages/reploid/src/mesh/partitions/partition-stage-commit.js +103 -0
  94. package/packages/reploid/src/mesh/partitions/partition-step-receiver.js +291 -0
  95. package/packages/reploid/src/mesh/partitions/partition-summary.js +122 -0
  96. package/packages/reploid/src/mesh/partitions/preparation-owner.js +40 -0
  97. package/packages/reploid/src/mesh/partitions/resident-error-codes.json +10 -0
  98. package/packages/reploid/src/mesh/partitions/resident-partition.js +333 -0
  99. package/packages/reploid/src/mesh/partitions/resident-route.js +46 -0
  100. package/packages/reploid/src/mesh/partitions/signed-resident-factory.js +59 -0
  101. package/packages/reploid/src/mesh/peer-generation.js +896 -0
  102. package/packages/reploid/src/mesh/peer-identity.js +87 -0
  103. package/packages/reploid/src/mesh/placement-beliefs.js +107 -0
  104. package/packages/reploid/src/mesh/preparation-progress.js +46 -0
  105. package/packages/reploid/src/mesh/provider-hardware.js +11 -0
  106. package/packages/reploid/src/mesh/remote-generation-requests.js +53 -0
  107. package/packages/reploid/src/mesh/swarm-coordination.js +140 -0
  108. package/packages/reploid/src/mesh/web-access-network.js +112 -0
  109. package/packages/reploid/src/mesh/web-access-policy.json +17 -0
  110. package/packages/reploid/src/retrieval/citations.js +156 -0
  111. package/packages/reploid/src/retrieval/claim-support.js +113 -0
  112. package/packages/reploid/src/retrieval/costs.js +101 -0
  113. package/packages/reploid/src/retrieval/fixed-provider.js +34 -0
  114. package/packages/reploid/src/retrieval/index.js +20 -0
  115. package/packages/reploid/src/retrieval/lexical-support.js +27 -0
  116. package/packages/reploid/src/retrieval/model-reviewer.js +67 -0
  117. package/packages/reploid/src/retrieval/page-fetch.json +10 -0
  118. package/packages/reploid/src/retrieval/page-text.js +182 -0
  119. package/packages/reploid/src/retrieval/passage.js +37 -0
  120. package/packages/reploid/src/retrieval/policy.json +14 -0
  121. package/packages/reploid/src/retrieval/retrieval.js +23 -0
  122. package/packages/reploid/src/retrieval/review-queue.js +76 -0
  123. package/packages/reploid/src/retrieval/sources.js +263 -0
  124. package/packages/reploid/src/retrieval/support-review.js +155 -0
  125. package/packages/reploid/src/retrieval/text.js +21 -0
  126. package/packages/reploid/src/rules/contribution.rules.json +8 -0
  127. package/packages/reploid/src/rules/routing.rules.json +24 -0
  128. package/packages/reploid/src/transport/assignment.js +571 -0
  129. package/packages/reploid/src/transport/bounded-channel-writer.js +46 -0
  130. package/packages/reploid/src/transport/index.js +11 -0
  131. package/packages/reploid/src/transport/introduction-admission.js +24 -0
  132. package/packages/reploid/src/transport/introduction-bootstrap.js +112 -0
  133. package/packages/reploid/src/transport/introduction-policy.json +12 -0
  134. package/packages/reploid/src/transport/introduction-records.js +90 -0
  135. package/packages/reploid/src/transport/introductions.js +209 -0
  136. package/packages/reploid/src/transport/partition-data-channel.js +421 -0
  137. package/packages/reploid/src/transport/partition-errors.json +16 -0
  138. package/packages/reploid/src/transport/retry-policy.js +36 -0
  139. package/packages/reploid/src/transport/room.js +502 -0
  140. package/packages/reploid/src/transport/sdp-fingerprint.js +5 -0
  141. package/packages/reploid/src/transport/signaling.js +571 -0
  142. package/packages/reploid/src/transport/swarm.js +1247 -0
  143. package/packages/reploid/src/transport/tool-offer-channel.js +137 -0
  144. package/server/browser-roots.js +42 -0
  145. package/server/browser-web-worker.js +94 -0
  146. package/server/chat-completions.js +45 -0
  147. package/server/fleet/browser-bridge.js +145 -0
  148. package/server/fleet/contract.js +42 -0
  149. package/server/fleet/credit-evidence.js +103 -0
  150. package/server/fleet/credit-recovery.js +175 -0
  151. package/server/fleet/credits.js +178 -0
  152. package/server/fleet/execution-admission.js +32 -0
  153. package/server/fleet/native-host.js +817 -0
  154. package/server/fleet/placement.js +132 -0
  155. package/server/fleet/requester.js +346 -0
  156. package/server/fleet/service.js +311 -0
  157. package/server/fleet/terminal-policy.js +83 -0
  158. package/server/fleet/text-api.js +94 -0
  159. package/server/hashing.js +35 -0
  160. package/server/http-request-owner.js +49 -0
  161. package/server/messages.js +57 -0
  162. package/server/native-canonical-epochs.js +90 -0
  163. package/server/native-change-log.js +40 -0
  164. package/server/native-earning.js +203 -0
  165. package/server/native-generation-journal.js +46 -0
  166. package/server/native-model-config.js +57 -0
  167. package/server/native-model-files.js +382 -0
  168. package/server/native-model-origin.js +54 -0
  169. package/server/native-model-origins.json +12 -0
  170. package/server/native-node.js +85 -0
  171. package/server/native-owner/controls.js +162 -0
  172. package/server/native-owner/monitor.js +251 -0
  173. package/server/native-owner/policy.js +273 -0
  174. package/server/native-owner/rate.js +92 -0
  175. package/server/native-participant.js +866 -0
  176. package/server/native-readiness-log.js +30 -0
  177. package/server/native-release-storage.js +64 -0
  178. package/server/native-shader-cache.js +369 -0
  179. package/server/native-supply-budget.js +66 -0
  180. package/server/native-whole-earning.js +67 -0
  181. package/server/native-whole-model-fill.js +182 -0
  182. package/server/native-whole-model.js +130 -0
  183. package/server/public-web-worker.js +66 -0
  184. package/server/responses.js +92 -0
  185. package/server/search/errors.js +47 -0
  186. package/server/search/page-fetch.js +185 -0
  187. package/server/text-api.js +83 -0
  188. package/server/text-recovery.js +45 -0
  189. package/server/text-request-options.js +23 -0
  190. package/server/text-requester.js +231 -0
  191. package/server/token-auth.js +9 -0
  192. package/server/whole-model-requester.js +99 -0
  193. package/shared/config/chat-models.json +4685 -0
  194. package/shared/config/doppler-execution-contracts.js +32 -0
  195. package/shared/config/doppler-execution-contracts.json +43 -0
  196. package/shared/config/doppler-generation-contract.js +223 -0
  197. package/shared/config/doppler-local-models.js +79 -0
  198. package/shared/config/doppler-package.json +7 -0
  199. package/shared/config/model-manifests/a0dc69e917133a82780823d17fc771c0f6b882d7c86c571deeaef45be2ff108b.json +6434 -0
  200. package/shared/config/model-manifests/e985e3cdad60d0cb57d84b436161e66ab5b3f6589a168de6f00b4610e5f1fb59.json +16534 -0
  201. package/shared/config/model-manifests/fa6eb7bbc3ba63571f8cede780b877d9272bde948697af93dd3d33837cb46137.json +7175 -0
  202. package/shared/config/model-pieces/qwen-0-8b-pieces.json +1506 -0
  203. package/shared/config/model-pieces/qwen-2b-pieces.json +4800 -0
  204. package/shared/config/model-pieces/qwen35b-q4_0-pieces.json +28880 -0
  205. package/shared/config/reploid-library.json +49 -0
  206. package/shared/config/swarm-bootstrap.json +21 -0
  207. package/shared/infrastructure/doppler-runtime-service.js +261 -0
  208. package/shared/pool/executable-pack.js +87 -0
  209. package/shared/pool/pack-operation-adapters.js +124 -0
  210. package/shared/pool/pack-operation-policy.js +75 -0
  211. package/shared/pool/pool-config.json +2280 -0
  212. package/shared/vendor/reploid/adapters/doppler-session.js +372 -0
  213. package/shared/vendor/reploid/artifacts/binary.js +25 -0
  214. package/shared/vendor/reploid/artifacts/canonical-json.js +23 -0
  215. package/shared/vendor/reploid/artifacts/delivery-attestation.js +64 -0
  216. package/shared/vendor/reploid/artifacts/identity.js +91 -0
  217. package/shared/vendor/reploid/artifacts/manifest-identity.js +17 -0
  218. package/shared/vendor/reploid/artifacts/participant-proofs.js +159 -0
  219. package/shared/vendor/reploid/artifacts/signatures.js +94 -0
  220. package/shared/vendor/reploid/config/defaults.json +110 -0
  221. package/shared/vendor/reploid/config/index.js +209 -0
  222. package/shared/vendor/reploid/config/request-overrides.json +9 -0
  223. package/shared/vendor/reploid/hashing.js +30 -0
  224. package/shared/vendor/reploid/mesh/generation-context-cache.js +97 -0
  225. package/shared/vendor/reploid/mesh/partitions/partition-attempt-evidence.js +282 -0
  226. package/shared/vendor/reploid/mesh/partitions/partition-contract.js +192 -0
  227. package/shared/vendor/reploid/mesh/partitions/partition-receiver-policy.json +7 -0
  228. package/shared/vendor/reploid/mesh/partitions/partition-route-ranking.js +268 -0
  229. package/shared/vendor/reploid/mesh/preparation-progress.js +46 -0
  230. package/shared/vendor/reploid/mesh/provider-hardware.js +11 -0
  231. package/shared/vendor/reploid/rules/routing.rules.json +24 -0
  232. package/shared/vendor/reploid/transport/partition-errors.json +16 -0
  233. package/README.md +0 -3
@@ -0,0 +1,502 @@
1
+ /**
2
+ * @fileoverview Swarm Transport Abstraction
3
+ * Auto-selects between BroadcastChannel (same browser, no server) and WebRTC (cross-machine).
4
+ * Provides a unified interface for SwarmSync regardless of underlying transport.
5
+ */
6
+
7
+ import { requireResolvedConfig } from '../config/index.js';
8
+ import { createWebRTCSwarm } from './swarm.js';
9
+
10
+ const PROTOCOL_VERSION = 1;
11
+
12
+
13
+ // Valid message types
14
+ const MESSAGE_TYPES = new Set([
15
+ 'sync-request', 'sync-response',
16
+ 'goal-update', 'reflection-share',
17
+ 'artifact-announce', 'artifact-request', 'artifact-chunk', 'artifact-ack',
18
+ 'reploid:peer-advertisement',
19
+ 'reploid:identity-challenge', 'reploid:identity-proof',
20
+ 'reploid:generation-request', 'reploid:generation-update',
21
+ 'reploid:generation-result', 'reploid:generation-error',
22
+ 'reploid:generation-cancel', 'reploid:generation-recover', 'reploid:generation-recovery',
23
+ 'reploid:receipt',
24
+ 'reploid:tool-offer', 'reploid:tool-offer-ack',
25
+ 'reploid:custody-request', 'reploid:custody-response', 'reploid:custody-offer',
26
+ 'ping', 'pong', 'peer-announce', 'peer-leave',
27
+ 'raft:request-vote', 'raft:request-vote-response',
28
+ 'raft:append-entries', 'raft:append-entries-response',
29
+ 'raft:client-request', 'raft:client-response',
30
+ 'fl:hello', 'fl:round-start', 'fl:update', 'fl:round-commit', 'fl:round-failed'
31
+ ]);
32
+
33
+ const SwarmTransport = {
34
+ metadata: {
35
+ id: 'SwarmTransport',
36
+ version: '1.0.0',
37
+ genesis: { introduced: 'full' },
38
+ dependencies: ['Utils', 'EventBus'],
39
+ async: true,
40
+ type: 'capability'
41
+ },
42
+
43
+ factory: (deps) => {
44
+ const { Utils, EventBus } = deps;
45
+ const { logger, generateId } = Utils;
46
+ const policy = requireResolvedConfig(deps.config);
47
+ const MAX_PAYLOAD_SIZE = policy.webrtc.maxMessageBytes;
48
+ const BroadcastChannel = deps.BroadcastChannel || globalThis.BroadcastChannel;
49
+ const WebSocket = deps.WebSocket || globalThis.WebSocket;
50
+
51
+ // State
52
+ let _peerId = null;
53
+ let _roomId = null;
54
+ let _transport = null; // 'broadcast' | 'webrtc'
55
+ let _broadcastChannel = null;
56
+ let _webrtcSwarm = null; // Reference to WebRTCSwarm if using WebRTC
57
+ let _messageHandlers = new Map();
58
+ let _logicalClock = 0;
59
+ let _peers = new Map(); // peerId -> { lastSeen, metadata }
60
+ let _connectionState = 'disconnected';
61
+ let initVersion = 0;
62
+ let cancelProbe = null;
63
+
64
+ // Peer timeout for BroadcastChannel (no heartbeat from server)
65
+ const PEER_TIMEOUT = policy.webrtc.peerTimeoutMs;
66
+ const ANNOUNCE_INTERVAL = policy.webrtc.announceIntervalMs;
67
+ let _announceTimer = null;
68
+ let _cleanupTimer = null;
69
+
70
+ /**
71
+ * Increment logical clock
72
+ */
73
+ const tick = () => ++_logicalClock;
74
+
75
+ /**
76
+ * Update clock on receive
77
+ */
78
+ const updateClock = (remoteClock) => {
79
+ _logicalClock = Math.max(_logicalClock, remoteClock) + 1;
80
+ };
81
+
82
+ /**
83
+ * Get room ID from URL param or default
84
+ */
85
+ const getRoomId = () => policy.webrtc.broadcastRoomId;
86
+ const isEnabled = () => policy.mesh.enabled;
87
+ const checkSignalingServer = async () => {
88
+ if (!policy.webrtc.signalingUrl || typeof WebSocket !== 'function') return false;
89
+ return new Promise(resolve => {
90
+ let socket = null;
91
+ let settled = false;
92
+ const finish = available => {
93
+ if (settled) return;
94
+ settled = true;
95
+ cancelProbe = null;
96
+ clearTimeout(timer);
97
+ if (socket) { socket.onopen = null; socket.onerror = null; socket.close(); }
98
+ resolve(available);
99
+ };
100
+ const timer = setTimeout(() => finish(false), policy.webrtc.signalingProbeTimeoutMs);
101
+ cancelProbe = () => finish(false);
102
+ try {
103
+ socket = new WebSocket(policy.webrtc.signalingUrl);
104
+ socket.onopen = () => finish(true);
105
+ socket.onerror = () => finish(false);
106
+ } catch { finish(false); }
107
+ });
108
+ };
109
+
110
+ /**
111
+ * Validate message envelope
112
+ */
113
+ const validateEnvelope = (envelope) => {
114
+ if (!envelope || typeof envelope !== 'object') {
115
+ return { valid: false, reason: 'Invalid envelope format' };
116
+ }
117
+ if (envelope.protocolVersion !== PROTOCOL_VERSION) {
118
+ return { valid: false, reason: `Protocol version mismatch: ${envelope.protocolVersion}` };
119
+ }
120
+ if (!MESSAGE_TYPES.has(envelope.type)) {
121
+ return { valid: false, reason: `Unknown message type: ${envelope.type}` };
122
+ }
123
+ if (!envelope.peerId || typeof envelope.peerId !== 'string') {
124
+ return { valid: false, reason: 'Missing or invalid peerId' };
125
+ }
126
+ return { valid: true };
127
+ };
128
+
129
+ /**
130
+ * Wrap message in protocol envelope
131
+ */
132
+ const wrapMessage = (type, payload) => {
133
+ const payloadStr = JSON.stringify(payload || {});
134
+ if (payloadStr.length > MAX_PAYLOAD_SIZE) {
135
+ throw new Error(`Payload exceeds max size: ${payloadStr.length} > ${MAX_PAYLOAD_SIZE}`);
136
+ }
137
+ return {
138
+ protocolVersion: PROTOCOL_VERSION,
139
+ type,
140
+ peerId: _peerId,
141
+ roomId: _roomId,
142
+ timestamp: tick(),
143
+ payload: payload || {},
144
+ payloadSize: payloadStr.length
145
+ };
146
+ };
147
+
148
+ // =========================================
149
+ // BroadcastChannel Transport
150
+ // =========================================
151
+
152
+ /**
153
+ * Initialize BroadcastChannel transport
154
+ */
155
+ const initBroadcastChannel = () => {
156
+ _transport = 'broadcast';
157
+ _broadcastChannel = new BroadcastChannel(_roomId);
158
+
159
+ _broadcastChannel.onmessage = (event) => {
160
+ handleBroadcastMessage(event.data);
161
+ };
162
+
163
+ _connectionState = 'connected';
164
+ EventBus.emit('swarm:state-change', { state: _connectionState, transport: 'broadcast' });
165
+
166
+ // Announce presence
167
+ broadcastRaw({ type: 'peer-announce', peerId: _peerId, timestamp: Date.now() });
168
+
169
+ // Periodic announce for peer discovery
170
+ _announceTimer = setInterval(() => {
171
+ broadcastRaw({ type: 'peer-announce', peerId: _peerId, timestamp: Date.now() });
172
+ }, ANNOUNCE_INTERVAL);
173
+
174
+ // Cleanup stale peers
175
+ _cleanupTimer = setInterval(() => {
176
+ const now = Date.now();
177
+ for (const [peerId, peer] of _peers) {
178
+ if (now - peer.lastSeen > PEER_TIMEOUT) {
179
+ logger.debug(`[SwarmTransport] Peer timeout: ${peerId}`);
180
+ _peers.delete(peerId);
181
+ EventBus.emit('swarm:peer-left', { peerId });
182
+ }
183
+ }
184
+ }, PEER_TIMEOUT / 2);
185
+
186
+ logger.info(`[SwarmTransport] BroadcastChannel initialized for room: ${_roomId}`);
187
+ return true;
188
+ };
189
+
190
+ /**
191
+ * Send raw message via BroadcastChannel (for internal use)
192
+ */
193
+ const broadcastRaw = (message) => {
194
+ if (_broadcastChannel) {
195
+ _broadcastChannel.postMessage(message);
196
+ }
197
+ };
198
+
199
+ /**
200
+ * Handle incoming BroadcastChannel message
201
+ */
202
+ const handleBroadcastMessage = (data) => {
203
+ // Handle peer announce (not wrapped in envelope)
204
+ if (data.type === 'peer-announce') {
205
+ if (data.peerId !== _peerId) {
206
+ const isNew = !_peers.has(data.peerId);
207
+ _peers.set(data.peerId, { lastSeen: Date.now(), metadata: {} });
208
+
209
+ if (isNew) {
210
+ logger.info(`[SwarmTransport] Peer discovered: ${data.peerId}`);
211
+ EventBus.emit('swarm:peer-joined', { peerId: data.peerId });
212
+
213
+ // Announce back so they know about us
214
+ broadcastRaw({ type: 'peer-announce', peerId: _peerId, timestamp: Date.now() });
215
+ }
216
+ }
217
+ return;
218
+ }
219
+
220
+ if (data.type === 'peer-leave') {
221
+ if (data.peerId !== _peerId && _peers.has(data.peerId)) {
222
+ _peers.delete(data.peerId);
223
+ EventBus.emit('swarm:peer-left', { peerId: data.peerId });
224
+ }
225
+ return;
226
+ }
227
+
228
+ // Validate envelope
229
+ const validation = validateEnvelope(data);
230
+ if (!validation.valid) {
231
+ logger.debug(`[SwarmTransport] Invalid message: ${validation.reason}`);
232
+ return;
233
+ }
234
+
235
+ // Ignore own messages
236
+ if (data.peerId === _peerId) return;
237
+
238
+ // Ignore messages for other rooms
239
+ if (data.roomId && data.roomId !== _roomId) return;
240
+ if (data.targetPeer && data.targetPeer !== _peerId) return;
241
+
242
+ // Update peer tracking
243
+ _peers.set(data.peerId, { lastSeen: Date.now(), metadata: {} });
244
+
245
+ // Update clock
246
+ updateClock(data.timestamp);
247
+
248
+ // Route to handler
249
+ const handler = _messageHandlers.get(data.type);
250
+ if (handler) {
251
+ try {
252
+ handler(data.peerId, data.payload, data);
253
+ } catch (e) {
254
+ logger.error(`[SwarmTransport] Handler error for ${data.type}:`, e);
255
+ }
256
+ }
257
+
258
+ // Emit event
259
+ EventBus.emit(`swarm:message:${data.type}`, {
260
+ peerId: data.peerId,
261
+ payload: data.payload,
262
+ timestamp: data.timestamp
263
+ });
264
+ };
265
+
266
+ // =========================================
267
+ // WebRTC Transport (delegates to WebRTCSwarm)
268
+ // =========================================
269
+
270
+ /**
271
+ * Initialize WebRTC transport via existing WebRTCSwarm
272
+ */
273
+ const initWebRTC = async (version) => {
274
+ _transport = 'webrtc';
275
+
276
+ try {
277
+ const transport = deps.createWebRTCSwarm
278
+ ? await deps.createWebRTCSwarm()
279
+ : createWebRTCSwarm(deps);
280
+ if (version !== initVersion) { transport.disconnect(); return false; }
281
+ _webrtcSwarm = transport;
282
+ for (const [type, handler] of _messageHandlers.entries()) transport.onMessage(type, handler);
283
+ const initialized = await transport.init();
284
+ if (version !== initVersion) { transport.disconnect(); return false; }
285
+ if (!initialized && policy.webrtc.transportOrder.length !== 1) { transport.disconnect(); _webrtcSwarm = null; return false; }
286
+
287
+ if (_webrtcSwarm) {
288
+ _peerId = _webrtcSwarm._getPeerId();
289
+ _connectionState = _webrtcSwarm.getConnectionState();
290
+ for (const [type, handler] of _messageHandlers.entries()) {
291
+ _webrtcSwarm.onMessage(type, handler);
292
+ }
293
+ logger.info('[SwarmTransport] Using WebRTC transport via WebRTCSwarm');
294
+ return true;
295
+ }
296
+ } catch (e) {
297
+ logger.warn('[SwarmTransport] WebRTC initialization failed', e);
298
+ }
299
+
300
+ return false;
301
+ };
302
+
303
+ // =========================================
304
+ // Unified Public API
305
+ // =========================================
306
+
307
+ /**
308
+ * Initialize transport - auto-selects best option
309
+ */
310
+ const init = async () => {
311
+ if (!isEnabled()) return false;
312
+ if (_webrtcSwarm || _connectionState === 'connected') return true;
313
+ const version = ++initVersion;
314
+ _peerId = deps.peerId || generateId('peer');
315
+ _roomId = getRoomId();
316
+ for (const transport of policy.webrtc.transportOrder) {
317
+ if (transport === 'broadcast' && typeof BroadcastChannel === 'function') {
318
+ initBroadcastChannel();
319
+ return true;
320
+ }
321
+ if (transport === 'webrtc' && (policy.webrtc.transportOrder.length === 1 || await checkSignalingServer())) {
322
+ if (version !== initVersion) return false;
323
+ if (await initWebRTC(version)) return version === initVersion;
324
+ }
325
+ if (version !== initVersion) return false;
326
+ }
327
+ return false;
328
+ };
329
+
330
+ /**
331
+ * Send message to specific peer
332
+ */
333
+ const sendToPeer = (remotePeerId, type, payload) => {
334
+ if (_transport === 'webrtc' && _webrtcSwarm) {
335
+ return _webrtcSwarm.sendToPeer(remotePeerId, type, payload);
336
+ }
337
+
338
+ if (_transport === 'broadcast' && _broadcastChannel) {
339
+ // BroadcastChannel is inherently broadcast, but we can add target filtering
340
+ const envelope = wrapMessage(type, payload);
341
+ envelope.targetPeer = remotePeerId; // Receiver can filter
342
+ _broadcastChannel.postMessage(envelope);
343
+ return true;
344
+ }
345
+
346
+ return false;
347
+ };
348
+
349
+ /**
350
+ * Broadcast message to all peers
351
+ */
352
+ const broadcast = (type, payload) => {
353
+ if (_transport === 'webrtc' && _webrtcSwarm) {
354
+ return _webrtcSwarm.broadcast(type, payload);
355
+ }
356
+
357
+ if (_transport === 'broadcast' && _broadcastChannel) {
358
+ const envelope = wrapMessage(type, payload);
359
+ _broadcastChannel.postMessage(envelope);
360
+ return _peers.size; // Approximate count
361
+ }
362
+
363
+ return 0;
364
+ };
365
+
366
+ /**
367
+ * Register message handler
368
+ */
369
+ const onMessage = (type, handler) => {
370
+ _messageHandlers.set(type, handler);
371
+
372
+ // Also register with WebRTCSwarm if using it
373
+ if (_transport === 'webrtc' && _webrtcSwarm) {
374
+ _webrtcSwarm.onMessage(type, handler);
375
+ }
376
+ };
377
+
378
+ /**
379
+ * Get connected peers
380
+ */
381
+ const getConnectedPeers = () => {
382
+ if (_transport === 'webrtc' && _webrtcSwarm) {
383
+ return _webrtcSwarm.getConnectedPeers();
384
+ }
385
+
386
+ return Array.from(_peers.entries()).map(([id, peer]) => ({
387
+ id,
388
+ metadata: peer.metadata,
389
+ lastSeen: peer.lastSeen
390
+ }));
391
+ };
392
+
393
+ /**
394
+ * Get connection state
395
+ */
396
+ const getConnectionState = () => _transport === 'webrtc' && _webrtcSwarm
397
+ ? _webrtcSwarm.getConnectionState() : _connectionState;
398
+
399
+ /**
400
+ * Get transport type
401
+ */
402
+ const getTransportType = () => _transport;
403
+
404
+ /**
405
+ * Get logical clock
406
+ */
407
+ const getClock = () => _logicalClock;
408
+
409
+ /**
410
+ * Disconnect and cleanup
411
+ */
412
+ const disconnect = () => {
413
+ initVersion += 1;
414
+ cancelProbe?.();
415
+ if (_announceTimer) {
416
+ clearInterval(_announceTimer);
417
+ _announceTimer = null;
418
+ }
419
+
420
+ if (_cleanupTimer) {
421
+ clearInterval(_cleanupTimer);
422
+ _cleanupTimer = null;
423
+ }
424
+
425
+ if (_broadcastChannel) {
426
+ broadcastRaw({ type: 'peer-leave', peerId: _peerId });
427
+ _broadcastChannel.close();
428
+ _broadcastChannel = null;
429
+ }
430
+
431
+ if (_webrtcSwarm) {
432
+ _webrtcSwarm.disconnect();
433
+ _webrtcSwarm = null;
434
+ }
435
+
436
+ _peers.clear();
437
+ _connectionState = 'disconnected';
438
+ _transport = null;
439
+
440
+ EventBus.emit('swarm:state-change', { state: _connectionState });
441
+ };
442
+
443
+ /**
444
+ * Get stats
445
+ */
446
+ const getStats = () => {
447
+ if (_transport === 'webrtc' && _webrtcSwarm) {
448
+ return {
449
+ ..._webrtcSwarm.getStats(),
450
+ transport: 'webrtc'
451
+ };
452
+ }
453
+
454
+ return {
455
+ peerId: _peerId,
456
+ roomId: _roomId,
457
+ transport: _transport,
458
+ connectionState: getConnectionState(),
459
+ connectedPeers: _peers.size,
460
+ clock: _logicalClock
461
+ };
462
+ };
463
+
464
+ // Register default handlers
465
+ onMessage('ping', (peerId, payload) => {
466
+ sendToPeer(peerId, 'pong', { ts: payload.ts, received: Date.now() });
467
+ });
468
+
469
+ return {
470
+ init,
471
+ disconnect,
472
+ sendToPeer,
473
+ broadcast,
474
+ onMessage,
475
+ getConnectionState,
476
+ getConnectedPeers,
477
+ observePeerAuthentication: (peerId, attemptId) => _webrtcSwarm?.observePeerAuthentication?.(peerId, attemptId),
478
+ getPeerStates: () => _transport === 'webrtc' && _webrtcSwarm
479
+ ? _webrtcSwarm.getPeerStates?.() || [] : getConnectedPeers().map(peer => ({ id: peer.id, phase: 'connected', transport: 'broadcast', updatedAt: peer.lastSeen || 0, iceState: null, channelState: null })),
480
+ onDataChannel: (label, handler) => {
481
+ if (!_webrtcSwarm) throw new Error('WebRTC transport is not initialized');
482
+ return _webrtcSwarm.onDataChannel(label, handler);
483
+ },
484
+ openDataChannel: (peerId, label) => {
485
+ if (!_webrtcSwarm) throw new Error('WebRTC transport is not initialized');
486
+ return _webrtcSwarm.openDataChannel(peerId, label);
487
+ },
488
+ getIntroductions: () => _webrtcSwarm?.getIntroductions?.() || null,
489
+ getPeerBinding: peerId => _webrtcSwarm?.getPeerBinding?.(peerId) || null,
490
+ getTransportType,
491
+ getStats,
492
+ getClock,
493
+ tick,
494
+ _getPeerId: () => _peerId,
495
+ _getSessionId: () => _roomId
496
+ };
497
+ }
498
+ };
499
+
500
+ export default SwarmTransport;
501
+
502
+ export function createSwarmTransport(options) { return SwarmTransport.factory(options); }
@@ -0,0 +1,5 @@
1
+ export const fingerprint = description => {
2
+ const values = [...new Set((description?.sdp || '').split(/\r?\n/)
3
+ .filter(line => /^a=fingerprint:sha-256 /i.test(line)).map(line => line.slice(14).trim().toLowerCase()))];
4
+ return values.length === 1 && /^sha-256 (?:[a-f0-9]{2}:){31}[a-f0-9]{2}$/.test(values[0]) ? values[0] : null;
5
+ };