@carverjs/multiplayer 0.0.1 → 0.0.3

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 (43) hide show
  1. package/README.md +154 -0
  2. package/dist/InputBuffer-J6XT_Tt0.d.mts +61 -0
  3. package/dist/InputBuffer-V7XfHbc6.d.ts +61 -0
  4. package/dist/{NetworkManager-nvVAOr1O.d.ts → NetworkManager-D-DxFgdM.d.mts} +66 -14
  5. package/dist/{NetworkManager-DrKM2tEx.d.mts → NetworkManager-DH9uGVMg.d.ts} +66 -14
  6. package/dist/{chunk-UD6FDZMX.mjs → chunk-GOTAQDBJ.mjs} +47 -4
  7. package/dist/chunk-GOTAQDBJ.mjs.map +1 -0
  8. package/dist/{chunk-3KT73N2S.mjs → chunk-LPNEP2VH.mjs} +0 -0
  9. package/dist/chunk-LPNEP2VH.mjs.map +1 -0
  10. package/dist/{chunk-EO3YNPRQ.mjs → chunk-Q25TJEY4.mjs} +494 -204
  11. package/dist/chunk-Q25TJEY4.mjs.map +1 -0
  12. package/dist/{firebase-CPu87KA0.d.ts → firebase-B5MgLlHk.d.ts} +6 -1
  13. package/dist/{firebase-PE6MxGdJ.d.mts → firebase-GrbVrNgs.d.mts} +6 -1
  14. package/dist/index.d.mts +27 -6
  15. package/dist/index.d.ts +27 -6
  16. package/dist/index.js +821 -258
  17. package/dist/index.js.map +1 -1
  18. package/dist/index.mjs +172 -37
  19. package/dist/index.mjs.map +1 -1
  20. package/dist/strategy.d.mts +2 -2
  21. package/dist/strategy.d.ts +2 -2
  22. package/dist/strategy.js +46 -3
  23. package/dist/strategy.js.map +1 -1
  24. package/dist/strategy.mjs +1 -1
  25. package/dist/sync.d.mts +134 -50
  26. package/dist/sync.d.ts +134 -50
  27. package/dist/sync.js +499 -205
  28. package/dist/sync.js.map +1 -1
  29. package/dist/sync.mjs +15 -3
  30. package/dist/transport.d.mts +0 -0
  31. package/dist/transport.d.ts +0 -0
  32. package/dist/transport.js +0 -0
  33. package/dist/transport.js.map +1 -1
  34. package/dist/transport.mjs +2 -2
  35. package/dist/{types-5LHBOW08.d.mts → types-hNfCIBzj.d.mts} +7 -0
  36. package/dist/{types-5LHBOW08.d.ts → types-hNfCIBzj.d.ts} +7 -0
  37. package/dist/types.d.mts +2 -2
  38. package/dist/types.d.ts +2 -2
  39. package/dist/types.js.map +1 -1
  40. package/package.json +26 -5
  41. package/dist/chunk-3KT73N2S.mjs.map +0 -1
  42. package/dist/chunk-EO3YNPRQ.mjs.map +0 -1
  43. package/dist/chunk-UD6FDZMX.mjs.map +0 -1
package/package.json CHANGED
@@ -1,7 +1,25 @@
1
1
  {
2
2
  "name": "@carverjs/multiplayer",
3
- "version": "0.0.1",
4
- "description": "Multiplayer networking for CarverJS games",
3
+ "version": "0.0.3",
4
+ "description": "Serverless P2P multiplayer for CarverJS games — WebRTC mesh, lobbies, host authority, and state sync over MQTT or Firebase signaling.",
5
+ "homepage": "https://docs.carverjs.dev",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/MoneyTales/carverjs.git",
9
+ "directory": "packages/multiplayer"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/MoneyTales/carverjs/issues"
13
+ },
14
+ "keywords": [
15
+ "multiplayer",
16
+ "webrtc",
17
+ "p2p",
18
+ "netcode",
19
+ "gamedev",
20
+ "threejs",
21
+ "react"
22
+ ],
5
23
  "exports": {
6
24
  ".": {
7
25
  "types": "./dist/index.d.ts",
@@ -40,7 +58,9 @@
40
58
  "files": ["dist"],
41
59
  "scripts": {
42
60
  "build": "tsup",
43
- "dev": "tsup --watch"
61
+ "dev": "tsup --watch",
62
+ "test": "vitest run",
63
+ "test:watch": "vitest"
44
64
  },
45
65
  "keywords": [],
46
66
  "author": "Het Dave",
@@ -50,7 +70,7 @@
50
70
  "mqtt": "^5.10.0"
51
71
  },
52
72
  "peerDependencies": {
53
- "@carverjs/core": "workspace:*",
73
+ "@carverjs/core": ">=0.0.1",
54
74
  "@react-three/fiber": ">=9.0.0",
55
75
  "react": ">=18.0.0",
56
76
  "react-dom": ">=18.0.0",
@@ -68,6 +88,7 @@
68
88
  "@types/react-dom": "^19.2.3",
69
89
  "firebase": "^11.0.0",
70
90
  "tsup": "^8.5.1",
71
- "typescript": "^5.9.3"
91
+ "typescript": "^5.9.3",
92
+ "vitest": "^3.2.0"
72
93
  }
73
94
  }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/transport/webrtc/ice.ts","../src/transport/webrtc/peer.ts","../src/transport/webrtc/WebRTCTransport.ts"],"sourcesContent":["/** Default STUN servers (free, public) */\nconst DEFAULT_STUN_SERVERS: RTCIceServer[] = [\n { urls: 'stun:stun.l.google.com:19302' },\n { urls: 'stun:stun1.l.google.com:19302' },\n { urls: 'stun:stun2.l.google.com:19302' },\n { urls: 'stun:stun.cloudflare.com:3478' },\n];\n\n/**\n * Build RTCConfiguration from user-provided ICE servers.\n *\n * If the user provides `iceServers`, those are used as-is (STUN + TURN).\n * Otherwise, default public STUN servers are used.\n *\n * TURN servers should be included in the `iceServers` array by the user:\n * ```ts\n * iceServers: [\n * { urls: 'stun:stun.cloudflare.com:3478' },\n * { urls: 'turn:turn.cloudflare.com:3478', username: '...', credential: '...' },\n * ]\n * ```\n */\nexport function buildICEConfig(options?: {\n iceServers?: RTCIceServer[];\n iceTransportPolicy?: RTCIceTransportPolicy;\n}): RTCConfiguration {\n const servers: RTCIceServer[] =\n options?.iceServers && options.iceServers.length > 0\n ? options.iceServers\n : DEFAULT_STUN_SERVERS;\n\n return {\n iceServers: servers,\n iceCandidatePoolSize: 10,\n iceTransportPolicy: options?.iceTransportPolicy ?? 'all',\n };\n}\n","import type { ChannelOptions } from \"../../types\";\nimport type { PeerState } from \"../types\";\n\nexport interface PeerConnectionEvents {\n onStateChange: (state: PeerState) => void;\n onDataChannel: (channel: RTCDataChannel) => void;\n onIceCandidate: (candidate: RTCIceCandidate) => void;\n}\n\n/**\n * Manages a single RTCPeerConnection to one remote peer.\n *\n * ICE candidates that arrive before the remote description is set are\n * buffered and flushed automatically once setRemoteDescription completes.\n * This is critical for Firebase/MQTT signaling where offer, answer, and\n * candidates can arrive nearly simultaneously.\n */\nexport class PeerConnection {\n readonly peerId: string;\n private _connection: RTCPeerConnection;\n private _channels = new Map<string, RTCDataChannel>();\n private _events: PeerConnectionEvents;\n private _state: PeerState = 'connecting';\n private _remoteDescriptionSet = false;\n private _pendingCandidates: RTCIceCandidateInit[] = [];\n\n constructor(\n peerId: string,\n config: RTCConfiguration,\n events: PeerConnectionEvents,\n ) {\n this.peerId = peerId;\n this._events = events;\n this._connection = new RTCPeerConnection(config);\n\n this._connection.onicecandidate = (e) => {\n if (e.candidate) {\n this._events.onIceCandidate(e.candidate);\n }\n };\n\n this._connection.oniceconnectionstatechange = () => {\n this._updateState();\n };\n\n this._connection.onconnectionstatechange = () => {\n this._updateState();\n };\n\n this._connection.ondatachannel = (e) => {\n const channel = e.channel;\n this._channels.set(channel.label, channel);\n this._events.onDataChannel(channel);\n };\n }\n\n get state(): PeerState {\n return this._state;\n }\n\n get connection(): RTCPeerConnection {\n return this._connection;\n }\n\n private _updateState(): void {\n const iceState = this._connection.iceConnectionState;\n const connState = this._connection.connectionState;\n\n let newState: PeerState;\n if (connState === 'connected' || iceState === 'connected') {\n newState = 'connected';\n } else if (connState === 'failed' || iceState === 'failed') {\n newState = 'failed';\n } else if (connState === 'closed' || iceState === 'closed' || iceState === 'disconnected') {\n newState = 'disconnected';\n } else {\n newState = 'connecting';\n }\n\n if (newState !== this._state) {\n this._state = newState;\n this._events.onStateChange(newState);\n }\n }\n\n async createOffer(): Promise<RTCSessionDescriptionInit> {\n const offer = await this._connection.createOffer();\n await this._connection.setLocalDescription(offer);\n return offer;\n }\n\n async handleOffer(offer: RTCSessionDescriptionInit): Promise<RTCSessionDescriptionInit> {\n await this._connection.setRemoteDescription(new RTCSessionDescription(offer));\n this._remoteDescriptionSet = true;\n await this._flushPendingCandidates();\n const answer = await this._connection.createAnswer();\n await this._connection.setLocalDescription(answer);\n return answer;\n }\n\n async handleAnswer(answer: RTCSessionDescriptionInit): Promise<void> {\n await this._connection.setRemoteDescription(new RTCSessionDescription(answer));\n this._remoteDescriptionSet = true;\n await this._flushPendingCandidates();\n }\n\n async addIceCandidate(candidate: RTCIceCandidateInit): Promise<void> {\n if (!this._remoteDescriptionSet) {\n // Buffer until remote description is set -- prevents silent drops\n this._pendingCandidates.push(candidate);\n return;\n }\n try {\n await this._connection.addIceCandidate(new RTCIceCandidate(candidate));\n } catch {\n // Ignore ICE candidate errors (can happen during race conditions)\n }\n }\n\n private async _flushPendingCandidates(): Promise<void> {\n const candidates = this._pendingCandidates;\n this._pendingCandidates = [];\n for (const c of candidates) {\n try {\n await this._connection.addIceCandidate(new RTCIceCandidate(c));\n } catch {\n // Ignore errors on individual candidates\n }\n }\n }\n\n createDataChannel(name: string, options?: ChannelOptions): RTCDataChannel {\n // If a channel with this label already exists (e.g. received via\n // ondatachannel from the remote peer), reuse it instead of creating\n // a duplicate that fragments send/receive across two channels.\n const existing = this._channels.get(name);\n if (existing && existing.readyState !== 'closed') {\n return existing;\n }\n\n const dcOptions: RTCDataChannelInit = {};\n if (options?.reliable === false) {\n dcOptions.ordered = options?.ordered ?? false;\n dcOptions.maxRetransmits = options?.maxRetransmits ?? 0;\n } else {\n dcOptions.ordered = options?.ordered ?? true;\n }\n const channel = this._connection.createDataChannel(name, dcOptions);\n this._channels.set(name, channel);\n return channel;\n }\n\n getDataChannel(name: string): RTCDataChannel | undefined {\n return this._channels.get(name);\n }\n\n close(): void {\n for (const channel of this._channels.values()) {\n try { channel.close(); } catch { /* ignore */ }\n }\n this._channels.clear();\n this._pendingCandidates = [];\n this._remoteDescriptionSet = false;\n try { this._connection.close(); } catch { /* ignore */ }\n this._state = 'disconnected';\n }\n}\n","import type {\n CarverTransport,\n CarverChannel,\n ChannelOptions,\n TransportConfig,\n Player,\n Room,\n RoomState,\n} from \"../../types\";\nimport type { TransportCallbacks, RateLimitConfig } from \"../types\";\nimport type { SignalingStrategy, PeerMetadata } from \"../strategy/types\";\nimport { buildICEConfig } from \"./ice\";\nimport { PeerConnection } from \"./peer\";\n\nconst ROOM_CONTROL_CHANNEL = 'carver:room-control';\n\ninterface ChannelState<T> {\n name: string;\n options: ChannelOptions;\n receivers: ((data: T, peerId: string) => void)[];\n}\n\n/** Room control messages exchanged over the room-control data channel */\ntype RoomControlMessage =\n | { type: 'player-updated'; player: Player }\n | { type: 'room-updated'; room: Partial<Room> }\n | { type: 'kick'; peerId: string; reason?: string }\n | { type: 'host-changed'; newHostId: string }\n | { type: 'request-ready'; ready: boolean }\n | { type: 'request-metadata'; metadata: Record<string, unknown> }\n | { type: 'request-room-metadata'; metadata: Record<string, unknown> }\n | { type: 'request-room-state'; state: RoomState }\n | { type: 'request-max-players'; maxPlayers: number }\n | { type: 'request-lock' }\n | { type: 'request-unlock' }\n | { type: 'request-transfer-host'; peerId: string }\n | { type: 'sync-state'; room: Room; players: Player[] };\n\n/** Deterministic host election: lowest peerId alphabetically */\nfunction electHost(peerIds: string[]): string {\n return [...peerIds].sort()[0];\n}\n\n/**\n * Implements CarverTransport using WebRTC data channels for game data\n * and a pluggable SignalingStrategy for peer discovery + SDP/ICE relay.\n *\n * No WebSocket server required. The strategy handles signaling through\n * MQTT brokers, Firebase RTDB, or any other network.\n */\nexport class WebRTCTransport implements CarverTransport {\n private _strategy: SignalingStrategy;\n private _peers = new Map<string, PeerConnection>();\n private _peerSet = new Set<string>();\n private _peerId: string;\n private _hostId = '';\n private _isHost = false;\n private _callbacks: TransportCallbacks = {\n onPeerJoin: [],\n onPeerLeave: [],\n onPeerUpdated: [],\n onHostChanged: [],\n };\n private _roomUpdatedCallbacks: ((room: Room) => void)[] = [];\n private _channels = new Map<string, ChannelState<any>>();\n private _iceConfig: RTCConfiguration;\n private _rateLimitConfig: RateLimitConfig = { maxMessagesPerSecond: 60, windowMs: 1000 };\n private _rateLimitCounters = new Map<string, { count: number; resetAt: number }>();\n private _connected = false;\n private _room: Room | null = null;\n private _playerMap = new Map<string, Player>();\n private _initialPeers: Player[] = [];\n private _strategyUnsubs: (() => void)[] = [];\n\n /**\n * @param strategy Shared SignalingStrategy instance (managed by MultiplayerProvider)\n * @param iceServers Optional ICE servers (STUN + TURN). Defaults to public STUN.\n * @param iceTransportPolicy 'all' (default) or 'relay' (force TURN only).\n */\n constructor(\n strategy: SignalingStrategy,\n iceServers?: RTCIceServer[],\n iceTransportPolicy?: RTCIceTransportPolicy,\n ) {\n this._strategy = strategy;\n this._peerId = strategy.selfId;\n this._iceConfig = buildICEConfig({ iceServers, iceTransportPolicy });\n }\n\n // ── CarverTransport getters ──\n\n get peerId(): string { return this._peerId; }\n get peers(): ReadonlySet<string> { return this._peerSet; }\n get hostId(): string { return this._hostId; }\n get isHost(): boolean { return this._isHost; }\n get room(): Room | undefined { return this._room ?? undefined; }\n get initialPlayers(): Player[] { return this._initialPeers; }\n\n // ── Event registration ──\n\n onPeerJoin(cb: (peerId: string) => void): void { this._callbacks.onPeerJoin.push(cb); }\n onPeerLeave(cb: (peerId: string) => void): void { this._callbacks.onPeerLeave.push(cb); }\n onPeerUpdated(cb: (player: Player) => void): void { this._callbacks.onPeerUpdated.push(cb); }\n onRoomUpdated(cb: (room: Room) => void): void { this._roomUpdatedCallbacks.push(cb); }\n onHostChanged(cb: (newHostId: string) => void): void { this._callbacks.onHostChanged.push(cb); }\n\n // ── Channel management ──\n\n createChannel<T>(name: string, options?: ChannelOptions): CarverChannel<T> {\n // Idempotent: return existing channel if already created\n const existing = this._channels.get(name);\n if (existing) {\n return {\n send: (data: T, target?: string | string[]) => this._sendOnChannel(name, data, target),\n onReceive: (cb: (data: T, peerId: string) => void) => { existing.receivers.push(cb); },\n close: () => { this._channels.delete(name); },\n };\n }\n\n const state: ChannelState<T> = {\n name,\n options: options ?? { reliable: true, ordered: true },\n receivers: [],\n };\n this._channels.set(name, state);\n\n // Create data channels on existing peers if already connected\n if (this._connected) {\n for (const peer of this._peers.values()) {\n this._createDataChannelOnPeer(peer, name, state.options);\n }\n }\n\n return {\n send: (data: T, target?: string | string[]) => this._sendOnChannel(name, data, target),\n onReceive: (cb: (data: T, peerId: string) => void) => { state.receivers.push(cb); },\n close: () => { this._channels.delete(name); },\n };\n }\n\n // ── Connect / Disconnect ──\n\n async connect(roomId: string, config?: TransportConfig): Promise<void> {\n // Override ICE config if user passed custom servers\n if (config?.iceServers) {\n this._iceConfig = buildICEConfig({\n iceServers: config.iceServers,\n iceTransportPolicy: config.iceTransportPolicy,\n });\n }\n\n // Pre-register ALL standard channels so the initiator includes them\n // in the initial WebRTC offer. Channels created after the peer connection\n // is established won't get a proper data channel on the remote side.\n this._setupRoomControlChannel();\n this._preRegisterChannel('carver:events', { reliable: true, ordered: true });\n this._preRegisterChannel('carver:snapshots', { reliable: false, ordered: false });\n this._preRegisterChannel('carver:acks', { reliable: true, ordered: true });\n this._preRegisterChannel('carver:inputs', { reliable: true, ordered: true });\n this._preRegisterChannel('carver:network-state', { reliable: true, ordered: true });\n\n // Bind strategy callbacks (store unsubs for cleanup)\n this._strategyUnsubs.push(\n this._strategy.onPeerDiscovered((peerId, meta) => {\n this._onStrategyPeerDiscovered(peerId, meta);\n }),\n );\n this._strategyUnsubs.push(\n this._strategy.onPeerLeft((peerId) => {\n this._removePeer(peerId);\n this._playerMap.delete(peerId);\n this._electAndSetHost();\n for (const cb of this._callbacks.onPeerLeave) cb(peerId);\n }),\n );\n this._strategyUnsubs.push(\n this._strategy.onSignal((fromPeerId, data) => {\n this._handleSignal(fromPeerId, data);\n }),\n );\n\n // Join room via strategy (publishes presence, subscribes to room)\n await this._strategy.joinRoom(roomId, {\n displayName: config?.displayName,\n ...(config?.playerMetadata ?? {}),\n });\n\n // Create self Player\n const selfPlayer: Player = {\n peerId: this._peerId,\n displayName: config?.displayName ?? `Player-${this._peerId.slice(0, 4)}`,\n isHost: false,\n isSelf: true,\n isReady: false,\n isConnected: true,\n metadata: config?.playerMetadata ?? {},\n latencyMs: 0,\n joinedAt: Date.now(),\n };\n this._playerMap.set(this._peerId, selfPlayer);\n\n // Elect host (may just be us if we're the first in the room)\n this._electAndSetHost();\n\n // Create initial Room object\n this._room = {\n id: roomId,\n name: roomId,\n hostId: this._hostId,\n playerCount: this._playerMap.size,\n maxPlayers: config?.maxPlayers ?? 8,\n isPrivate: false,\n metadata: {},\n createdAt: Date.now(),\n state: 'lobby',\n };\n\n this._initialPeers = Array.from(this._playerMap.values());\n this._connected = true;\n }\n\n disconnect(): void {\n this._connected = false;\n\n // Unsubscribe from strategy callbacks\n for (const unsub of this._strategyUnsubs) unsub();\n this._strategyUnsubs = [];\n\n // Close all peer connections\n for (const peer of this._peers.values()) peer.close();\n this._peers.clear();\n this._peerSet.clear();\n this._channels.clear();\n this._rateLimitCounters.clear();\n this._playerMap.clear();\n\n // Leave room via strategy (don't destroy -- provider manages lifecycle)\n this._strategy.leaveRoom().catch(() => {});\n\n this._hostId = '';\n this._isHost = false;\n this._room = null;\n }\n\n /** Expose strategy for lobby hooks */\n get strategy(): SignalingStrategy { return this._strategy; }\n\n // ── Channel pre-registration ──\n\n /**\n * Register a channel name and options without creating data channels yet.\n * When _connectToPeer runs, it iterates this._channels and creates data\n * channels for every registered name in the initial WebRTC offer.\n * Later, when EventSync/SnapshotSync call createChannel(), the idempotent\n * check returns the pre-registered entry and they just attach receivers.\n */\n private _preRegisterChannel(name: string, options: ChannelOptions): void {\n if (this._channels.has(name)) return;\n this._channels.set(name, { name, options, receivers: [] });\n }\n\n // ── Room management (over WebRTC data channels) ──\n\n setReady(ready: boolean): void {\n this._sendControlMessage({ type: 'request-ready', ready });\n }\n\n setMetadata(metadata: Record<string, unknown>): void {\n this._sendControlMessage({ type: 'request-metadata', metadata });\n }\n\n setRoomMetadata(metadata: Record<string, unknown>): void {\n if (!this._isHost) return;\n this._sendControlMessage({ type: 'request-room-metadata', metadata });\n }\n\n kick(peerId: string, reason?: string): void {\n if (!this._isHost) return;\n // Broadcast kick so the target peer and everyone else knows\n this._broadcastControlMessage({ type: 'kick', peerId, reason });\n }\n\n transferHost(peerId: string): void {\n if (!this._isHost) return;\n this._sendControlMessage({ type: 'request-transfer-host', peerId });\n }\n\n setRoomState(state: RoomState): void {\n if (!this._isHost) return;\n this._sendControlMessage({ type: 'request-room-state', state });\n }\n\n setMaxPlayers(n: number): void {\n if (!this._isHost) return;\n this._sendControlMessage({ type: 'request-max-players', maxPlayers: n });\n }\n\n lockRoom(): void {\n if (!this._isHost) return;\n this._sendControlMessage({ type: 'request-lock' });\n }\n\n unlockRoom(): void {\n if (!this._isHost) return;\n this._sendControlMessage({ type: 'request-unlock' });\n }\n\n /** No-op: lobby uses strategy.subscribeToLobby() directly */\n requestRoomList(): void {}\n\n // ── Private: Strategy callbacks ──\n\n private _onStrategyPeerDiscovered(peerId: string, meta: PeerMetadata): void {\n this._connectToPeer(peerId);\n this._peerSet.add(peerId);\n\n const player: Player = {\n peerId,\n displayName: (meta.displayName as string) ?? `Player-${peerId.slice(0, 4)}`,\n isHost: false,\n isSelf: false,\n isReady: false,\n isConnected: true,\n metadata: meta as Record<string, unknown>,\n latencyMs: 0,\n joinedAt: Date.now(),\n };\n this._playerMap.set(peerId, player);\n this._electAndSetHost();\n\n for (const cb of this._callbacks.onPeerJoin) cb(peerId);\n for (const cb of this._callbacks.onPeerUpdated) cb(player);\n }\n\n // ── Private: Room control channel ──\n\n private _setupRoomControlChannel(): void {\n const ch = this.createChannel<RoomControlMessage>(ROOM_CONTROL_CHANNEL, {\n reliable: true,\n ordered: true,\n });\n ch.onReceive((msg, peerId) => {\n this._handleControlMessage(msg, peerId);\n });\n }\n\n private _handleControlMessage(msg: RoomControlMessage, fromPeerId: string): void {\n switch (msg.type) {\n case 'player-updated': {\n this._playerMap.set(msg.player.peerId, msg.player);\n for (const cb of this._callbacks.onPeerUpdated) cb(msg.player);\n break;\n }\n case 'room-updated': {\n if (this._room) {\n Object.assign(this._room, msg.room);\n for (const cb of this._roomUpdatedCallbacks) cb(this._room);\n }\n break;\n }\n case 'kick': {\n if (msg.peerId === this._peerId) {\n // We were kicked\n this.disconnect();\n }\n break;\n }\n case 'host-changed': {\n this._hostId = msg.newHostId;\n this._isHost = msg.newHostId === this._peerId;\n for (const cb of this._callbacks.onHostChanged) cb(msg.newHostId);\n break;\n }\n case 'sync-state': {\n // Full state sync from host (sent to newly connected peers)\n this._room = msg.room;\n for (const p of msg.players) {\n this._playerMap.set(p.peerId, { ...p, isSelf: p.peerId === this._peerId });\n for (const cb of this._callbacks.onPeerUpdated) cb(p);\n }\n for (const cb of this._roomUpdatedCallbacks) cb(msg.room);\n break;\n }\n\n // Host processes requests from peers\n case 'request-ready': {\n if (!this._isHost) break;\n const p = this._playerMap.get(fromPeerId);\n if (p) {\n p.isReady = msg.ready;\n this._broadcastControlMessage({ type: 'player-updated', player: p });\n }\n break;\n }\n case 'request-metadata': {\n if (!this._isHost) break;\n const pm = this._playerMap.get(fromPeerId);\n if (pm) {\n pm.metadata = { ...pm.metadata, ...msg.metadata };\n this._broadcastControlMessage({ type: 'player-updated', player: pm });\n }\n break;\n }\n case 'request-room-metadata': {\n if (!this._isHost || !this._room) break;\n this._room.metadata = { ...this._room.metadata, ...msg.metadata };\n this._broadcastControlMessage({ type: 'room-updated', room: this._room });\n break;\n }\n case 'request-room-state': {\n if (!this._isHost || !this._room) break;\n this._room.state = msg.state;\n this._broadcastControlMessage({ type: 'room-updated', room: this._room });\n break;\n }\n case 'request-max-players': {\n if (!this._isHost || !this._room) break;\n this._room.maxPlayers = msg.maxPlayers;\n this._broadcastControlMessage({ type: 'room-updated', room: this._room });\n break;\n }\n case 'request-lock': {\n if (!this._isHost || !this._room) break;\n (this._room as any).locked = true;\n this._broadcastControlMessage({ type: 'room-updated', room: this._room });\n break;\n }\n case 'request-unlock': {\n if (!this._isHost || !this._room) break;\n (this._room as any).locked = false;\n this._broadcastControlMessage({ type: 'room-updated', room: this._room });\n break;\n }\n case 'request-transfer-host': {\n if (!this._isHost) break;\n this._hostId = msg.peerId;\n this._isHost = false;\n this._broadcastControlMessage({ type: 'host-changed', newHostId: msg.peerId });\n break;\n }\n }\n }\n\n private _sendControlMessage(msg: RoomControlMessage): void {\n if (this._isHost && msg.type.startsWith('request-')) {\n // Host processes locally and broadcasts result\n this._handleControlMessage(msg, this._peerId);\n return;\n }\n // Non-host: send to host\n if (this._hostId && this._hostId !== this._peerId) {\n this._sendOnChannel(ROOM_CONTROL_CHANNEL, msg, this._hostId);\n }\n }\n\n private _broadcastControlMessage(msg: RoomControlMessage): void {\n this._sendOnChannel(ROOM_CONTROL_CHANNEL, msg);\n // Handle locally too so host updates its own state\n this._handleControlMessage(msg, this._peerId);\n }\n\n // ── Private: Host election ──\n\n private _electAndSetHost(): void {\n const allIds = [this._peerId, ...this._peerSet];\n const newHostId = electHost(allIds);\n const changed = newHostId !== this._hostId;\n this._hostId = newHostId;\n this._isHost = newHostId === this._peerId;\n\n for (const [id, p] of this._playerMap) {\n p.isHost = id === newHostId;\n }\n if (this._room) {\n this._room.hostId = newHostId;\n this._room.playerCount = this._playerMap.size;\n }\n\n if (changed) {\n for (const cb of this._callbacks.onHostChanged) cb(newHostId);\n }\n }\n\n // ── Private: WebRTC peer management ──\n\n private _connectToPeer(peerId: string): void {\n if (this._peers.has(peerId)) return;\n\n const peer = new PeerConnection(peerId, this._iceConfig, {\n onStateChange: (state) => {\n if (state === 'connected' && this._isHost && this._room) {\n // Send full state sync to the new peer\n const syncMsg: RoomControlMessage = {\n type: 'sync-state',\n room: this._room,\n players: Array.from(this._playerMap.values()),\n };\n setTimeout(() => {\n this._sendOnChannel(ROOM_CONTROL_CHANNEL, syncMsg, peerId);\n }, 100);\n }\n if (state === 'failed' || state === 'disconnected') {\n this._removePeer(peerId);\n this._playerMap.delete(peerId);\n this._electAndSetHost();\n for (const cb of this._callbacks.onPeerLeave) cb(peerId);\n }\n },\n onDataChannel: (channel) => {\n this._setupDataChannelReceiver(channel, peerId);\n },\n onIceCandidate: (candidate) => {\n this._strategy.signal(peerId, { type: 'ice-candidate', candidate: candidate.toJSON() });\n },\n });\n\n this._peers.set(peerId, peer);\n this._peerSet.add(peerId);\n\n // Deterministic initiator: lower peerId creates the offer\n if (this._peerId < peerId) {\n for (const [name, state] of this._channels) {\n this._createDataChannelOnPeer(peer, name, state.options);\n }\n peer.createOffer().then((offer) => {\n this._strategy.signal(peerId, { type: 'offer', sdp: offer });\n });\n }\n }\n\n private async _handleSignal(peerId: string, data: unknown): Promise<void> {\n try {\n const signal = data as {\n type: string;\n sdp?: RTCSessionDescriptionInit;\n candidate?: RTCIceCandidateInit;\n };\n\n let peer = this._peers.get(peerId);\n\n if (signal.type === 'offer') {\n if (!peer) {\n this._connectToPeer(peerId);\n peer = this._peers.get(peerId)!;\n }\n const answer = await peer.handleOffer(signal.sdp!);\n this._strategy.signal(peerId, { type: 'answer', sdp: answer });\n } else if (signal.type === 'answer' && peer) {\n await peer.handleAnswer(signal.sdp!);\n } else if (signal.type === 'ice-candidate' && peer) {\n await peer.addIceCandidate(signal.candidate!);\n }\n } catch (err) {\n if (typeof console !== 'undefined') {\n console.error('[CarverJS] Signal handling failed:', err);\n }\n }\n }\n\n // ── Private: Data channel helpers ──\n\n private _createDataChannelOnPeer(peer: PeerConnection, name: string, options: ChannelOptions): void {\n const channel = peer.createDataChannel(name, options);\n this._setupDataChannelReceiver(channel, peer.peerId);\n }\n\n private _setupDataChannelReceiver(dataChannel: RTCDataChannel, peerId: string): void {\n const channelName = dataChannel.label;\n dataChannel.onmessage = (event) => {\n if (!this._checkRateLimit(peerId)) return;\n const channelState = this._channels.get(channelName);\n if (!channelState) return;\n try {\n const data =\n typeof event.data === 'string' ? JSON.parse(event.data) : event.data;\n for (const receiver of channelState.receivers) receiver(data, peerId);\n } catch {\n // Ignore malformed messages\n }\n };\n }\n\n private _sendOnChannel<T>(channelName: string, data: T, target?: string | string[]): void {\n const serialized =\n typeof data === 'object' &&\n data !== null &&\n !(data instanceof ArrayBuffer) &&\n !(data instanceof Uint8Array)\n ? JSON.stringify(data)\n : data;\n\n const targets = target\n ? Array.isArray(target) ? target : [target]\n : Array.from(this._peers.keys());\n\n for (const pid of targets) {\n const peer = this._peers.get(pid);\n const ch = peer?.getDataChannel(channelName);\n if (ch?.readyState === 'open') {\n try { ch.send(serialized as string); } catch { /* closed between check and send */ }\n }\n }\n }\n\n private _removePeer(peerId: string): void {\n const peer = this._peers.get(peerId);\n if (peer) { peer.close(); this._peers.delete(peerId); }\n this._peerSet.delete(peerId);\n this._rateLimitCounters.delete(peerId);\n }\n\n private _checkRateLimit(peerId: string): boolean {\n const now = Date.now();\n let c = this._rateLimitCounters.get(peerId);\n if (!c || now >= c.resetAt) {\n c = { count: 0, resetAt: now + this._rateLimitConfig.windowMs };\n this._rateLimitCounters.set(peerId, c);\n }\n c.count++;\n return c.count <= this._rateLimitConfig.maxMessagesPerSecond;\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/sync/EventSync.ts","../src/core/HostAuthority.ts","../src/core/ClientReceiver.ts","../src/sync/SnapshotSync.ts","../src/sync/PredictionSync.ts"],"sourcesContent":["import type { CarverTransport, CarverChannel, EventPacket } from \"../types\";\n\n/**\n * Layer 1: Event-based messaging over a reliable+ordered channel.\n * Used for turn-based games, chat, and infrequent state changes.\n */\nexport class EventSync {\n private _transport: CarverTransport;\n private _channel: CarverChannel<string>;\n private _handlers = new Map<string, ((payload: unknown, peerId: string) => void)[]>();\n private _hostValidation: boolean;\n\n constructor(transport: CarverTransport, options?: { hostValidation?: boolean }) {\n this._transport = transport;\n this._hostValidation = options?.hostValidation ?? false;\n\n // Create a single reliable+ordered channel for events\n this._channel = transport.createChannel<string>('carver:events', {\n reliable: true,\n ordered: true,\n });\n\n // Listen for incoming events\n this._channel.onReceive((rawData: string, peerId: string) => {\n try {\n const packet: EventPacket = typeof rawData === 'string' ? JSON.parse(rawData) : rawData as unknown as EventPacket;\n\n // If host validation is enabled and we're the host, rebroadcast\n if (this._hostValidation && this._transport.isHost && packet.sender !== this._transport.peerId) {\n // Rebroadcast to all peers (except the original sender)\n const targets = Array.from(this._transport.peers).filter(p => p !== peerId);\n if (targets.length > 0) {\n this._channel.send(JSON.stringify(packet), targets);\n }\n }\n\n // If host validation is enabled and we're NOT the host,\n // only accept events from host (who rebroadcasts validated events)\n if (this._hostValidation && !this._transport.isHost && peerId !== this._transport.hostId) {\n return;\n }\n\n // Fire handlers for this event type\n const handlers = this._handlers.get(packet.type);\n if (handlers) {\n for (const handler of handlers) {\n handler(packet.payload, packet.sender);\n }\n }\n } catch {\n // Ignore malformed events\n }\n });\n }\n\n /**\n * Send a typed event to a specific peer or all peers.\n */\n sendEvent(type: string, payload: unknown, target?: string): void {\n const packet: EventPacket = {\n type,\n payload,\n sender: this._transport.peerId,\n target,\n };\n const serialized = JSON.stringify(packet);\n\n if (this._hostValidation && !this._transport.isHost) {\n // Route through host for validation\n this._channel.send(serialized, this._transport.hostId);\n } else if (target) {\n this._channel.send(serialized, target);\n } else {\n // Broadcast to all peers\n this._channel.send(serialized);\n }\n }\n\n /**\n * Broadcast a typed event to all connected peers.\n */\n broadcast(type: string, payload: unknown): void {\n this.sendEvent(type, payload);\n }\n\n /**\n * Register a handler for a specific event type.\n * Returns an unsubscribe function.\n */\n onEvent(type: string, callback: (payload: unknown, peerId: string) => void): () => void {\n let handlers = this._handlers.get(type);\n if (!handlers) {\n handlers = [];\n this._handlers.set(type, handlers);\n }\n handlers.push(callback);\n\n return () => {\n const arr = this._handlers.get(type);\n if (arr) {\n const idx = arr.indexOf(callback);\n if (idx >= 0) arr.splice(idx, 1);\n if (arr.length === 0) this._handlers.delete(type);\n }\n };\n }\n\n /**\n * Clean up the event channel.\n */\n destroy(): void {\n this._channel.close();\n this._handlers.clear();\n }\n}\n","import type { CarverTransport, CarverChannel, EntityState } from \"../types\";\nimport { Codec, SnapshotBuffer } from \"./codec\";\n\n/**\n * Host-side authority: reads networked actor states, serializes with delta compression,\n * and broadcasts to all clients at the configured broadcast rate.\n */\nexport class HostAuthority {\n private _transport: CarverTransport;\n private _codec: Codec;\n private _snapshotBuffer: SnapshotBuffer;\n private _snapshotChannel: CarverChannel<Uint8Array>;\n private _ackChannel: CarverChannel<string>;\n private _tick = 0;\n private _broadcastRate: number;\n private _broadcastAccumulator = 0;\n private _keyframeInterval: number;\n\n // Per-client last ACK'd tick for delta compression\n private _clientBaselines = new Map<string, number>();\n // Per-client last keyframe tick for scheduling\n private _clientLastKeyframeTick = new Map<string, number>();\n\n // Interest management callback (optional)\n private _interestFilter:\n | ((entityId: string, peerId: string) => boolean)\n | null = null;\n\n constructor(\n transport: CarverTransport,\n codec: Codec,\n snapshotBuffer: SnapshotBuffer,\n options?: {\n broadcastRate?: number;\n keyframeInterval?: number;\n },\n ) {\n this._transport = transport;\n this._codec = codec;\n this._snapshotBuffer = snapshotBuffer;\n this._broadcastRate = options?.broadcastRate ?? 20;\n this._keyframeInterval = options?.keyframeInterval ?? 300;\n\n // Unreliable channel for snapshots (fast, may drop)\n this._snapshotChannel = transport.createChannel<Uint8Array>(\n \"carver:snapshots\",\n {\n reliable: false,\n ordered: false,\n maxRetransmits: 0,\n },\n );\n\n // Reliable channel for ACKs\n this._ackChannel = transport.createChannel<string>(\"carver:acks\", {\n reliable: true,\n ordered: true,\n });\n\n // Listen for client ACKs\n this._ackChannel.onReceive((data: string, peerId: string) => {\n try {\n const ackTick =\n typeof data === \"string\"\n ? parseInt(data, 10)\n : (data as unknown as number);\n if (ackTick === -1) {\n // Client requesting keyframe\n this._clientBaselines.delete(peerId);\n } else {\n this._clientBaselines.set(peerId, ackTick);\n }\n } catch {\n /* ignore malformed ACKs */\n }\n });\n\n // Handle new peer joins — they need a keyframe\n transport.onPeerJoin((peerId) => {\n this._clientBaselines.delete(peerId); // Will force keyframe\n });\n\n transport.onPeerLeave((peerId) => {\n this._clientBaselines.delete(peerId);\n });\n }\n\n /** Set optional interest management filter */\n setInterestFilter(\n filter: ((entityId: string, peerId: string) => boolean) | null,\n ): void {\n this._interestFilter = filter;\n }\n\n /**\n * Called every fixed tick by the sync engine.\n * Collects entity states and decides whether to broadcast.\n */\n tick(\n currentTick: number,\n entities: Map<string, EntityState>,\n delta: number,\n ): void {\n this._tick = currentTick;\n\n // Store snapshot in ring buffer\n this._snapshotBuffer.store(currentTick, new Map(entities));\n\n // Check if we should broadcast this tick\n this._broadcastAccumulator += delta;\n const broadcastInterval = 1 / this._broadcastRate;\n if (this._broadcastAccumulator < broadcastInterval) return;\n this._broadcastAccumulator -= broadcastInterval;\n\n // Broadcast to each connected client\n for (const peerId of this._transport.peers) {\n this._broadcastToClient(peerId, currentTick, entities);\n }\n }\n\n /** Force a keyframe broadcast to all clients (e.g., after host migration) */\n forceKeyframe(\n currentTick: number,\n entities: Map<string, EntityState>,\n ): void {\n this._clientBaselines.clear();\n this._clientLastKeyframeTick.clear();\n this._snapshotBuffer.store(currentTick, new Map(entities));\n for (const peerId of this._transport.peers) {\n this._broadcastToClient(peerId, currentTick, entities);\n }\n }\n\n destroy(): void {\n this._snapshotChannel.close();\n this._ackChannel.close();\n this._clientBaselines.clear();\n this._clientLastKeyframeTick.clear();\n }\n\n private _broadcastToClient(\n peerId: string,\n currentTick: number,\n entities: Map<string, EntityState>,\n ): void {\n // Apply interest management filter\n let clientEntities = entities;\n if (this._interestFilter) {\n clientEntities = new Map<string, EntityState>();\n for (const [id, entity] of entities) {\n if (this._interestFilter(id, peerId)) {\n clientEntities.set(id, entity);\n }\n }\n }\n\n // Determine baseline for delta compression (per-client keyframe scheduling)\n const clientBaseTick = this._clientBaselines.get(peerId);\n const clientLastKeyframe = this._clientLastKeyframeTick.get(peerId) ?? 0;\n const needsKeyframe =\n clientBaseTick === undefined ||\n currentTick - clientLastKeyframe >= this._keyframeInterval;\n\n let baseline: Map<string, EntityState> | undefined;\n if (!needsKeyframe && clientBaseTick !== undefined) {\n baseline = this._snapshotBuffer.get(clientBaseTick);\n }\n\n if (needsKeyframe) {\n this._clientLastKeyframeTick.set(peerId, currentTick);\n }\n\n // Serialize (delta or keyframe)\n const packet = this._codec.serializeDelta(\n currentTick,\n needsKeyframe ? -1 : (clientBaseTick ?? -1),\n clientEntities,\n baseline,\n );\n\n if (packet) {\n this._snapshotChannel.send(packet, peerId);\n }\n }\n}\n","import type {\n CarverTransport,\n CarverChannel,\n EntityState,\n EntityState2D,\n EntityState3D,\n NetworkQuality,\n} from \"../types\";\nimport { Codec } from \"./codec\";\n\ninterface BufferedSnapshot {\n tick: number;\n entities: Map<string, EntityState>;\n receivedAt: number;\n}\n\n/**\n * Client-side state receiver: buffers incoming snapshots and interpolates\n * between them for smooth rendering.\n */\nexport class ClientReceiver {\n private _transport: CarverTransport;\n private _codec: Codec;\n private _snapshotChannel: CarverChannel<Uint8Array>;\n private _ackChannel: CarverChannel<string>;\n\n // Snapshot buffer (ring buffer of last N snapshots)\n private _buffer: BufferedSnapshot[] = [];\n private _bufferSize: number;\n\n // Interpolation settings\n private _method: \"hermite\" | \"linear\";\n private _extrapolateMs: number;\n\n // Current interpolated state\n private _interpolatedState = new Map<string, EntityState>();\n\n // Network quality tracking\n private _lastSnapshotTime = 0;\n private _networkQuality: NetworkQuality = \"good\";\n private _packetLossCount = 0;\n private _packetCount = 0;\n\n // Is2D mode\n private _is2D: boolean;\n\n // Entity state: full accumulated state from keyframes + deltas\n private _fullState = new Map<string, EntityState>();\n\n constructor(\n transport: CarverTransport,\n codec: Codec,\n options?: {\n bufferSize?: number;\n method?: \"hermite\" | \"linear\";\n extrapolateMs?: number;\n is2D?: boolean;\n },\n ) {\n this._transport = transport;\n this._codec = codec;\n this._bufferSize = options?.bufferSize ?? 3;\n this._method = options?.method ?? \"hermite\";\n this._extrapolateMs = options?.extrapolateMs ?? 250;\n this._is2D = options?.is2D ?? false;\n\n // Listen on the unreliable snapshot channel\n this._snapshotChannel = transport.createChannel<Uint8Array>(\n \"carver:snapshots\",\n {\n reliable: false,\n ordered: false,\n maxRetransmits: 0,\n },\n );\n\n this._ackChannel = transport.createChannel<string>(\"carver:acks\", {\n reliable: true,\n ordered: true,\n });\n\n this._snapshotChannel.onReceive((data: Uint8Array) => {\n this._handleSnapshot(data);\n });\n }\n\n /** Get the current interpolated entity states */\n get state(): Map<string, EntityState> {\n return this._interpolatedState;\n }\n\n get networkQuality(): NetworkQuality {\n return this._networkQuality;\n }\n\n /**\n * Called every render frame to interpolate between buffered snapshots.\n * @param renderTime - current render time in ms\n */\n interpolate(renderTime: number): Map<string, EntityState> {\n if (this._buffer.length < 2) {\n // Not enough snapshots to interpolate, return latest\n return this._buffer.length > 0\n ? this._buffer[this._buffer.length - 1].entities\n : this._interpolatedState;\n }\n\n // Find two snapshots to interpolate between\n // We render \"behind\" by one buffer interval\n const interpDelay = (this._bufferSize - 1) * (1000 / 20); // Assume ~20 pps\n const targetTime = renderTime - interpDelay;\n\n let from: BufferedSnapshot | null = null;\n let to: BufferedSnapshot | null = null;\n\n for (let i = 0; i < this._buffer.length - 1; i++) {\n if (\n this._buffer[i].receivedAt <= targetTime &&\n this._buffer[i + 1].receivedAt > targetTime\n ) {\n from = this._buffer[i];\n to = this._buffer[i + 1];\n break;\n }\n }\n\n if (!from || !to) {\n // Extrapolation case: we're past all buffered snapshots\n const latest = this._buffer[this._buffer.length - 1];\n const timeSinceLatest = renderTime - latest.receivedAt;\n\n if (timeSinceLatest > this._extrapolateMs) {\n // Too far behind, just use latest snapshot\n this._updateNetworkQuality(\"poor\");\n return latest.entities;\n }\n\n if (this._buffer.length >= 2) {\n from = this._buffer[this._buffer.length - 2];\n to = latest;\n this._updateNetworkQuality(\"degraded\");\n } else {\n return latest.entities;\n }\n } else {\n this._updateNetworkQuality(\"good\");\n }\n\n // Compute interpolation factor\n const range = to.receivedAt - from.receivedAt;\n const t =\n range > 0\n ? Math.min(1, Math.max(0, (targetTime - from.receivedAt) / range))\n : 1;\n\n // Interpolate all entities\n const result = new Map<string, EntityState>();\n const allIds = new Set([...from.entities.keys(), ...to.entities.keys()]);\n\n for (const id of allIds) {\n const fromEntity = from.entities.get(id);\n const toEntity = to.entities.get(id);\n\n if (toEntity && toEntity.c?.__removed) continue; // Removed entity\n\n if (fromEntity && toEntity) {\n result.set(id, this._interpolateEntity(fromEntity, toEntity, t));\n } else if (toEntity) {\n result.set(id, toEntity);\n }\n }\n\n this._interpolatedState = result;\n return result;\n }\n\n /** Request a keyframe from the host */\n requestKeyframe(): void {\n this._ackChannel.send(\"-1\");\n }\n\n destroy(): void {\n this._snapshotChannel.close();\n this._ackChannel.close();\n this._buffer = [];\n this._interpolatedState.clear();\n this._fullState.clear();\n }\n\n private _handleSnapshot(data: Uint8Array): void {\n try {\n const { tick, baseTick, entities } = this._codec.deserializePacket(data);\n const now = performance.now();\n\n if (baseTick === -1) {\n // Keyframe: replace full state\n this._fullState.clear();\n for (const entity of entities) {\n this._fullState.set(entity.id, entity);\n }\n } else {\n // Delta: apply changes to full state\n for (const entity of entities) {\n if (entity.c?.__removed) {\n this._fullState.delete(entity.id);\n } else {\n this._fullState.set(entity.id, entity);\n }\n }\n }\n\n // Buffer the full state snapshot\n this._buffer.push({\n tick,\n entities: new Map(this._fullState),\n receivedAt: now,\n });\n\n // Keep buffer bounded\n while (this._buffer.length > this._bufferSize * 2) {\n this._buffer.shift();\n }\n\n // Send ACK\n this._ackChannel.send(String(tick));\n\n // Track timing for network quality\n this._lastSnapshotTime = now;\n this._packetCount++;\n } catch {\n this._packetLossCount++;\n }\n }\n\n private _interpolateEntity(\n from: EntityState,\n to: EntityState,\n t: number,\n ): EntityState {\n if (this._is2D || !(\"z\" in from)) {\n return this._interpolateEntity2D(\n from as EntityState2D,\n to as EntityState2D,\n t,\n );\n }\n return this._interpolateEntity3D(\n from as EntityState3D,\n to as EntityState3D,\n t,\n );\n }\n\n private _interpolateEntity2D(\n from: EntityState2D,\n to: EntityState2D,\n t: number,\n ): EntityState2D {\n if (this._method === \"hermite\") {\n return {\n id: to.id,\n x: hermite(from.x, from.vx, to.x, to.vx, t),\n y: hermite(from.y, from.vy, to.y, to.vy, t),\n a: lerpAngle(from.a, to.a, t),\n vx: lerp(from.vx, to.vx, t),\n vy: lerp(from.vy, to.vy, t),\n va: lerp(from.va, to.va, t),\n c: interpolateCustom(from.c, to.c, t),\n };\n }\n // Linear\n return {\n id: to.id,\n x: lerp(from.x, to.x, t),\n y: lerp(from.y, to.y, t),\n a: lerpAngle(from.a, to.a, t),\n vx: lerp(from.vx, to.vx, t),\n vy: lerp(from.vy, to.vy, t),\n va: lerp(from.va, to.va, t),\n c: interpolateCustom(from.c, to.c, t),\n };\n }\n\n private _interpolateEntity3D(\n from: EntityState3D,\n to: EntityState3D,\n t: number,\n ): EntityState3D {\n // Quaternion SLERP for rotation\n const [qx, qy, qz, qw] = slerp(\n from.qx,\n from.qy,\n from.qz,\n from.qw,\n to.qx,\n to.qy,\n to.qz,\n to.qw,\n t,\n );\n\n if (this._method === \"hermite\") {\n return {\n id: to.id,\n x: hermite(from.x, from.vx, to.x, to.vx, t),\n y: hermite(from.y, from.vy, to.y, to.vy, t),\n z: hermite(from.z, from.vz, to.z, to.vz, t),\n qx,\n qy,\n qz,\n qw,\n vx: lerp(from.vx, to.vx, t),\n vy: lerp(from.vy, to.vy, t),\n vz: lerp(from.vz, to.vz, t),\n wx: lerp(from.wx, to.wx, t),\n wy: lerp(from.wy, to.wy, t),\n wz: lerp(from.wz, to.wz, t),\n c: interpolateCustom(from.c, to.c, t),\n };\n }\n\n // Linear\n return {\n id: to.id,\n x: lerp(from.x, to.x, t),\n y: lerp(from.y, to.y, t),\n z: lerp(from.z, to.z, t),\n qx,\n qy,\n qz,\n qw,\n vx: lerp(from.vx, to.vx, t),\n vy: lerp(from.vy, to.vy, t),\n vz: lerp(from.vz, to.vz, t),\n wx: lerp(from.wx, to.wx, t),\n wy: lerp(from.wy, to.wy, t),\n wz: lerp(from.wz, to.wz, t),\n c: interpolateCustom(from.c, to.c, t),\n };\n }\n\n private _updateNetworkQuality(quality: NetworkQuality): void {\n this._networkQuality = quality;\n }\n}\n\n// ── Math utilities ──\n\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\nfunction lerpAngle(a: number, b: number, t: number): number {\n let diff = b - a;\n // Wrap to [-PI, PI]\n while (diff > Math.PI) diff -= Math.PI * 2;\n while (diff < -Math.PI) diff += Math.PI * 2;\n return a + diff * t;\n}\n\n/**\n * Hermite spline interpolation using velocity for smooth curves.\n * h(t) = (2t^3 - 3t^2 + 1)p0 + (t^3 - 2t^2 + t)v0 + (-2t^3 + 3t^2)p1 + (t^3 - t^2)v1\n */\nfunction hermite(\n p0: number,\n v0: number,\n p1: number,\n v1: number,\n t: number,\n): number {\n const t2 = t * t;\n const t3 = t2 * t;\n return (\n (2 * t3 - 3 * t2 + 1) * p0 +\n (t3 - 2 * t2 + t) * v0 +\n (-2 * t3 + 3 * t2) * p1 +\n (t3 - t2) * v1\n );\n}\n\n/**\n * Quaternion SLERP (Spherical Linear Interpolation).\n */\nfunction slerp(\n ax: number,\n ay: number,\n az: number,\n aw: number,\n bx: number,\n by: number,\n bz: number,\n bw: number,\n t: number,\n): [number, number, number, number] {\n // Compute dot product\n let dot = ax * bx + ay * by + az * bz + aw * bw;\n\n // Ensure shortest path\n if (dot < 0) {\n bx = -bx;\n by = -by;\n bz = -bz;\n bw = -bw;\n dot = -dot;\n }\n\n if (dot > 0.9995) {\n // Very close, use linear interpolation\n return [lerp(ax, bx, t), lerp(ay, by, t), lerp(az, bz, t), lerp(aw, bw, t)];\n }\n\n const theta = Math.acos(Math.min(1, Math.max(-1, dot)));\n const sinTheta = Math.sin(theta);\n const wa = Math.sin((1 - t) * theta) / sinTheta;\n const wb = Math.sin(t * theta) / sinTheta;\n\n return [\n ax * wa + bx * wb,\n ay * wa + by * wb,\n az * wa + bz * wb,\n aw * wa + bw * wb,\n ];\n}\n\n/**\n * Interpolate custom properties: lerp numbers, instant-swap others.\n */\nfunction interpolateCustom(\n from: Record<string, unknown> | undefined,\n to: Record<string, unknown> | undefined,\n t: number,\n): Record<string, unknown> | undefined {\n if (!from && !to) return undefined;\n if (!from) return to;\n if (!to) return from;\n\n const result: Record<string, unknown> = {};\n const allKeys = new Set([...Object.keys(from), ...Object.keys(to)]);\n\n for (const key of allKeys) {\n const fromVal = from[key];\n const toVal = to[key];\n if (typeof fromVal === \"number\" && typeof toVal === \"number\") {\n result[key] = lerp(fromVal, toVal, t);\n } else {\n // Instant swap: use target value after halfway\n result[key] = t >= 0.5 ? toVal : fromVal;\n }\n }\n\n return result;\n}\n","import type { CarverTransport, EntityState } from \"../types\";\nimport { Codec, SnapshotBuffer } from \"../core/codec\";\nimport { HostAuthority } from \"../core/HostAuthority\";\nimport { ClientReceiver } from \"../core/ClientReceiver\";\n\nexport interface SnapshotSyncOptions {\n broadcastRate?: number;\n keyframeInterval?: number;\n bufferSize?: number;\n interpolationMethod?: \"hermite\" | \"linear\";\n extrapolateMs?: number;\n is2D?: boolean;\n}\n\n/**\n * Layer 2: Snapshot interpolation sync engine.\n * Host broadcasts state at fixed intervals, clients interpolate.\n */\nexport class SnapshotSync {\n private _transport: CarverTransport;\n private _hostAuthority: HostAuthority | null = null;\n private _clientReceiver: ClientReceiver | null = null;\n private _codec: Codec;\n private _snapshotBuffer: SnapshotBuffer;\n\n constructor(\n transport: CarverTransport,\n codec: Codec,\n snapshotBuffer: SnapshotBuffer,\n options?: SnapshotSyncOptions,\n ) {\n this._transport = transport;\n this._codec = codec;\n this._snapshotBuffer = snapshotBuffer;\n\n if (transport.isHost) {\n this._hostAuthority = new HostAuthority(\n transport,\n codec,\n snapshotBuffer,\n {\n broadcastRate: options?.broadcastRate,\n keyframeInterval: options?.keyframeInterval,\n },\n );\n } else {\n this._clientReceiver = new ClientReceiver(transport, codec, {\n bufferSize: options?.bufferSize,\n method: options?.interpolationMethod,\n extrapolateMs: options?.extrapolateMs,\n is2D: options?.is2D,\n });\n }\n }\n\n get isHost(): boolean {\n return this._hostAuthority !== null;\n }\n\n get hostAuthority(): HostAuthority | null {\n return this._hostAuthority;\n }\n\n get clientReceiver(): ClientReceiver | null {\n return this._clientReceiver;\n }\n\n /** Host: called every fixed tick to potentially broadcast state */\n hostTick(\n tick: number,\n entities: Map<string, EntityState>,\n delta: number,\n ): void {\n this._hostAuthority?.tick(tick, entities, delta);\n }\n\n /** Client: called every render frame to interpolate */\n clientInterpolate(renderTime: number): Map<string, EntityState> {\n return this._clientReceiver?.interpolate(renderTime) ?? new Map();\n }\n\n /** Set interest filter on host authority */\n setInterestFilter(\n filter: ((entityId: string, peerId: string) => boolean) | null,\n ): void {\n this._hostAuthority?.setInterestFilter(filter);\n }\n\n /** Handle host migration: switch from client to host mode */\n promoteToHost(options?: SnapshotSyncOptions): void {\n this._clientReceiver?.destroy();\n this._clientReceiver = null;\n this._hostAuthority = new HostAuthority(\n this._transport,\n this._codec,\n this._snapshotBuffer,\n {\n broadcastRate: options?.broadcastRate,\n keyframeInterval: options?.keyframeInterval,\n },\n );\n }\n\n /** Handle host migration: switch from host to client mode */\n demoteToClient(options?: SnapshotSyncOptions): void {\n this._hostAuthority?.destroy();\n this._hostAuthority = null;\n this._clientReceiver = new ClientReceiver(\n this._transport,\n this._codec,\n {\n bufferSize: options?.bufferSize,\n method: options?.interpolationMethod,\n extrapolateMs: options?.extrapolateMs,\n is2D: options?.is2D,\n },\n );\n }\n\n destroy(): void {\n this._hostAuthority?.destroy();\n this._clientReceiver?.destroy();\n this._hostAuthority = null;\n this._clientReceiver = null;\n }\n}\n","import { pack, unpack } from \"msgpackr\";\nimport type {\n CarverTransport,\n CarverChannel,\n EntityState,\n EntityState3D,\n InputPacket,\n} from \"../types\";\nimport { Codec } from \"../core/codec\";\nimport { TickKeeper } from \"../core/TickKeeper\";\n\ninterface PredictionOptions {\n maxRewindTicks: number;\n errorSmoothingDecay: number;\n maxErrorPerFrame: number;\n snapThreshold: number;\n lagCompensation: boolean;\n}\n\ninterface InputBufferEntry {\n tick: number;\n input: unknown;\n}\n\ninterface ErrorCorrection {\n x: number;\n y: number;\n z: number;\n}\n\nconst DEFAULT_OPTIONS: PredictionOptions = {\n maxRewindTicks: 15,\n errorSmoothingDecay: 0.85,\n maxErrorPerFrame: 5,\n snapThreshold: 15,\n lagCompensation: false,\n};\n\n/**\n * Layer 3: Client-side prediction with server reconciliation.\n * Builds on top of Layer 2 (SnapshotSync).\n *\n * Flow:\n * Client: input -> apply locally (predict) -> store in buffer -> send to host\n * Host: receive input -> apply to simulation -> broadcast state + lastProcessedInputTick\n * Client: receive state -> compare with prediction ->\n * if mismatch: reset to server state + replay unacked inputs -> visual smoothing\n */\nexport class PredictionSync {\n private _transport: CarverTransport;\n private _codec: Codec;\n private _tickKeeper: TickKeeper;\n private _options: PredictionOptions;\n\n // Channels\n private _inputChannel: CarverChannel<string>;\n private _stateChannel: CarverChannel<Uint8Array>;\n private _ackChannel: CarverChannel<string>;\n\n // Input buffer: ring buffer of recent inputs keyed by tick\n private _inputBuffer: InputBufferEntry[] = [];\n // Per-client last processed input tick (host-side tracking)\n private _clientLastProcessedTick = new Map<string, number>();\n\n // Predicted state (client-side)\n private _predictedState = new Map<string, EntityState>();\n\n // Error correction vectors per entity\n private _errorCorrections = new Map<string, ErrorCorrection>();\n\n // Server state (last received authoritative snapshot)\n private _serverState = new Map<string, EntityState>();\n private _serverTick = 0;\n\n // Physics step callback (provided by developer)\n private _onPhysicsStep:\n | ((inputs: Map<string, unknown>, tick: number, isRollback: boolean) => void)\n | null = null;\n\n // Own input for current tick\n private _currentInput: unknown = null;\n\n // Is host\n private _isHost: boolean;\n\n constructor(\n transport: CarverTransport,\n codec: Codec,\n tickKeeper: TickKeeper,\n options?: Partial<PredictionOptions>,\n ) {\n this._transport = transport;\n this._codec = codec;\n this._tickKeeper = tickKeeper;\n this._options = { ...DEFAULT_OPTIONS, ...options };\n this._isHost = transport.isHost;\n\n // Reliable channel for inputs (client -> host)\n this._inputChannel = transport.createChannel<string>(\"carver:inputs\", {\n reliable: true,\n ordered: true,\n });\n\n // Unreliable channel for state (host -> clients)\n this._stateChannel = transport.createChannel<Uint8Array>(\"carver:pred-state\", {\n reliable: false,\n ordered: false,\n maxRetransmits: 0,\n });\n\n // Reliable ACK channel\n this._ackChannel = transport.createChannel<string>(\"carver:pred-acks\", {\n reliable: true,\n ordered: true,\n });\n\n if (this._isHost) {\n this._setupHostListeners();\n } else {\n this._setupClientListeners();\n }\n }\n\n /** Set the physics step callback (required for rollback re-simulation) */\n setPhysicsStep(\n cb: (inputs: Map<string, unknown>, tick: number, isRollback: boolean) => void,\n ): void {\n this._onPhysicsStep = cb;\n }\n\n /** Set the current input for this tick (client-side) */\n setInput(input: unknown): void {\n this._currentInput = input;\n }\n\n /**\n * Called every fixed tick on the client.\n * Applies input locally (prediction), buffers it, and sends to host.\n */\n clientTick(tick: number): void {\n if (this._isHost) return;\n\n // Buffer the input\n if (this._currentInput !== null) {\n this._inputBuffer.push({ tick, input: this._currentInput });\n\n // Send input to host\n const packet: InputPacket = {\n t: tick,\n i: this._currentInput,\n p: this._transport.peerId,\n };\n this._inputChannel.send(JSON.stringify(packet));\n\n // Apply input locally (prediction)\n if (this._onPhysicsStep) {\n const inputs = new Map<string, unknown>();\n inputs.set(this._transport.peerId, this._currentInput);\n this._onPhysicsStep(inputs, tick, false);\n }\n\n this._currentInput = null;\n }\n\n // Trim old inputs from buffer\n const minTick = tick - this._options.maxRewindTicks * 2;\n while (this._inputBuffer.length > 0 && this._inputBuffer[0].tick < minTick) {\n this._inputBuffer.shift();\n }\n }\n\n /**\n * Called every fixed tick on the host.\n * Processes received inputs and broadcasts authoritative state.\n */\n hostTick(tick: number, entities: Map<string, EntityState>, _delta: number): void {\n if (!this._isHost) return;\n\n // Broadcast authoritative state with per-client last processed input tick\n const stateArray = Array.from(entities.values());\n const data = this._codec.serialize(stateArray);\n\n // Send per-client packets with each client's own last processed input tick\n for (const peerId of this._transport.peers) {\n const lastTick = this._clientLastProcessedTick.get(peerId) ?? -1;\n const packet = {\n t: tick,\n s: data,\n li: lastTick,\n };\n this._stateChannel.send(pack(packet), peerId);\n }\n }\n\n /**\n * Called every render frame on the client to apply visual error smoothing.\n * Returns the corrected entity states.\n */\n applyErrorSmoothing(entities: Map<string, EntityState>): Map<string, EntityState> {\n const result = new Map<string, EntityState>();\n const decay = this._options.errorSmoothingDecay;\n\n for (const [id, entity] of entities) {\n const correction = this._errorCorrections.get(id);\n if (!correction) {\n result.set(id, entity);\n continue;\n }\n\n // Apply correction and decay\n const corrected = { ...entity };\n corrected.x += correction.x;\n corrected.y += correction.y;\n if (\"z\" in corrected) {\n (corrected as EntityState3D).z += correction.z;\n }\n\n // Decay the correction\n correction.x *= decay;\n correction.y *= decay;\n correction.z *= decay;\n\n // Remove if negligible\n const mag = Math.abs(correction.x) + Math.abs(correction.y) + Math.abs(correction.z);\n if (mag < 0.001) {\n this._errorCorrections.delete(id);\n }\n\n result.set(id, corrected);\n }\n\n return result;\n }\n\n get predictedState(): Map<string, EntityState> {\n return this._predictedState;\n }\n\n get serverTick(): number {\n return this._serverTick;\n }\n\n destroy(): void {\n this._inputChannel.close();\n this._stateChannel.close();\n this._ackChannel.close();\n this._inputBuffer = [];\n this._predictedState.clear();\n this._errorCorrections.clear();\n this._serverState.clear();\n }\n\n // ── Private: Host-side ──\n\n private _setupHostListeners(): void {\n // Receive inputs from clients\n this._inputChannel.onReceive((rawData: string, peerId: string) => {\n try {\n const packet: InputPacket = JSON.parse(rawData);\n // Apply input to simulation via callback\n if (this._onPhysicsStep) {\n const inputs = new Map<string, unknown>();\n inputs.set(peerId, packet.i);\n this._onPhysicsStep(inputs, packet.t, false);\n }\n const prevTick = this._clientLastProcessedTick.get(peerId) ?? -1;\n this._clientLastProcessedTick.set(peerId, Math.max(prevTick, packet.t));\n } catch (err) {\n if (typeof console !== 'undefined') console.debug('[CarverJS] Malformed input packet:', err);\n }\n });\n }\n\n // ── Private: Client-side ──\n\n private _setupClientListeners(): void {\n // Receive authoritative state from host\n this._stateChannel.onReceive((data: Uint8Array) => {\n try {\n const packet = unpack(data) as { t: number; s: Uint8Array; li: number };\n\n const entities = this._codec.deserialize(packet.s);\n const serverTick = packet.t;\n const lastInputTick = packet.li;\n\n this._serverTick = serverTick;\n this._tickKeeper.setServerTick(serverTick);\n\n // Build server state map\n this._serverState.clear();\n for (const entity of entities) {\n this._serverState.set(entity.id, entity);\n }\n\n // Reconciliation: compare predicted state with server state\n this._reconcile(lastInputTick);\n } catch (err) {\n if (typeof console !== 'undefined') console.debug('[CarverJS] Malformed state packet:', err);\n }\n });\n }\n\n private _reconcile(lastInputTick: number): void {\n // Remove acknowledged inputs from buffer\n this._inputBuffer = this._inputBuffer.filter((entry) => entry.tick > lastInputTick);\n\n // Compare predicted state with server state\n let needsRollback = false;\n let maxError = 0;\n\n for (const [id, serverEntity] of this._serverState) {\n const predicted = this._predictedState.get(id);\n if (!predicted) continue;\n\n const error = this._computeError(predicted, serverEntity);\n maxError = Math.max(maxError, error);\n\n if (error > this._options.maxErrorPerFrame) {\n needsRollback = true;\n }\n }\n\n if (maxError > this._options.snapThreshold) {\n // Error too large -- hard snap to server state\n this._predictedState = new Map(this._serverState);\n this._errorCorrections.clear();\n return;\n }\n\n if (needsRollback) {\n // Store current positions for visual smoothing\n const oldPositions = new Map<string, { x: number; y: number; z: number }>();\n for (const [id, entity] of this._predictedState) {\n oldPositions.set(id, {\n x: entity.x,\n y: entity.y,\n z: \"z\" in entity ? (entity as EntityState3D).z : 0,\n });\n }\n\n // Reset to server state\n this._predictedState = new Map(this._serverState);\n\n // Replay unacked inputs\n if (this._onPhysicsStep) {\n for (const entry of this._inputBuffer) {\n const inputs = new Map<string, unknown>();\n inputs.set(this._transport.peerId, entry.input);\n this._onPhysicsStep(inputs, entry.tick, true); // isRollback = true\n }\n }\n\n // Compute visual error correction vectors\n for (const [id, newEntity] of this._predictedState) {\n const oldPos = oldPositions.get(id);\n if (oldPos) {\n this._errorCorrections.set(id, {\n x: oldPos.x - newEntity.x,\n y: oldPos.y - newEntity.y,\n z: oldPos.z - (\"z\" in newEntity ? (newEntity as EntityState3D).z : 0),\n });\n }\n }\n }\n }\n\n private _computeError(predicted: EntityState, server: EntityState): number {\n const dx = predicted.x - server.x;\n const dy = predicted.y - server.y;\n let dz = 0;\n if (\"z\" in predicted && \"z\" in server) {\n dz = (predicted as EntityState3D).z - (server as EntityState3D).z;\n }\n return Math.sqrt(dx * dx + dy * dy + dz * dz);\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/transport/strategy/utils.ts","../src/transport/strategy/mqtt.ts","../src/transport/strategy/firebase.ts"],"sourcesContent":["/** Generate a random peer ID (20 chars, URL-safe) */\nexport function generatePeerId(): string {\n const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n const bytes = new Uint8Array(20);\n crypto.getRandomValues(bytes);\n let id = '';\n for (let i = 0; i < 20; i++) {\n id += chars[bytes[i] % chars.length];\n }\n return id;\n}\n\n/** Build MQTT topic paths for a given appId + roomId */\nexport function mqttTopics(appId: string, roomId: string, peerId?: string) {\n const base = `carver/${appId}`;\n return {\n /** Lobby wildcard: subscribe to discover all room announcements */\n lobbyWildcard: `${base}/lobby/+`,\n /** Single room lobby entry */\n roomLobbyEntry: `${base}/lobby/${roomId}`,\n /** Wildcard for all peer presence in a room */\n roomPresenceWildcard: `${base}/room/${roomId}/presence/+`,\n /** This peer's presence topic */\n peerPresence: peerId ? `${base}/room/${roomId}/presence/${peerId}` : '',\n /** This peer's signal inbox */\n peerSignalInbox: peerId ? `${base}/room/${roomId}/signal/${peerId}` : '',\n };\n}\n\n/** Build Firebase RTDB paths for a given appId + roomId */\nexport function firebasePaths(appId: string, roomId: string, peerId?: string) {\n const base = `${appId}/__carver__`;\n return {\n lobby: `${base}/lobby`,\n roomLobbyEntry: `${base}/lobby/${roomId}`,\n peers: `${base}/rooms/${roomId}/peers`,\n peerPresence: peerId ? `${base}/rooms/${roomId}/peers/${peerId}` : '',\n peerSignalInbox: peerId ? `${base}/rooms/${roomId}/signals/${peerId}` : '',\n };\n}\n\n/** Default MQTT brokers (WebSocket endpoints, free/public) */\nexport const DEFAULT_MQTT_BROKERS = [\n 'wss://broker.emqx.io:8084/mqtt',\n 'wss://test.mosquitto.org:8081/mqtt',\n];\n\n/** Room announcement expiry time (30s without heartbeat = stale) */\nexport const ROOM_ANNOUNCE_EXPIRY_MS = 30_000;\n\n/** Room announcement heartbeat interval */\nexport const ROOM_ANNOUNCE_INTERVAL_MS = 10_000;\n\n/** Peer presence heartbeat interval */\nexport const PRESENCE_HEARTBEAT_MS = 5_000;\n\n/** Peer expiry: 3 missed heartbeats */\nexport const PEER_EXPIRY_MS = PRESENCE_HEARTBEAT_MS * 3;\n\n/** Rapid warmup announces for faster initial peer discovery */\nexport const PRESENCE_WARMUP_DELAYS_MS = [200, 500, 1500];\n\n/** Remove an item from an array by reference. Returns true if found. */\nexport function removeFromArray<T>(arr: T[], item: T): boolean {\n const idx = arr.indexOf(item);\n if (idx >= 0) {\n arr.splice(idx, 1);\n return true;\n }\n return false;\n}\n","import type {\n SignalingStrategy,\n PeerMetadata,\n RoomAnnouncement,\n MqttStrategyConfig,\n} from \"./types\";\nimport {\n generatePeerId,\n mqttTopics,\n removeFromArray,\n DEFAULT_MQTT_BROKERS,\n ROOM_ANNOUNCE_EXPIRY_MS,\n ROOM_ANNOUNCE_INTERVAL_MS,\n PRESENCE_HEARTBEAT_MS,\n PEER_EXPIRY_MS,\n PRESENCE_WARMUP_DELAYS_MS,\n} from \"./utils\";\n\n// mqtt.MqttClient type (avoid hard import at module level)\ntype MqttClient = {\n on(event: string, cb: (...args: any[]) => void): void;\n subscribe(topic: string | string[], opts: Record<string, unknown>, cb?: (err: Error | null) => void): void;\n unsubscribe(topic: string | string[]): void;\n publish(topic: string, payload: string | Buffer, opts?: Record<string, unknown>): void;\n end(force?: boolean): void;\n};\n\n/**\n * MQTT-based signaling strategy.\n *\n * Connects to public MQTT brokers over WebSocket. Peer discovery uses\n * retained presence messages with periodic heartbeats. SDP/ICE relay\n * uses per-peer signal topics. Room discovery uses retained lobby topics.\n *\n * Zero infrastructure cost -- uses free public brokers by default.\n */\nexport class MqttStrategy implements SignalingStrategy {\n readonly selfId: string;\n\n private _appId: string;\n private _config: MqttStrategyConfig;\n private _client: MqttClient | null = null;\n private _roomId: string | null = null;\n private _peerMeta: PeerMetadata = {};\n /** Monotonic counter to detect stale leaveRoom completions */\n private _joinGeneration = 0;\n\n // Lazy init\n private _initPromise: Promise<void> | null = null;\n\n // Callbacks\n private _onPeerDiscovered: ((peerId: string, meta: PeerMetadata) => void)[] = [];\n private _onPeerLeft: ((peerId: string) => void)[] = [];\n private _onSignal: ((fromPeerId: string, data: unknown) => void)[] = [];\n private _onLobby: ((rooms: RoomAnnouncement[]) => void)[] = [];\n\n // State\n private _knownPeers = new Map<string, { meta: PeerMetadata; lastSeen: number }>();\n private _lobbyRooms = new Map<string, RoomAnnouncement>();\n private _presenceTimer: ReturnType<typeof setInterval> | null = null;\n private _warmupTimers: ReturnType<typeof setTimeout>[] = [];\n private _lobbyAnnounceTimer: ReturnType<typeof setInterval> | null = null;\n private _peerExpiryTimer: ReturnType<typeof setInterval> | null = null;\n private _lobbySubscribed = false;\n private _destroyed = false;\n\n constructor(appId: string, config: MqttStrategyConfig = { type: 'mqtt' }) {\n this.selfId = generatePeerId();\n this._appId = appId;\n this._config = config;\n }\n\n // ── Public API ──\n\n async init(): Promise<void> {\n return this._ensureInit();\n }\n\n async joinRoom(roomId: string, peerMeta: PeerMetadata): Promise<void> {\n await this._ensureInit();\n if (!this._client) throw new Error('MQTT client not available');\n\n this._joinGeneration++;\n this._roomId = roomId;\n this._peerMeta = peerMeta;\n\n const topics = mqttTopics(this._appId, roomId, this.selfId);\n\n // Subscribe to room presence (discover peers) and own signal inbox\n await new Promise<void>((resolve, reject) => {\n this._client!.subscribe(\n [topics.roomPresenceWildcard, topics.peerSignalInbox],\n { qos: 1 },\n (err: Error | null) => (err ? reject(err) : resolve()),\n );\n });\n\n // Publish retained presence\n this._publishPresence();\n\n // Rapid warmup announces for fast peer discovery\n for (const delay of PRESENCE_WARMUP_DELAYS_MS) {\n this._warmupTimers.push(setTimeout(() => this._publishPresence(), delay));\n }\n\n // Periodic heartbeat\n this._presenceTimer = setInterval(() => this._publishPresence(), PRESENCE_HEARTBEAT_MS);\n\n // Periodic peer expiry check\n this._peerExpiryTimer = setInterval(() => this._checkPeerExpiry(), PRESENCE_HEARTBEAT_MS);\n }\n\n async leaveRoom(): Promise<void> {\n if (!this._client || !this._roomId) return;\n\n const generation = this._joinGeneration;\n const topics = mqttTopics(this._appId, this._roomId, this.selfId);\n this._clearRoomTimers();\n\n // Clear retained presence\n this._client.publish(topics.peerPresence, '', { retain: true, qos: 1 });\n\n // Unsubscribe from room topics\n this._client.unsubscribe([topics.roomPresenceWildcard, topics.peerSignalInbox]);\n\n this._knownPeers.clear();\n\n // Only null out _roomId if no new joinRoom() has run since we started.\n if (this._joinGeneration === generation) {\n this._roomId = null;\n }\n }\n\n signal(targetPeerId: string, data: unknown): void {\n if (!this._client || !this._roomId) return;\n const targetTopic = `carver/${this._appId}/room/${this._roomId}/signal/${targetPeerId}`;\n this._client.publish(\n targetTopic,\n JSON.stringify({ from: this.selfId, data, ts: Date.now() }),\n { qos: 1 },\n );\n }\n\n subscribeToLobby(cb: (rooms: RoomAnnouncement[]) => void): () => void {\n this._onLobby.push(cb);\n\n // Subscribe to lobby topic (lazy -- waits for init)\n if (!this._lobbySubscribed) {\n this._lobbySubscribed = true;\n this._ensureInit().then(() => {\n if (this._client && !this._destroyed) {\n const lobbyTopic = mqttTopics(this._appId, '', '').lobbyWildcard;\n this._client.subscribe(lobbyTopic, { qos: 0 });\n }\n });\n }\n\n return () => {\n removeFromArray(this._onLobby, cb);\n };\n }\n\n announceRoom(announcement: RoomAnnouncement): void {\n if (!this._client) return;\n const topic = mqttTopics(this._appId, announcement.roomId, '').roomLobbyEntry;\n\n announcement.lastSeen = Date.now();\n this._client.publish(topic, JSON.stringify(announcement), { retain: true, qos: 1 });\n\n // Periodic heartbeat\n if (this._lobbyAnnounceTimer) clearInterval(this._lobbyAnnounceTimer);\n this._lobbyAnnounceTimer = setInterval(() => {\n announcement.lastSeen = Date.now();\n this._client?.publish(topic, JSON.stringify(announcement), { retain: true, qos: 1 });\n }, ROOM_ANNOUNCE_INTERVAL_MS);\n }\n\n removeRoomAnnouncement(roomId: string): void {\n if (!this._client) return;\n const topic = mqttTopics(this._appId, roomId, '').roomLobbyEntry;\n this._client.publish(topic, '', { retain: true, qos: 1 });\n if (this._lobbyAnnounceTimer) {\n clearInterval(this._lobbyAnnounceTimer);\n this._lobbyAnnounceTimer = null;\n }\n }\n\n onPeerDiscovered(cb: (peerId: string, meta: PeerMetadata) => void): () => void {\n this._onPeerDiscovered.push(cb);\n return () => { removeFromArray(this._onPeerDiscovered, cb); };\n }\n\n onPeerLeft(cb: (peerId: string) => void): () => void {\n this._onPeerLeft.push(cb);\n return () => { removeFromArray(this._onPeerLeft, cb); };\n }\n\n onSignal(cb: (fromPeerId: string, data: unknown) => void): () => void {\n this._onSignal.push(cb);\n return () => { removeFromArray(this._onSignal, cb); };\n }\n\n destroy(): void {\n this._destroyed = true;\n this._clearRoomTimers();\n\n // Clean up retained presence\n if (this._client && this._roomId) {\n const topics = mqttTopics(this._appId, this._roomId, this.selfId);\n this._client.publish(topics.peerPresence, '', { retain: true, qos: 1 });\n }\n\n this._client?.end(true);\n this._client = null;\n this._knownPeers.clear();\n this._lobbyRooms.clear();\n this._onPeerDiscovered = [];\n this._onPeerLeft = [];\n this._onSignal = [];\n this._onLobby = [];\n }\n\n // ── Private ──\n\n private _ensureInit(): Promise<void> {\n if (!this._initPromise) {\n this._initPromise = this._doInit();\n }\n return this._initPromise;\n }\n\n private async _doInit(): Promise<void> {\n const mqtt = await import('mqtt');\n const brokers = this._config.brokerUrls ?? DEFAULT_MQTT_BROKERS;\n // Pick a random broker from the available pool\n const brokerUrl = brokers[Math.floor(Math.random() * brokers.length)];\n\n return new Promise<void>((resolve, reject) => {\n const connectFn = mqtt.default?.connect ?? mqtt.connect;\n this._client = connectFn(brokerUrl, {\n clientId: `carver_${this.selfId}`,\n clean: true,\n connectTimeout: 10_000,\n keepalive: 30,\n }) as MqttClient;\n\n this._client.on('connect', () => {\n if (!this._destroyed) resolve();\n });\n\n this._client.on('error', (err: Error) => {\n if (!this._initPromise) return; // already resolved\n reject(err);\n });\n\n this._client.on('message', (topic: string, payload: Buffer) => {\n this._handleMessage(topic, payload);\n });\n });\n }\n\n private _publishPresence(): void {\n if (!this._client || !this._roomId) return;\n const topics = mqttTopics(this._appId, this._roomId, this.selfId);\n this._client.publish(\n topics.peerPresence,\n JSON.stringify({ peerId: this.selfId, meta: this._peerMeta, ts: Date.now() }),\n { retain: true, qos: 1 },\n );\n }\n\n private _handleMessage(topic: string, payload: Buffer): void {\n const raw = payload.toString();\n\n // ── Presence message ──\n const presenceMatch = topic.match(/\\/room\\/[^/]+\\/presence\\/([^/]+)$/);\n if (presenceMatch) {\n const peerId = presenceMatch[1];\n if (peerId === this.selfId) return;\n\n if (!raw) {\n // Empty retained = peer left\n if (this._knownPeers.has(peerId)) {\n this._knownPeers.delete(peerId);\n for (const cb of this._onPeerLeft) cb(peerId);\n }\n return;\n }\n try {\n const msg = JSON.parse(raw);\n const isNew = !this._knownPeers.has(peerId);\n this._knownPeers.set(peerId, { meta: msg.meta ?? {}, lastSeen: msg.ts ?? Date.now() });\n if (isNew) {\n for (const cb of this._onPeerDiscovered) cb(peerId, msg.meta ?? {});\n }\n } catch { /* ignore malformed */ }\n return;\n }\n\n // ── Signal message (SDP / ICE) ──\n const signalMatch = topic.match(/\\/room\\/[^/]+\\/signal\\/([^/]+)$/);\n if (signalMatch) {\n try {\n const msg = JSON.parse(raw);\n if (msg.from && msg.from !== this.selfId) {\n for (const cb of this._onSignal) cb(msg.from, msg.data);\n }\n } catch { /* ignore malformed */ }\n return;\n }\n\n // ── Lobby announcement ──\n const lobbyMatch = topic.match(/\\/lobby\\/([^/]+)$/);\n if (lobbyMatch) {\n const roomId = lobbyMatch[1];\n if (!raw) {\n this._lobbyRooms.delete(roomId);\n } else {\n try {\n const ann = JSON.parse(raw) as RoomAnnouncement;\n if (Date.now() - ann.lastSeen < ROOM_ANNOUNCE_EXPIRY_MS) {\n this._lobbyRooms.set(roomId, ann);\n } else {\n this._lobbyRooms.delete(roomId);\n }\n } catch { /* ignore */ }\n }\n const rooms = Array.from(this._lobbyRooms.values());\n for (const cb of this._onLobby) cb(rooms);\n }\n }\n\n private _checkPeerExpiry(): void {\n const now = Date.now();\n for (const [peerId, data] of this._knownPeers) {\n if (now - data.lastSeen > PEER_EXPIRY_MS) {\n this._knownPeers.delete(peerId);\n for (const cb of this._onPeerLeft) cb(peerId);\n }\n }\n }\n\n private _clearRoomTimers(): void {\n if (this._presenceTimer) { clearInterval(this._presenceTimer); this._presenceTimer = null; }\n for (const t of this._warmupTimers) clearTimeout(t);\n this._warmupTimers = [];\n if (this._lobbyAnnounceTimer) { clearInterval(this._lobbyAnnounceTimer); this._lobbyAnnounceTimer = null; }\n if (this._peerExpiryTimer) { clearInterval(this._peerExpiryTimer); this._peerExpiryTimer = null; }\n }\n}\n","import type {\n SignalingStrategy,\n PeerMetadata,\n RoomAnnouncement,\n FirebaseStrategyConfig,\n} from \"./types\";\nimport {\n generatePeerId,\n firebasePaths,\n removeFromArray,\n ROOM_ANNOUNCE_EXPIRY_MS,\n ROOM_ANNOUNCE_INTERVAL_MS,\n} from \"./utils\";\n\n/**\n * Firebase Realtime Database signaling strategy.\n *\n * Requires the `firebase` package as a peer dependency.\n * Pass either a `databaseURL` (auto-creates a namespaced Firebase app)\n * or an existing `firebaseApp` instance.\n *\n * Presence cleanup is automatic via Firebase onDisconnect().\n */\nexport class FirebaseStrategy implements SignalingStrategy {\n readonly selfId: string;\n\n private _appId: string;\n private _config: FirebaseStrategyConfig;\n private _db: any = null;\n private _firebaseApp: any = null;\n private _ownApp = false;\n private _roomId: string | null = null;\n private _peerMeta: PeerMetadata = {};\n /** Monotonic counter to detect stale leaveRoom completions */\n private _joinGeneration = 0;\n\n // Lazy init\n private _initPromise: Promise<void> | null = null;\n\n // Firebase module references (filled after dynamic import)\n private _fb: {\n ref: any;\n set: any;\n push: any;\n remove: any;\n onValue: any;\n onChildAdded: any;\n onChildRemoved: any;\n onDisconnect: any;\n } | null = null;\n\n // Unsubscribe handles for Firebase listeners\n private _listeners: (() => void)[] = [];\n\n // Callbacks\n private _onPeerDiscovered: ((peerId: string, meta: PeerMetadata) => void)[] = [];\n private _onPeerLeft: ((peerId: string) => void)[] = [];\n private _onSignal: ((fromPeerId: string, data: unknown) => void)[] = [];\n private _onLobby: ((rooms: RoomAnnouncement[]) => void)[] = [];\n\n // State\n private _knownPeers = new Set<string>();\n private _lobbyAnnounceTimer: ReturnType<typeof setInterval> | null = null;\n private _destroyed = false;\n\n constructor(appId: string, config: FirebaseStrategyConfig) {\n this.selfId = generatePeerId();\n this._appId = appId;\n this._config = config;\n }\n\n // ── Public API ──\n\n async init(): Promise<void> {\n return this._ensureInit();\n }\n\n async joinRoom(roomId: string, peerMeta: PeerMetadata): Promise<void> {\n await this._ensureInit();\n if (!this._db || !this._fb) throw new Error('Firebase not initialized');\n\n // Bump generation so any in-flight leaveRoom from a prior call won't\n // null out _roomId after we set it here (React StrictMode race fix).\n this._joinGeneration++;\n\n this._roomId = roomId;\n this._peerMeta = peerMeta;\n const { ref, set, onChildAdded, onChildRemoved, onDisconnect, remove } = this._fb;\n const paths = firebasePaths(this._appId, roomId, this.selfId);\n\n // Clean stale signals from our inbox before listening (prevents\n // replaying SDP from a previous session that wasn't cleaned up).\n await remove(ref(this._db, paths.peerSignalInbox)).catch(() => {});\n\n // 1. Write presence with auto-cleanup on disconnect\n const presenceRef = ref(this._db, paths.peerPresence);\n await set(presenceRef, {\n peerId: this.selfId,\n meta: peerMeta,\n ts: Date.now(),\n });\n onDisconnect(presenceRef).remove();\n\n // 2. Listen for peers joining\n const peersRef = ref(this._db, paths.peers);\n const addedUnsub = onChildAdded(peersRef, (snapshot: any) => {\n const data = snapshot.val();\n if (!data || data.peerId === this.selfId) return;\n if (!this._knownPeers.has(data.peerId)) {\n this._knownPeers.add(data.peerId);\n for (const cb of this._onPeerDiscovered) cb(data.peerId, data.meta ?? {});\n }\n });\n this._listeners.push(() => addedUnsub());\n\n // 3. Listen for peers leaving\n const removedUnsub = onChildRemoved(peersRef, (snapshot: any) => {\n const data = snapshot.val();\n const peerId = data?.peerId ?? snapshot.key;\n if (peerId && this._knownPeers.has(peerId)) {\n this._knownPeers.delete(peerId);\n for (const cb of this._onPeerLeft) cb(peerId);\n }\n });\n this._listeners.push(() => removedUnsub());\n\n // 4. Listen for signals addressed to us\n const signalRef = ref(this._db, paths.peerSignalInbox);\n const signalUnsub = onChildAdded(signalRef, (snapshot: any) => {\n const msg = snapshot.val();\n if (!msg || msg.from === this.selfId) return;\n for (const cb of this._onSignal) cb(msg.from, msg.data);\n // Remove processed signal to keep the inbox clean\n remove(snapshot.ref);\n });\n this._listeners.push(() => signalUnsub());\n }\n\n async leaveRoom(): Promise<void> {\n if (!this._db || !this._fb || !this._roomId) return;\n\n // Capture current state so async cleanup targets the correct room\n // even if joinRoom() is called concurrently (React StrictMode).\n const leavingRoomId = this._roomId;\n const generation = this._joinGeneration;\n\n const { ref, remove } = this._fb;\n const paths = firebasePaths(this._appId, leavingRoomId, this.selfId);\n\n // Detach listeners\n for (const unsub of this._listeners) unsub();\n this._listeners = [];\n\n // Remove presence and signal inbox\n await Promise.all([\n remove(ref(this._db, paths.peerPresence)),\n remove(ref(this._db, paths.peerSignalInbox)),\n ]).catch(() => {});\n\n if (this._lobbyAnnounceTimer) {\n clearInterval(this._lobbyAnnounceTimer);\n this._lobbyAnnounceTimer = null;\n }\n\n this._knownPeers.clear();\n\n // Only null out _roomId if no new joinRoom() has run since we started.\n // This prevents the StrictMode race: old leaveRoom completing after\n // new joinRoom already set _roomId to the fresh value.\n if (this._joinGeneration === generation) {\n this._roomId = null;\n }\n }\n\n signal(targetPeerId: string, data: unknown): void {\n if (!this._db || !this._fb || !this._roomId) return;\n const { ref, push } = this._fb;\n\n // Atomic push: single operation writes the key + data together.\n // Avoids the push() + set() two-step that can cause onChildAdded to\n // fire with null if the listener catches the intermediate state.\n const inboxPath = firebasePaths(this._appId, this._roomId, targetPeerId).peerSignalInbox;\n push(ref(this._db, inboxPath), {\n from: this.selfId,\n data: sanitizeForFirebase(data),\n ts: Date.now(),\n });\n }\n\n subscribeToLobby(cb: (rooms: RoomAnnouncement[]) => void): () => void {\n this._onLobby.push(cb);\n\n this._ensureInit().then(() => {\n if (!this._db || !this._fb || this._destroyed) return;\n const { ref, onValue } = this._fb;\n const paths = firebasePaths(this._appId, '', '');\n const lobbyRef = ref(this._db, paths.lobby);\n\n const unsub = onValue(lobbyRef, (snapshot: any) => {\n const data = snapshot.val();\n if (!data) {\n for (const lcb of this._onLobby) lcb([]);\n return;\n }\n const now = Date.now();\n const rooms: RoomAnnouncement[] = Object.values(data).filter(\n (r: any) => r && now - (r.lastSeen ?? 0) < ROOM_ANNOUNCE_EXPIRY_MS,\n ) as RoomAnnouncement[];\n for (const lcb of this._onLobby) lcb(rooms);\n });\n this._listeners.push(() => unsub());\n });\n\n return () => {\n removeFromArray(this._onLobby, cb);\n };\n }\n\n announceRoom(announcement: RoomAnnouncement): void {\n if (!this._db || !this._fb) return;\n const { ref, set } = this._fb;\n const paths = firebasePaths(this._appId, announcement.roomId, '');\n\n announcement.lastSeen = Date.now();\n set(ref(this._db, paths.roomLobbyEntry), announcement);\n\n // Periodic heartbeat\n if (this._lobbyAnnounceTimer) clearInterval(this._lobbyAnnounceTimer);\n this._lobbyAnnounceTimer = setInterval(() => {\n announcement.lastSeen = Date.now();\n set(ref(this._db, paths.roomLobbyEntry), announcement);\n }, ROOM_ANNOUNCE_INTERVAL_MS);\n }\n\n removeRoomAnnouncement(roomId: string): void {\n if (!this._db || !this._fb) return;\n const { ref, remove } = this._fb;\n remove(ref(this._db, firebasePaths(this._appId, roomId, '').roomLobbyEntry));\n if (this._lobbyAnnounceTimer) {\n clearInterval(this._lobbyAnnounceTimer);\n this._lobbyAnnounceTimer = null;\n }\n }\n\n onPeerDiscovered(cb: (peerId: string, meta: PeerMetadata) => void): () => void {\n this._onPeerDiscovered.push(cb);\n return () => { removeFromArray(this._onPeerDiscovered, cb); };\n }\n\n onPeerLeft(cb: (peerId: string) => void): () => void {\n this._onPeerLeft.push(cb);\n return () => { removeFromArray(this._onPeerLeft, cb); };\n }\n\n onSignal(cb: (fromPeerId: string, data: unknown) => void): () => void {\n this._onSignal.push(cb);\n return () => { removeFromArray(this._onSignal, cb); };\n }\n\n destroy(): void {\n this._destroyed = true;\n for (const unsub of this._listeners) unsub();\n this._listeners = [];\n\n if (this._lobbyAnnounceTimer) {\n clearInterval(this._lobbyAnnounceTimer);\n this._lobbyAnnounceTimer = null;\n }\n\n // Best-effort cleanup\n if (this._db && this._fb && this._roomId) {\n const { ref, remove } = this._fb;\n const paths = firebasePaths(this._appId, this._roomId, this.selfId);\n remove(ref(this._db, paths.peerPresence)).catch(() => {});\n remove(ref(this._db, paths.peerSignalInbox)).catch(() => {});\n }\n\n // Delete own Firebase app if we created it\n if (this._ownApp && this._firebaseApp) {\n import('firebase/app').then(({ deleteApp }) => {\n deleteApp(this._firebaseApp).catch(() => {});\n });\n }\n\n this._db = null;\n this._firebaseApp = null;\n this._fb = null;\n this._knownPeers.clear();\n this._onPeerDiscovered = [];\n this._onPeerLeft = [];\n this._onSignal = [];\n this._onLobby = [];\n }\n\n // ── Private ──\n\n private _ensureInit(): Promise<void> {\n if (!this._initPromise) {\n this._initPromise = this._doInit();\n }\n return this._initPromise;\n }\n\n private async _doInit(): Promise<void> {\n const { initializeApp, getApps } = await import('firebase/app');\n const {\n getDatabase,\n ref,\n set,\n push,\n remove,\n onValue,\n onChildAdded,\n onChildRemoved,\n onDisconnect,\n } = await import('firebase/database');\n\n this._fb = { ref, set, push, remove, onValue, onChildAdded, onChildRemoved, onDisconnect };\n\n if (this._config.firebaseApp) {\n this._firebaseApp = this._config.firebaseApp;\n this._ownApp = false;\n } else {\n const appName = `carver_${this._appId}`;\n const existing = getApps().find((a: any) => a.name === appName);\n if (existing) {\n this._firebaseApp = existing;\n this._ownApp = false;\n } else {\n this._firebaseApp = initializeApp({ databaseURL: this._config.databaseURL }, appName);\n this._ownApp = true;\n }\n }\n\n this._db = getDatabase(this._firebaseApp);\n }\n}\n\n/**\n * Firebase RTDB deletes any key whose value is `null` (treats null as \"remove\").\n * ICE candidates from `toJSON()` can contain `null` fields (e.g. usernameFragment).\n * We recursively replace `null` with a sentinel so Firebase preserves the key.\n * On the receiving end, `_handleSignal` doesn't need to reverse this because\n * `new RTCIceCandidate()` / `new RTCSessionDescription()` accept missing fields.\n */\nfunction sanitizeForFirebase(obj: unknown): unknown {\n if (obj === null) return '__null__';\n if (Array.isArray(obj)) return obj.map(sanitizeForFirebase);\n if (typeof obj === 'object' && obj !== null) {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[key] = value === null ? '__null__' : sanitizeForFirebase(value);\n }\n return result;\n }\n return obj;\n}\n"],"mappings":"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","names":[]}