@carverjs/multiplayer 0.0.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/dist/NetworkManager-DrKM2tEx.d.mts +369 -0
- package/dist/NetworkManager-nvVAOr1O.d.ts +369 -0
- package/dist/chunk-3KT73N2S.mjs +655 -0
- package/dist/chunk-3KT73N2S.mjs.map +1 -0
- package/dist/chunk-EO3YNPRQ.mjs +817 -0
- package/dist/chunk-EO3YNPRQ.mjs.map +1 -0
- package/dist/chunk-UD6FDZMX.mjs +581 -0
- package/dist/chunk-UD6FDZMX.mjs.map +1 -0
- package/dist/firebase-CPu87KA0.d.ts +100 -0
- package/dist/firebase-PE6MxGdJ.d.mts +100 -0
- package/dist/index.d.mts +316 -0
- package/dist/index.d.ts +316 -0
- package/dist/index.js +3817 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1743 -0
- package/dist/index.mjs.map +1 -0
- package/dist/strategy.d.mts +7 -0
- package/dist/strategy.d.ts +7 -0
- package/dist/strategy.js +619 -0
- package/dist/strategy.js.map +1 -0
- package/dist/strategy.mjs +11 -0
- package/dist/strategy.mjs.map +1 -0
- package/dist/sync.d.mts +212 -0
- package/dist/sync.d.ts +212 -0
- package/dist/sync.js +845 -0
- package/dist/sync.js.map +1 -0
- package/dist/sync.mjs +11 -0
- package/dist/sync.mjs.map +1 -0
- package/dist/transport.d.mts +159 -0
- package/dist/transport.d.ts +159 -0
- package/dist/transport.js +1274 -0
- package/dist/transport.js.map +1 -0
- package/dist/transport.mjs +19 -0
- package/dist/transport.mjs.map +1 -0
- package/dist/types-5LHBOW08.d.mts +74 -0
- package/dist/types-5LHBOW08.d.ts +74 -0
- package/dist/types.d.mts +2 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/dist/types.mjs +1 -0
- package/dist/types.mjs.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1 @@
|
|
|
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":[]}
|