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.
- package/apps/poolday/config/chat-files.json +27 -0
- package/apps/poolday/config/partition-generation.js +21 -0
- package/apps/poolday/config/partition-policy.json +74 -0
- package/apps/poolday/config/partition-releases.json +3 -0
- package/apps/poolday/config/work-profile.json +122 -0
- package/apps/poolday/host/chat-view.js +47 -0
- package/apps/poolday/host/earning/client.js +199 -0
- package/apps/poolday/host/earning/contribution.js +168 -0
- package/apps/poolday/host/partition-release.js +33 -0
- package/apps/poolday/host/work-partition-artifact-source.js +37 -0
- package/apps/poolday/host/work-partition-capsule.js +39 -0
- package/apps/poolday/host/work-partition-plan.js +167 -0
- package/apps/poolday/host/work-partition-tokenizer-guard.js +21 -0
- package/apps/poolday/infrastructure/model-storage-lock.js +22 -0
- package/apps/poolday/infrastructure/pack-release-storage.js +52 -0
- package/apps/poolday/pool/pack-operation.js +161 -0
- package/apps/poolday/pool/pack-release-policy.js +44 -0
- package/apps/poolday/pool/peer-pack-data-channel.js +217 -0
- package/apps/poolday/providers/work-adapter.js +67 -0
- package/apps/poolday/providers/work-device.js +33 -0
- package/apps/poolday/providers/work-generation-completion.js +55 -0
- package/apps/poolday/providers/work-provider.js +209 -0
- package/apps/poolday/providers/work-resident-provider.js +113 -0
- package/deploy/artifacts/doppler-gpu-0.7.1.tgz +0 -0
- package/docs/fleet-service.md +214 -0
- package/docs/native-participant.md +542 -0
- package/native-delivery.json +1174 -0
- package/package.json +29 -4
- package/packages/reploid/src/artifacts/binary.js +25 -0
- package/packages/reploid/src/artifacts/canonical-json.js +23 -0
- package/packages/reploid/src/artifacts/custody/exchange.js +402 -0
- package/packages/reploid/src/artifacts/custody/piece-acquisition.js +70 -0
- package/packages/reploid/src/artifacts/custody/runtime.js +404 -0
- package/packages/reploid/src/artifacts/custody/supply-budget.js +19 -0
- package/packages/reploid/src/artifacts/custody-certificate.js +123 -0
- package/packages/reploid/src/artifacts/delivery-attestation.js +64 -0
- package/packages/reploid/src/artifacts/generation-terminal.js +36 -0
- package/packages/reploid/src/artifacts/identity.js +91 -0
- package/packages/reploid/src/artifacts/participant-proofs.js +159 -0
- package/packages/reploid/src/artifacts/receipt.js +131 -0
- package/packages/reploid/src/artifacts/signatures.js +94 -0
- package/packages/reploid/src/artifacts/whole-model-receipt.js +42 -0
- package/packages/reploid/src/cancellation.js +44 -0
- package/packages/reploid/src/config/defaults.json +110 -0
- package/packages/reploid/src/config/index.js +209 -0
- package/packages/reploid/src/config/request-overrides.json +9 -0
- package/packages/reploid/src/config/transport-defaults.json +7 -0
- package/packages/reploid/src/hashing.js +30 -0
- package/packages/reploid/src/measurement/capacity-utilization.js +321 -0
- package/packages/reploid/src/measurement/index.js +5 -0
- package/packages/reploid/src/measurement/readiness-records.js +310 -0
- package/packages/reploid/src/mesh/computer-identity.js +50 -0
- package/packages/reploid/src/mesh/computer-identity.policy.json +8 -0
- package/packages/reploid/src/mesh/contribution.js +141 -0
- package/packages/reploid/src/mesh/generation-recovery.js +58 -0
- package/packages/reploid/src/mesh/gpu-signature-shader.js +27 -0
- package/packages/reploid/src/mesh/gpu-signature.js +136 -0
- package/packages/reploid/src/mesh/gpu-signature.policy.json +11 -0
- package/packages/reploid/src/mesh/index.js +66 -0
- package/packages/reploid/src/mesh/partitions/automatic-ordered-partitions.js +540 -0
- package/packages/reploid/src/mesh/partitions/automatic-partitions.js +298 -0
- package/packages/reploid/src/mesh/partitions/partition-attempt-evidence.js +282 -0
- package/packages/reploid/src/mesh/partitions/partition-attempt-queue-policy.json +6 -0
- package/packages/reploid/src/mesh/partitions/partition-attempt-queue.js +112 -0
- package/packages/reploid/src/mesh/partitions/partition-canonical-contract.js +167 -0
- package/packages/reploid/src/mesh/partitions/partition-canonical-execution.js +168 -0
- package/packages/reploid/src/mesh/partitions/partition-chat-canonical.js +49 -0
- package/packages/reploid/src/mesh/partitions/partition-chat.js +125 -0
- package/packages/reploid/src/mesh/partitions/partition-contract.js +192 -0
- package/packages/reploid/src/mesh/partitions/partition-device-admission.js +96 -0
- package/packages/reploid/src/mesh/partitions/partition-discovery.js +96 -0
- package/packages/reploid/src/mesh/partitions/partition-entry-publication.js +183 -0
- package/packages/reploid/src/mesh/partitions/partition-entry.js +434 -0
- package/packages/reploid/src/mesh/partitions/partition-generation-request.js +33 -0
- package/packages/reploid/src/mesh/partitions/partition-grants.js +277 -0
- package/packages/reploid/src/mesh/partitions/partition-link.js +103 -0
- package/packages/reploid/src/mesh/partitions/partition-network.js +109 -0
- package/packages/reploid/src/mesh/partitions/partition-ordered-chat.js +290 -0
- package/packages/reploid/src/mesh/partitions/partition-ordered-peer.js +415 -0
- package/packages/reploid/src/mesh/partitions/partition-ordered-publication.js +100 -0
- package/packages/reploid/src/mesh/partitions/partition-peer.js +314 -0
- package/packages/reploid/src/mesh/partitions/partition-placement.js +143 -0
- package/packages/reploid/src/mesh/partitions/partition-preparation-progress.js +25 -0
- package/packages/reploid/src/mesh/partitions/partition-receiver-policy.json +7 -0
- package/packages/reploid/src/mesh/partitions/partition-reservations.js +75 -0
- package/packages/reploid/src/mesh/partitions/partition-route-envelope-policy.json +16 -0
- package/packages/reploid/src/mesh/partitions/partition-route-envelope.js +74 -0
- package/packages/reploid/src/mesh/partitions/partition-route-execution.js +409 -0
- package/packages/reploid/src/mesh/partitions/partition-route-network.js +179 -0
- package/packages/reploid/src/mesh/partitions/partition-route-ranking.js +268 -0
- package/packages/reploid/src/mesh/partitions/partition-runner.js +319 -0
- package/packages/reploid/src/mesh/partitions/partition-stage-assignment.js +109 -0
- package/packages/reploid/src/mesh/partitions/partition-stage-commit.js +103 -0
- package/packages/reploid/src/mesh/partitions/partition-step-receiver.js +291 -0
- package/packages/reploid/src/mesh/partitions/partition-summary.js +122 -0
- package/packages/reploid/src/mesh/partitions/preparation-owner.js +40 -0
- package/packages/reploid/src/mesh/partitions/resident-error-codes.json +10 -0
- package/packages/reploid/src/mesh/partitions/resident-partition.js +333 -0
- package/packages/reploid/src/mesh/partitions/resident-route.js +46 -0
- package/packages/reploid/src/mesh/partitions/signed-resident-factory.js +59 -0
- package/packages/reploid/src/mesh/peer-generation.js +896 -0
- package/packages/reploid/src/mesh/peer-identity.js +87 -0
- package/packages/reploid/src/mesh/placement-beliefs.js +107 -0
- package/packages/reploid/src/mesh/preparation-progress.js +46 -0
- package/packages/reploid/src/mesh/provider-hardware.js +11 -0
- package/packages/reploid/src/mesh/remote-generation-requests.js +53 -0
- package/packages/reploid/src/mesh/swarm-coordination.js +140 -0
- package/packages/reploid/src/mesh/web-access-network.js +112 -0
- package/packages/reploid/src/mesh/web-access-policy.json +17 -0
- package/packages/reploid/src/retrieval/citations.js +156 -0
- package/packages/reploid/src/retrieval/claim-support.js +113 -0
- package/packages/reploid/src/retrieval/costs.js +101 -0
- package/packages/reploid/src/retrieval/fixed-provider.js +34 -0
- package/packages/reploid/src/retrieval/index.js +20 -0
- package/packages/reploid/src/retrieval/lexical-support.js +27 -0
- package/packages/reploid/src/retrieval/model-reviewer.js +67 -0
- package/packages/reploid/src/retrieval/page-fetch.json +10 -0
- package/packages/reploid/src/retrieval/page-text.js +182 -0
- package/packages/reploid/src/retrieval/passage.js +37 -0
- package/packages/reploid/src/retrieval/policy.json +14 -0
- package/packages/reploid/src/retrieval/retrieval.js +23 -0
- package/packages/reploid/src/retrieval/review-queue.js +76 -0
- package/packages/reploid/src/retrieval/sources.js +263 -0
- package/packages/reploid/src/retrieval/support-review.js +155 -0
- package/packages/reploid/src/retrieval/text.js +21 -0
- package/packages/reploid/src/rules/contribution.rules.json +8 -0
- package/packages/reploid/src/rules/routing.rules.json +24 -0
- package/packages/reploid/src/transport/assignment.js +571 -0
- package/packages/reploid/src/transport/bounded-channel-writer.js +46 -0
- package/packages/reploid/src/transport/index.js +11 -0
- package/packages/reploid/src/transport/introduction-admission.js +24 -0
- package/packages/reploid/src/transport/introduction-bootstrap.js +112 -0
- package/packages/reploid/src/transport/introduction-policy.json +12 -0
- package/packages/reploid/src/transport/introduction-records.js +90 -0
- package/packages/reploid/src/transport/introductions.js +209 -0
- package/packages/reploid/src/transport/partition-data-channel.js +421 -0
- package/packages/reploid/src/transport/partition-errors.json +16 -0
- package/packages/reploid/src/transport/retry-policy.js +36 -0
- package/packages/reploid/src/transport/room.js +502 -0
- package/packages/reploid/src/transport/sdp-fingerprint.js +5 -0
- package/packages/reploid/src/transport/signaling.js +571 -0
- package/packages/reploid/src/transport/swarm.js +1247 -0
- package/packages/reploid/src/transport/tool-offer-channel.js +137 -0
- package/server/browser-roots.js +42 -0
- package/server/browser-web-worker.js +94 -0
- package/server/chat-completions.js +45 -0
- package/server/fleet/browser-bridge.js +145 -0
- package/server/fleet/contract.js +42 -0
- package/server/fleet/credit-evidence.js +103 -0
- package/server/fleet/credit-recovery.js +175 -0
- package/server/fleet/credits.js +178 -0
- package/server/fleet/execution-admission.js +32 -0
- package/server/fleet/native-host.js +817 -0
- package/server/fleet/placement.js +132 -0
- package/server/fleet/requester.js +346 -0
- package/server/fleet/service.js +311 -0
- package/server/fleet/terminal-policy.js +83 -0
- package/server/fleet/text-api.js +94 -0
- package/server/hashing.js +35 -0
- package/server/http-request-owner.js +49 -0
- package/server/messages.js +57 -0
- package/server/native-canonical-epochs.js +90 -0
- package/server/native-change-log.js +40 -0
- package/server/native-earning.js +203 -0
- package/server/native-generation-journal.js +46 -0
- package/server/native-model-config.js +57 -0
- package/server/native-model-files.js +382 -0
- package/server/native-model-origin.js +54 -0
- package/server/native-model-origins.json +12 -0
- package/server/native-node.js +85 -0
- package/server/native-owner/controls.js +162 -0
- package/server/native-owner/monitor.js +251 -0
- package/server/native-owner/policy.js +273 -0
- package/server/native-owner/rate.js +92 -0
- package/server/native-participant.js +866 -0
- package/server/native-readiness-log.js +30 -0
- package/server/native-release-storage.js +64 -0
- package/server/native-shader-cache.js +369 -0
- package/server/native-supply-budget.js +66 -0
- package/server/native-whole-earning.js +67 -0
- package/server/native-whole-model-fill.js +182 -0
- package/server/native-whole-model.js +130 -0
- package/server/public-web-worker.js +66 -0
- package/server/responses.js +92 -0
- package/server/search/errors.js +47 -0
- package/server/search/page-fetch.js +185 -0
- package/server/text-api.js +83 -0
- package/server/text-recovery.js +45 -0
- package/server/text-request-options.js +23 -0
- package/server/text-requester.js +231 -0
- package/server/token-auth.js +9 -0
- package/server/whole-model-requester.js +99 -0
- package/shared/config/chat-models.json +4685 -0
- package/shared/config/doppler-execution-contracts.js +32 -0
- package/shared/config/doppler-execution-contracts.json +43 -0
- package/shared/config/doppler-generation-contract.js +223 -0
- package/shared/config/doppler-local-models.js +79 -0
- package/shared/config/doppler-package.json +7 -0
- package/shared/config/model-manifests/a0dc69e917133a82780823d17fc771c0f6b882d7c86c571deeaef45be2ff108b.json +6434 -0
- package/shared/config/model-manifests/e985e3cdad60d0cb57d84b436161e66ab5b3f6589a168de6f00b4610e5f1fb59.json +16534 -0
- package/shared/config/model-manifests/fa6eb7bbc3ba63571f8cede780b877d9272bde948697af93dd3d33837cb46137.json +7175 -0
- package/shared/config/model-pieces/qwen-0-8b-pieces.json +1506 -0
- package/shared/config/model-pieces/qwen-2b-pieces.json +4800 -0
- package/shared/config/model-pieces/qwen35b-q4_0-pieces.json +28880 -0
- package/shared/config/reploid-library.json +49 -0
- package/shared/config/swarm-bootstrap.json +21 -0
- package/shared/infrastructure/doppler-runtime-service.js +261 -0
- package/shared/pool/executable-pack.js +87 -0
- package/shared/pool/pack-operation-adapters.js +124 -0
- package/shared/pool/pack-operation-policy.js +75 -0
- package/shared/pool/pool-config.json +2280 -0
- package/shared/vendor/reploid/adapters/doppler-session.js +372 -0
- package/shared/vendor/reploid/artifacts/binary.js +25 -0
- package/shared/vendor/reploid/artifacts/canonical-json.js +23 -0
- package/shared/vendor/reploid/artifacts/delivery-attestation.js +64 -0
- package/shared/vendor/reploid/artifacts/identity.js +91 -0
- package/shared/vendor/reploid/artifacts/manifest-identity.js +17 -0
- package/shared/vendor/reploid/artifacts/participant-proofs.js +159 -0
- package/shared/vendor/reploid/artifacts/signatures.js +94 -0
- package/shared/vendor/reploid/config/defaults.json +110 -0
- package/shared/vendor/reploid/config/index.js +209 -0
- package/shared/vendor/reploid/config/request-overrides.json +9 -0
- package/shared/vendor/reploid/hashing.js +30 -0
- package/shared/vendor/reploid/mesh/generation-context-cache.js +97 -0
- package/shared/vendor/reploid/mesh/partitions/partition-attempt-evidence.js +282 -0
- package/shared/vendor/reploid/mesh/partitions/partition-contract.js +192 -0
- package/shared/vendor/reploid/mesh/partitions/partition-receiver-policy.json +7 -0
- package/shared/vendor/reploid/mesh/partitions/partition-route-ranking.js +268 -0
- package/shared/vendor/reploid/mesh/preparation-progress.js +46 -0
- package/shared/vendor/reploid/mesh/provider-hardware.js +11 -0
- package/shared/vendor/reploid/rules/routing.rules.json +24 -0
- package/shared/vendor/reploid/transport/partition-errors.json +16 -0
- 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
|
+
};
|