@wetspace/wetrtc 3.0.1 → 4.0.0
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/README.md +156 -27
- package/dist/es/__test__/codec-preference.test.js +36 -0
- package/dist/es/__test__/codec-preference.test.js.map +1 -0
- package/dist/es/__test__/data-manager.test.js +60 -0
- package/dist/es/__test__/data-manager.test.js.map +1 -0
- package/dist/es/__test__/fsm.test.js +33 -0
- package/dist/es/__test__/fsm.test.js.map +1 -0
- package/dist/es/__test__/media-manager.test.js +41 -0
- package/dist/es/__test__/media-manager.test.js.map +1 -0
- package/dist/es/__test__/signal-manager.test.js +100 -0
- package/dist/es/__test__/signal-manager.test.js.map +1 -0
- package/dist/es/__test__/wetrtc-lifecycle.test.js +152 -0
- package/dist/es/__test__/wetrtc-lifecycle.test.js.map +1 -0
- package/dist/es/data/data-manager.d.ts +37 -0
- package/dist/es/data/data-manager.d.ts.map +1 -0
- package/dist/es/data/data-manager.js +282 -0
- package/dist/es/data/data-manager.js.map +1 -0
- package/dist/es/data/types.d.ts +34 -0
- package/dist/es/data/types.d.ts.map +1 -0
- package/dist/es/data/types.js +0 -0
- package/dist/es/disposable.d.ts +12 -0
- package/dist/es/disposable.d.ts.map +1 -0
- package/dist/es/disposable.js +36 -0
- package/dist/es/disposable.js.map +1 -0
- package/dist/es/fsm.d.ts +26 -0
- package/dist/es/fsm.d.ts.map +1 -0
- package/dist/es/fsm.js +63 -0
- package/dist/es/fsm.js.map +1 -0
- package/dist/es/index.d.ts +22 -0
- package/dist/es/index.d.ts.map +1 -0
- package/dist/es/index.js +48 -0
- package/dist/es/index.js.map +1 -0
- package/dist/es/media/audio-encoding.d.ts +10 -0
- package/dist/es/media/audio-encoding.d.ts.map +1 -0
- package/dist/es/media/audio-encoding.js +41 -0
- package/dist/es/media/audio-encoding.js.map +1 -0
- package/dist/es/media/codec-preference.d.ts +11 -0
- package/dist/es/media/codec-preference.d.ts.map +1 -0
- package/dist/es/media/codec-preference.js +77 -0
- package/dist/es/media/codec-preference.js.map +1 -0
- package/dist/es/media/encoding-utils.d.ts +2 -0
- package/dist/es/media/encoding-utils.d.ts.map +1 -0
- package/dist/es/media/encoding-utils.js +8 -0
- package/dist/es/media/encoding-utils.js.map +1 -0
- package/dist/es/media/media-manager.d.ts +39 -0
- package/dist/es/media/media-manager.d.ts.map +1 -0
- package/dist/es/media/media-manager.js +121 -0
- package/dist/es/media/media-manager.js.map +1 -0
- package/dist/es/media/types.d.ts +25 -0
- package/dist/es/media/types.d.ts.map +1 -0
- package/dist/es/media/types.js +0 -0
- package/dist/es/media/video-encoding.d.ts +12 -0
- package/dist/es/media/video-encoding.d.ts.map +1 -0
- package/dist/es/media/video-encoding.js +60 -0
- package/dist/es/media/video-encoding.js.map +1 -0
- package/dist/es/signal/signal-manager.d.ts +45 -0
- package/dist/es/signal/signal-manager.d.ts.map +1 -0
- package/dist/es/signal/signal-manager.js +250 -0
- package/dist/es/signal/signal-manager.js.map +1 -0
- package/dist/es/signal/types.d.ts +26 -0
- package/dist/es/signal/types.d.ts.map +1 -0
- package/dist/es/signal/types.js +8 -0
- package/dist/es/signal/types.js.map +1 -0
- package/dist/es/stats/stats-monitor.d.ts +32 -0
- package/dist/es/stats/stats-monitor.d.ts.map +1 -0
- package/dist/es/stats/stats-monitor.js +191 -0
- package/dist/es/stats/stats-monitor.js.map +1 -0
- package/dist/es/stats/types.d.ts +33 -0
- package/dist/es/stats/types.d.ts.map +1 -0
- package/dist/es/stats/types.js +0 -0
- package/dist/es/utils/types.d.ts +46 -0
- package/dist/es/utils/types.d.ts.map +1 -0
- package/dist/es/utils/types.js +80 -0
- package/dist/es/utils/types.js.map +1 -0
- package/dist/es/wetrtc.d.ts +92 -0
- package/dist/es/wetrtc.d.ts.map +1 -0
- package/dist/es/wetrtc.js +403 -0
- package/dist/es/wetrtc.js.map +1 -0
- package/dist/lib/__test__/codec-preference.test.js +34 -0
- package/dist/lib/__test__/codec-preference.test.js.map +1 -0
- package/dist/lib/__test__/data-manager.test.js +61 -0
- package/dist/lib/__test__/data-manager.test.js.map +1 -0
- package/dist/lib/__test__/fsm.test.js +34 -0
- package/dist/lib/__test__/fsm.test.js.map +1 -0
- package/dist/lib/__test__/media-manager.test.js +42 -0
- package/dist/lib/__test__/media-manager.test.js.map +1 -0
- package/dist/lib/__test__/signal-manager.test.js +101 -0
- package/dist/lib/__test__/signal-manager.test.js.map +1 -0
- package/dist/lib/__test__/wetrtc-lifecycle.test.js +153 -0
- package/dist/lib/__test__/wetrtc-lifecycle.test.js.map +1 -0
- package/dist/lib/data/data-manager.js +306 -0
- package/dist/lib/data/data-manager.js.map +1 -0
- package/dist/lib/data/types.js +18 -0
- package/dist/lib/data/types.js.map +1 -0
- package/dist/lib/disposable.js +60 -0
- package/dist/lib/disposable.js.map +1 -0
- package/dist/lib/fsm.js +87 -0
- package/dist/lib/fsm.js.map +1 -0
- package/dist/lib/index.js +75 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/media/audio-encoding.js +66 -0
- package/dist/lib/media/audio-encoding.js.map +1 -0
- package/dist/lib/media/codec-preference.js +106 -0
- package/dist/lib/media/codec-preference.js.map +1 -0
- package/dist/lib/media/encoding-utils.js +32 -0
- package/dist/lib/media/encoding-utils.js.map +1 -0
- package/dist/lib/media/media-manager.js +145 -0
- package/dist/lib/media/media-manager.js.map +1 -0
- package/dist/lib/media/types.js +18 -0
- package/dist/lib/media/types.js.map +1 -0
- package/dist/lib/media/video-encoding.js +87 -0
- package/dist/lib/media/video-encoding.js.map +1 -0
- package/dist/lib/signal/signal-manager.js +274 -0
- package/dist/lib/signal/signal-manager.js.map +1 -0
- package/dist/lib/signal/types.js +32 -0
- package/dist/lib/signal/types.js.map +1 -0
- package/dist/lib/stats/stats-monitor.js +215 -0
- package/dist/lib/stats/stats-monitor.js.map +1 -0
- package/dist/lib/stats/types.js +18 -0
- package/dist/lib/stats/types.js.map +1 -0
- package/dist/lib/utils/types.js +108 -0
- package/dist/lib/utils/types.js.map +1 -0
- package/dist/lib/wetrtc.js +415 -0
- package/dist/lib/wetrtc.js.map +1 -0
- package/package.json +38 -43
- package/es/core/constant.d.ts +0 -6
- package/es/core/hook.d.ts +0 -31
- package/es/core/index.d.ts +0 -39
- package/es/index.d.ts +0 -6
- package/es/index.js +0 -1
- package/es/libs/index.d.ts +0 -41
- package/es/libs/record.d.ts +0 -8
- package/lib/core/constant.d.ts +0 -6
- package/lib/core/hook.d.ts +0 -31
- package/lib/core/index.d.ts +0 -39
- package/lib/index.d.ts +0 -6
- package/lib/index.js +0 -1
- package/lib/libs/index.d.ts +0 -41
- package/lib/libs/record.d.ts +0 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"mappings":"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","names":[],"ignoreList":[],"sources":["../../src/wetrtc.ts"],"sourcesContent":["import { ConnectionStateMachine } from './fsm';\r\nimport type { ConnectionState } from './fsm';\r\nimport { DisposableStack, type IDisposable } from './disposable';\r\nimport { SignalManager } from './signal/signal-manager';\r\nimport type { SignalConfig } from './signal/signal-manager';\r\nimport { MediaManager } from './media/media-manager';\r\nimport type { DisplayMediaOptions } from './media/types';\r\nimport {\r\n applyVideoEncodingToConnection,\r\n applyVideoSenderEncoding,\r\n applyVideoTrackContentHint,\r\n type VideoEncodingOptions,\r\n} from './media/video-encoding';\r\nimport {\r\n applyAudioEncodingToConnection,\r\n applyAudioSenderEncoding,\r\n type AudioEncodingOptions,\r\n} from './media/audio-encoding';\r\nimport {\r\n applyH264CodecPreference,\r\n applyH264CodecPreferences,\r\n applyOpusCodecPreference,\r\n applyOpusCodecPreferences,\r\n type PreferredVideoCodec,\r\n type PreferredAudioCodec,\r\n} from './media/codec-preference';\r\nimport { DataManager } from './data/data-manager';\r\nimport type { DataChannelConfig } from './data/types';\r\nimport { StatsMonitor } from './stats/stats-monitor';\r\nimport { TypedEmitter, createLogger, type Logger } from './utils/types';\r\nimport type { SignalChannel } from './signal/types';\r\nimport type {\r\n WetRTCEvent,\r\n WetRTCEventMap,\r\n WetRTCError,\r\n WetRTCErrorCode,\r\n} from './utils/types';\r\n\r\nexport type { ConnectionState };\r\nexport type { WetRTCEvent, WetRTCEventMap, WetRTCError, WetRTCErrorCode };\r\nexport type { SignalChannel };\r\nexport type { VideoEncodingOptions } from './media/video-encoding';\r\nexport type { AudioEncodingOptions } from './media/audio-encoding';\r\nexport type { PreferredVideoCodec, PreferredAudioCodec } from './media/codec-preference';\r\n\r\nexport interface WetRTCConfig {\r\n signal: SignalChannel;\r\n direction?: 'sendonly' | 'recvonly' | 'sendrecv';\r\n iceServers?: RTCIceServer[];\r\n iceTransportPolicy?: RTCIceTransportPolicy;\r\n polite?: boolean;\r\n /** 是否主动发起 SDP 协商;默认 sendonly/sendrecv 为 true,recvonly 为 false */\r\n initiator?: boolean;\r\n dataChannels?: DataChannelConfig[];\r\n signalConfig?: SignalConfig;\r\n statsInterval?: number;\r\n logLevel?: 'debug' | 'info' | 'warn' | 'error' | 'none';\r\n reconnect?: false | {\r\n maxAttempts?: number;\r\n baseDelay?: number;\r\n maxDelay?: number;\r\n backoffMultiplier?: number;\r\n };\r\n /** 发送端视频编码低延迟参数(屏幕共享场景) */\r\n videoEncoding?: VideoEncodingOptions;\r\n /** 视频编解码器偏好;h264 优先使用 H.264(Electron/Chrome 下通常走硬件编码) */\r\n preferredVideoCodec?: PreferredVideoCodec;\r\n /** 发送端音频编码参数(码率、优先级等) */\r\n audioEncoding?: AudioEncodingOptions;\r\n /** 音频编解码器偏好;opus 优先使用 Opus(WebRTC 默认语音编解码器) */\r\n preferredAudioCodec?: PreferredAudioCodec;\r\n}\r\n\r\nconst DEFAULT_ICE_SERVERS: RTCIceServer[] = [\r\n { urls: 'stun:stun.l.google.com:19302' },\r\n];\r\n\r\nconst DEFAULT_RECONNECT = {\r\n maxAttempts: 5,\r\n baseDelay: 1000,\r\n maxDelay: 30_000,\r\n backoffMultiplier: 2,\r\n};\r\n\r\ntype ReconnectOptions = typeof DEFAULT_RECONNECT;\r\n\r\ntype NormalizedConfig = Required<Omit<WetRTCConfig, 'statsInterval' | 'logLevel' | 'iceTransportPolicy' | 'reconnect' | 'initiator' | 'videoEncoding' | 'preferredVideoCodec' | 'audioEncoding' | 'preferredAudioCodec'>> & {\r\n statsInterval: number;\r\n iceTransportPolicy?: RTCIceTransportPolicy;\r\n reconnect: false | ReconnectOptions;\r\n initiator: boolean;\r\n videoEncoding?: VideoEncodingOptions;\r\n preferredVideoCodec: PreferredVideoCodec;\r\n audioEncoding?: AudioEncodingOptions;\r\n preferredAudioCodec: PreferredAudioCodec;\r\n};\r\n\r\nexport class WetRTC implements IDisposable {\r\n readonly fsm: ConnectionStateMachine;\r\n readonly media: MediaManager;\r\n readonly data: DataManager;\r\n readonly stats: StatsMonitor;\r\n\r\n private events = new TypedEmitter<WetRTCEventMap>();\r\n private disposables = new DisposableStack();\r\n private logger: Logger;\r\n private pc: RTCPeerConnection | null = null;\r\n private signalManager: SignalManager;\r\n private config!: NormalizedConfig;\r\n private reconnectAttempt = 0;\r\n private pcListenerCleanup: (() => void) | null = null;\r\n private reconnecting = false;\r\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\r\n private screenStream: MediaStream | null = null;\r\n\r\n constructor(config: WetRTCConfig) {\r\n this.config = this.normalizeConfig(config);\r\n this.logger = createLogger(\r\n config.logLevel === 'debug' ? 0 :\r\n config.logLevel === 'info' ? 1 :\r\n config.logLevel === 'none' ? 4 : 2\r\n );\r\n\r\n this.fsm = new ConnectionStateMachine();\r\n this.media = new MediaManager();\r\n this.pc = this.createPeerConnection();\r\n this.data = new DataManager(this.pc);\r\n this.stats = new StatsMonitor(this.pc, {\r\n interval: this.config.statsInterval || undefined,\r\n });\r\n\r\n this.fsm.onChange((newState, prev) => {\r\n this.events.emit('statechange', newState, prev);\r\n });\r\n\r\n this.signalManager = new SignalManager(\r\n this.config.signal,\r\n this.pc,\r\n this.fsm,\r\n this.config.polite,\r\n this.config.signalConfig,\r\n );\r\n\r\n this.attachPcListeners(this.pc);\r\n\r\n if (this.config.dataChannels.length > 0) {\r\n this.data.configureChannels(this.config.dataChannels);\r\n }\r\n\r\n this.data.onMessage((data, label) => {\r\n const channel = label ? this.data.getChannel(label) : undefined;\r\n this.events.emit('message', data, channel as RTCDataChannel);\r\n });\r\n\r\n this.stats.onStats((snapshot) => {\r\n this.events.emit('stats', snapshot);\r\n });\r\n\r\n this.disposables.push(this.fsm);\r\n this.disposables.push(this.media);\r\n this.disposables.push(this.data);\r\n this.disposables.push(this.stats);\r\n\r\n this.logger.info('WetRTC initialized', { direction: this.config.direction });\r\n }\r\n\r\n async connect(): Promise<void> {\r\n if (!this.fsm.is('idle', 'failed')) {\r\n throw new Error(`Cannot connect in state \"${this.fsm.state}\"`);\r\n }\r\n\r\n this.reconnectAttempt = 0;\r\n return this.doConnect();\r\n }\r\n\r\n async disconnect(): Promise<void> {\r\n this.logger.info('Disconnecting');\r\n this.stats.stop();\r\n this.cancelReconnect();\r\n try {\r\n await this.config.signal.send({ type: 'bye' });\r\n } catch (err) {\r\n this.logger.warn('Failed to send bye:', err);\r\n }\r\n this.rebindPeerConnection();\r\n this.fsm.force('idle');\r\n }\r\n\r\n dispose(): void {\r\n this.logger.info('Disposing WetRTC');\r\n this.stats.stop();\r\n this.cancelReconnect();\r\n this.screenStream?.getTracks().forEach(track => track.stop());\r\n this.screenStream = null;\r\n this.pcListenerCleanup?.();\r\n this.pcListenerCleanup = null;\r\n this.signalManager.dispose();\r\n this.pc?.close();\r\n this.pc = null;\r\n this.disposables.dispose();\r\n this.events.removeAll();\r\n this.fsm.force('disposed');\r\n }\r\n\r\n on<E extends WetRTCEvent>(event: E, handler: WetRTCEventMap[E]): () => void {\r\n return this.events.on(event, handler);\r\n }\r\n\r\n once<E extends WetRTCEvent>(event: E, handler: WetRTCEventMap[E]): void {\r\n this.events.once(event, handler);\r\n }\r\n\r\n off<E extends WetRTCEvent>(event: E, handler: WetRTCEventMap[E]): void {\r\n this.events.off(event, handler);\r\n }\r\n\r\n get state(): ConnectionState {\r\n return this.fsm.state;\r\n }\r\n\r\n get peerConnection(): RTCPeerConnection | null {\r\n return this.pc;\r\n }\r\n\r\n addTrack(track: MediaStreamTrack, stream: MediaStream): void {\r\n if (track.kind === 'video' && this.config.videoEncoding) {\r\n applyVideoTrackContentHint(\r\n track,\r\n this.config.videoEncoding.contentHint ?? 'motion',\r\n );\r\n }\r\n this.media.addTrack(track, stream);\r\n this.addTrackToPeerConnection(track, stream);\r\n }\r\n\r\n removeTrack(track: MediaStreamTrack): void {\r\n const sender = this.pc?.getSenders().find(s => s.track === track);\r\n if (sender) {\r\n this.pc?.removeTrack(sender);\r\n }\r\n this.media.removeTrack(track);\r\n }\r\n\r\n async replaceTrack(\r\n oldTrack: MediaStreamTrack,\r\n newTrack: MediaStreamTrack,\r\n ): Promise<void> {\r\n const sender = this.pc?.getSenders().find(s => s.track === oldTrack);\r\n if (sender) {\r\n await sender.replaceTrack(newTrack);\r\n }\r\n this.media.replaceTrack(oldTrack, newTrack);\r\n }\r\n\r\n async shareScreen(options?: DisplayMediaOptions): Promise<MediaStream> {\r\n try {\r\n const stream = await this.media.getDisplayMedia(options);\r\n this.screenStream = stream;\r\n for (const track of stream.getTracks()) {\r\n this.addTrack(track, stream);\r\n }\r\n stream.getVideoTracks()[0]?.addEventListener('ended', () => {\r\n this.stopScreenShare();\r\n }, { once: true });\r\n return stream;\r\n } catch (err) {\r\n this.emitError('MEDIA_FAILED', (err as Error).message, true);\r\n throw err;\r\n }\r\n }\r\n\r\n stopScreenShare(): void {\r\n if (!this.screenStream) return;\r\n for (const track of this.screenStream.getTracks()) {\r\n this.removeTrack(track);\r\n }\r\n this.screenStream = null;\r\n }\r\n\r\n async switchCamera(deviceId?: string): Promise<MediaStream> {\r\n const stream = await this.media.getUserMedia({\r\n video: deviceId ? { deviceId: { exact: deviceId } } : true,\r\n audio: false,\r\n });\r\n const nextVideo = stream.getVideoTracks()[0];\r\n const currentVideo = this.media.getLocalTracks('video')[0];\r\n if (currentVideo && nextVideo) {\r\n await this.replaceVideoTrack(nextVideo);\r\n } else if (nextVideo) {\r\n this.addTrack(nextVideo, stream);\r\n }\r\n return stream;\r\n }\r\n\r\n async replaceVideoTrack(track: MediaStreamTrack): Promise<void> {\r\n const current = this.media.getLocalTracks('video')[0];\r\n if (!current) {\r\n const stream = new MediaStream([track]);\r\n this.addTrack(track, stream);\r\n return;\r\n }\r\n await this.replaceTrack(current, track);\r\n }\r\n\r\n // ── private ──\r\n\r\n private rebindPeerConnection(): RTCPeerConnection {\r\n this.pcListenerCleanup?.();\r\n this.pc?.close();\r\n\r\n const pc = this.createPeerConnection();\r\n this.pc = pc;\r\n this.signalManager.setPeerConnection(pc);\r\n this.data.setPeerConnection(pc);\r\n this.stats.setPeerConnection(pc);\r\n this.attachPcListeners(pc);\r\n this.restoreLocalTracks(pc);\r\n\r\n if (this.config.dataChannels.length > 0) {\r\n this.data.configureChannels(this.config.dataChannels);\r\n }\r\n\r\n return pc;\r\n }\r\n\r\n private attachPcListeners(pc: RTCPeerConnection): void {\r\n const onTrack = (ev: RTCTrackEvent) => {\r\n if (ev.streams[0]) {\r\n this.media.addRemoteStream(ev.streams[0]);\r\n }\r\n this.events.emit('track', ev);\r\n };\r\n\r\n const onDataChannel = (ev: RTCDataChannelEvent) => {\r\n this.data.registerRemoteChannel(ev);\r\n this.events.emit('datachannel', ev.channel);\r\n };\r\n\r\n const onIceDisconnected = () => {\r\n if (this.pc !== pc) return;\r\n if (pc.iceConnectionState === 'disconnected' && this.config.reconnect !== false) {\r\n this.scheduleReconnect();\r\n }\r\n };\r\n\r\n const onConnectionStateChange = () => {\r\n if (this.pc !== pc || pc.connectionState !== 'connected') return;\r\n if (this.config.videoEncoding) {\r\n void applyVideoEncodingToConnection(pc, this.config.videoEncoding);\r\n }\r\n if (this.config.audioEncoding) {\r\n void applyAudioEncodingToConnection(pc, this.config.audioEncoding);\r\n }\r\n };\r\n\r\n pc.addEventListener('track', onTrack);\r\n pc.addEventListener('datachannel', onDataChannel);\r\n pc.addEventListener('iceconnectionstatechange', onIceDisconnected);\r\n pc.addEventListener('connectionstatechange', onConnectionStateChange);\r\n\r\n this.pcListenerCleanup = () => {\r\n pc.removeEventListener('track', onTrack);\r\n pc.removeEventListener('datachannel', onDataChannel);\r\n pc.removeEventListener('iceconnectionstatechange', onIceDisconnected);\r\n pc.removeEventListener('connectionstatechange', onConnectionStateChange);\r\n };\r\n }\r\n\r\n private restoreLocalTracks(pc: RTCPeerConnection): void {\r\n const stream = this.media.getLocalStream();\r\n if (!stream) return;\r\n for (const track of this.media.getLocalTracks()) {\r\n this.addTrackToPeerConnection(track, stream, pc);\r\n }\r\n }\r\n\r\n private addTrackToPeerConnection(\r\n track: MediaStreamTrack,\r\n stream: MediaStream,\r\n pc = this.pc,\r\n ): void {\r\n if (!pc) return;\r\n const sender = pc.addTrack(track, stream);\r\n const transceiver = pc.getTransceivers().find(t => t.sender === sender);\r\n if (!transceiver) return;\r\n if (this.config.direction === 'sendonly') {\r\n transceiver.direction = 'sendonly';\r\n } else if (this.config.direction === 'sendrecv') {\r\n transceiver.direction = 'sendrecv';\r\n }\r\n\r\n if (track.kind === 'video' && this.config.videoEncoding) {\r\n void applyVideoSenderEncoding(sender, this.config.videoEncoding);\r\n }\r\n\r\n if (track.kind === 'audio' && this.config.audioEncoding) {\r\n void applyAudioSenderEncoding(sender, this.config.audioEncoding);\r\n }\r\n\r\n if (track.kind === 'video' && this.config.preferredVideoCodec === 'h264') {\r\n applyH264CodecPreference(transceiver);\r\n }\r\n\r\n if (track.kind === 'audio' && this.config.preferredAudioCodec === 'opus') {\r\n applyOpusCodecPreference(transceiver);\r\n }\r\n }\r\n\r\n private scheduleReconnect(): void {\r\n if (this.reconnecting) return;\r\n\r\n const { maxAttempts = 5, baseDelay = 1000, maxDelay = 30000, backoffMultiplier = 2 } =\r\n this.config.reconnect === false ? DEFAULT_RECONNECT : this.config.reconnect;\r\n\r\n if (this.reconnectAttempt >= maxAttempts) {\r\n this.logger.error('Max reconnect attempts reached');\r\n this.fsm.transition('failed');\r\n this.emitError('RECONNECT_FAILED', 'Max reconnect attempts reached', false);\r\n return;\r\n }\r\n\r\n this.reconnecting = true;\r\n this.reconnectAttempt++;\r\n const delay = Math.min(\r\n baseDelay * Math.pow(backoffMultiplier, this.reconnectAttempt - 1),\r\n maxDelay,\r\n );\r\n\r\n this.logger.info(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempt}/${maxAttempts})`);\r\n this.events.emit('reconnecting', this.reconnectAttempt, maxAttempts);\r\n\r\n this.reconnectTimer = setTimeout(async () => {\r\n try {\r\n this.stats.stop();\r\n this.rebindPeerConnection();\r\n this.fsm.force('idle');\r\n await this.doConnect();\r\n this.reconnecting = false;\r\n this.reconnectTimer = null;\r\n } catch {\r\n this.reconnecting = false;\r\n this.reconnectTimer = null;\r\n this.scheduleReconnect();\r\n }\r\n }, delay);\r\n }\r\n\r\n private cancelReconnect(): void {\r\n this.reconnecting = false;\r\n if (this.reconnectTimer) {\r\n clearTimeout(this.reconnectTimer);\r\n this.reconnectTimer = null;\r\n }\r\n }\r\n\r\n private ensureTransceivers(): void {\r\n if (!this.pc || this.config.direction !== 'recvonly') return;\r\n if (this.pc.getTransceivers().length > 0) return;\r\n\r\n const videoTransceiver = this.pc.addTransceiver('video', { direction: 'recvonly' });\r\n const audioTransceiver = this.pc.addTransceiver('audio', { direction: 'recvonly' });\r\n\r\n if (this.config.preferredVideoCodec === 'h264') {\r\n applyH264CodecPreference(videoTransceiver);\r\n }\r\n if (this.config.preferredAudioCodec === 'opus') {\r\n applyOpusCodecPreference(audioTransceiver);\r\n }\r\n }\r\n\r\n private async doConnect(): Promise<void> {\r\n try {\r\n this.fsm.transition('signaling');\r\n this.stats.start();\r\n this.ensureTransceivers();\r\n\r\n if (this.config.preferredVideoCodec === 'h264' && this.pc) {\r\n applyH264CodecPreferences(this.pc);\r\n }\r\n if (this.config.preferredAudioCodec === 'opus' && this.pc) {\r\n applyOpusCodecPreferences(this.pc);\r\n }\r\n\r\n if (this.config.initiator) {\r\n this.data.initConfiguredChannels();\r\n await this.signalManager.createOffer();\r\n } else {\r\n this.logger.info('Waiting for remote offer (passive)');\r\n }\r\n } catch (err) {\r\n this.emitError('NEGOTIATION_FAILED', (err as Error).message, true);\r\n throw err;\r\n }\r\n }\r\n\r\n private createPeerConnection(): RTCPeerConnection {\r\n const config: RTCConfiguration = {\r\n iceServers: this.config.iceServers,\r\n };\r\n if (this.config.iceTransportPolicy) {\r\n config.iceTransportPolicy = this.config.iceTransportPolicy;\r\n }\r\n return new RTCPeerConnection(config);\r\n }\r\n\r\n private normalizeConfig(config: WetRTCConfig): NormalizedConfig {\r\n return {\r\n signal: config.signal,\r\n direction: config.direction ?? 'sendrecv',\r\n iceServers: config.iceServers ?? DEFAULT_ICE_SERVERS,\r\n iceTransportPolicy: config.iceTransportPolicy,\r\n polite: config.polite ?? (config.direction === 'sendonly'),\r\n initiator: config.initiator ?? (config.direction !== 'recvonly'),\r\n dataChannels: config.dataChannels ?? [],\r\n signalConfig: config.signalConfig ?? {},\r\n reconnect: config.reconnect === false ? false : { ...DEFAULT_RECONNECT, ...config.reconnect },\r\n statsInterval: config.statsInterval ?? 2000,\r\n videoEncoding: config.videoEncoding,\r\n preferredVideoCodec: config.preferredVideoCodec ?? 'auto',\r\n audioEncoding: config.audioEncoding,\r\n preferredAudioCodec: config.preferredAudioCodec ?? 'auto',\r\n };\r\n }\r\n\r\n private emitError(code: WetRTCErrorCode, message: string, recoverable: boolean): void {\r\n this.events.emit('error', {\r\n code,\r\n message: `[WetRTC] ${code}: ${message}`,\r\n recoverable,\r\n });\r\n }\r\n}\r\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6IiIsIm5hbWVzIjpbXSwiaWdub3JlTGlzdCI6W10sInNvdXJjZXMiOltdLCJzb3VyY2VzQ29udGVudCI6W119"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_vitest = require("vitest");
|
|
3
|
+
var import_codec_preference = require("../media/codec-preference");
|
|
4
|
+
(0, import_vitest.describe)("sortVideoCodecsH264First", () => {
|
|
5
|
+
(0, import_vitest.it)("places H264 codecs before others", () => {
|
|
6
|
+
const codecs = [
|
|
7
|
+
{ mimeType: "video/VP8", clockRate: 9e4, channels: void 0, sdpFmtpLine: void 0 },
|
|
8
|
+
{ mimeType: "video/H264", clockRate: 9e4, channels: void 0, sdpFmtpLine: "level-asymmetry-allowed=1;packetization-mode=0" },
|
|
9
|
+
{ mimeType: "video/VP9", clockRate: 9e4, channels: void 0, sdpFmtpLine: void 0 },
|
|
10
|
+
{ mimeType: "video/H264", clockRate: 9e4, channels: void 0, sdpFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1" }
|
|
11
|
+
];
|
|
12
|
+
const sorted = (0, import_codec_preference.sortVideoCodecsH264First)(codecs);
|
|
13
|
+
(0, import_vitest.expect)(sorted[0].mimeType).toBe("video/H264");
|
|
14
|
+
(0, import_vitest.expect)(sorted[0].sdpFmtpLine).toContain("packetization-mode=1");
|
|
15
|
+
(0, import_vitest.expect)(sorted[1].mimeType).toBe("video/H264");
|
|
16
|
+
(0, import_vitest.expect)(sorted[2].mimeType).toBe("video/VP8");
|
|
17
|
+
(0, import_vitest.expect)(sorted[3].mimeType).toBe("video/VP9");
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
(0, import_vitest.describe)("sortAudioCodecsOpusFirst", () => {
|
|
21
|
+
(0, import_vitest.it)("places Opus codecs before others", () => {
|
|
22
|
+
const codecs = [
|
|
23
|
+
{ mimeType: "audio/PCMU", clockRate: 8e3, channels: 1, sdpFmtpLine: void 0 },
|
|
24
|
+
{ mimeType: "audio/opus", clockRate: 48e3, channels: 2, sdpFmtpLine: "minptime=10;useinbandfec=1" },
|
|
25
|
+
{ mimeType: "audio/PCMA", clockRate: 8e3, channels: 1, sdpFmtpLine: void 0 }
|
|
26
|
+
];
|
|
27
|
+
const sorted = (0, import_codec_preference.sortAudioCodecsOpusFirst)(codecs);
|
|
28
|
+
(0, import_vitest.expect)(sorted[0].mimeType).toBe("audio/opus");
|
|
29
|
+
(0, import_vitest.expect)(sorted[1].mimeType).toBe("audio/PCMU");
|
|
30
|
+
(0, import_vitest.expect)(sorted[2].mimeType).toBe("audio/PCMA");
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=codec-preference.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"mappings":";AAAA,oBAAqC;AACrC,8BAKO;AAAA,IAEP,wBAAS,4BAA4B,MAAM;AACzC,wBAAG,oCAAoC,MAAM;AAC3C,UAAM,SAAS;AAAA,MACb,EAAE,UAAU,aAAa,WAAW,KAAO,UAAU,QAAW,aAAa,OAAU;AAAA,MACvF,EAAE,UAAU,cAAc,WAAW,KAAO,UAAU,QAAW,aAAa,iDAAiD;AAAA,MAC/H,EAAE,UAAU,aAAa,WAAW,KAAO,UAAU,QAAW,aAAa,OAAU;AAAA,MACvF,EAAE,UAAU,cAAc,WAAW,KAAO,UAAU,QAAW,aAAa,iDAAiD;AAAA,IACjI;AAEA,UAAM,aAAS,kDAAyB,MAAM;AAE9C,8BAAO,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,YAAY;AAC5C,8BAAO,OAAO,CAAC,EAAE,WAAW,EAAE,UAAU,sBAAsB;AAC9D,8BAAO,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,YAAY;AAC5C,8BAAO,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,WAAW;AAC3C,8BAAO,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,WAAW;AAAA,EAC7C,CAAC;AACH,CAAC;AAAA,IAED,wBAAS,4BAA4B,MAAM;AACzC,wBAAG,oCAAoC,MAAM;AAC3C,UAAM,SAAS;AAAA,MACb,EAAE,UAAU,cAAc,WAAW,KAAM,UAAU,GAAG,aAAa,OAAU;AAAA,MAC/E,EAAE,UAAU,cAAc,WAAW,MAAO,UAAU,GAAG,aAAa,6BAA6B;AAAA,MACnG,EAAE,UAAU,cAAc,WAAW,KAAM,UAAU,GAAG,aAAa,OAAU;AAAA,IACjF;AAEA,UAAM,aAAS,kDAAyB,MAAM;AAE9C,8BAAO,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,YAAY;AAC5C,8BAAO,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,YAAY;AAC5C,8BAAO,OAAO,CAAC,EAAE,QAAQ,EAAE,KAAK,YAAY;AAAA,EAC9C,CAAC;AACH,CAAC","names":[],"ignoreList":[],"sources":["../../../src/__test__/codec-preference.test.ts"],"sourcesContent":["import { describe, expect, it } from 'vitest';\r\nimport {\r\n sortVideoCodecsH264First,\r\n sortAudioCodecsOpusFirst,\r\n type VideoRtpCodec,\r\n type AudioRtpCodec,\r\n} from '../media/codec-preference';\r\n\r\ndescribe('sortVideoCodecsH264First', () => {\r\n it('places H264 codecs before others', () => {\r\n const codecs = [\r\n { mimeType: 'video/VP8', clockRate: 90000, channels: undefined, sdpFmtpLine: undefined },\r\n { mimeType: 'video/H264', clockRate: 90000, channels: undefined, sdpFmtpLine: 'level-asymmetry-allowed=1;packetization-mode=0' },\r\n { mimeType: 'video/VP9', clockRate: 90000, channels: undefined, sdpFmtpLine: undefined },\r\n { mimeType: 'video/H264', clockRate: 90000, channels: undefined, sdpFmtpLine: 'level-asymmetry-allowed=1;packetization-mode=1' },\r\n ] as VideoRtpCodec[];\r\n\r\n const sorted = sortVideoCodecsH264First(codecs);\r\n\r\n expect(sorted[0].mimeType).toBe('video/H264');\r\n expect(sorted[0].sdpFmtpLine).toContain('packetization-mode=1');\r\n expect(sorted[1].mimeType).toBe('video/H264');\r\n expect(sorted[2].mimeType).toBe('video/VP8');\r\n expect(sorted[3].mimeType).toBe('video/VP9');\r\n });\r\n});\r\n\r\ndescribe('sortAudioCodecsOpusFirst', () => {\r\n it('places Opus codecs before others', () => {\r\n const codecs = [\r\n { mimeType: 'audio/PCMU', clockRate: 8000, channels: 1, sdpFmtpLine: undefined },\r\n { mimeType: 'audio/opus', clockRate: 48000, channels: 2, sdpFmtpLine: 'minptime=10;useinbandfec=1' },\r\n { mimeType: 'audio/PCMA', clockRate: 8000, channels: 1, sdpFmtpLine: undefined },\r\n ] as AudioRtpCodec[];\r\n\r\n const sorted = sortAudioCodecsOpusFirst(codecs);\r\n\r\n expect(sorted[0].mimeType).toBe('audio/opus');\r\n expect(sorted[1].mimeType).toBe('audio/PCMU');\r\n expect(sorted[2].mimeType).toBe('audio/PCMA');\r\n });\r\n});\r\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6IiIsIm5hbWVzIjpbXSwiaWdub3JlTGlzdCI6W10sInNvdXJjZXMiOltdLCJzb3VyY2VzQ29udGVudCI6W119"]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_vitest = require("vitest");
|
|
3
|
+
var import_data_manager = require("../data/data-manager");
|
|
4
|
+
function createChannel(label = "file-test") {
|
|
5
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
6
|
+
return {
|
|
7
|
+
label,
|
|
8
|
+
readyState: "open",
|
|
9
|
+
bufferedAmount: 0,
|
|
10
|
+
bufferedAmountLowThreshold: 0,
|
|
11
|
+
send: import_vitest.vi.fn(),
|
|
12
|
+
close: import_vitest.vi.fn(),
|
|
13
|
+
addEventListener: import_vitest.vi.fn((type, fn) => {
|
|
14
|
+
if (!listeners.has(type))
|
|
15
|
+
listeners.set(type, /* @__PURE__ */ new Set());
|
|
16
|
+
listeners.get(type).add(fn);
|
|
17
|
+
}),
|
|
18
|
+
removeEventListener: import_vitest.vi.fn((type, fn) => {
|
|
19
|
+
listeners.get(type)?.delete(fn);
|
|
20
|
+
}),
|
|
21
|
+
onmessage: null,
|
|
22
|
+
onopen: null,
|
|
23
|
+
onclose: null,
|
|
24
|
+
onerror: null
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function createPc(channel) {
|
|
28
|
+
return {
|
|
29
|
+
createDataChannel: import_vitest.vi.fn(() => channel)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
(0, import_vitest.describe)("DataManager", () => {
|
|
33
|
+
(0, import_vitest.it)("receives a complete file", async () => {
|
|
34
|
+
const channel = createChannel();
|
|
35
|
+
const manager = new import_data_manager.DataManager(createPc(channel));
|
|
36
|
+
const handler = import_vitest.vi.fn();
|
|
37
|
+
manager.onFile(handler);
|
|
38
|
+
manager.createChannel("file-test");
|
|
39
|
+
channel.onmessage?.({ data: JSON.stringify({
|
|
40
|
+
type: "file-meta",
|
|
41
|
+
name: "hello.txt",
|
|
42
|
+
size: 5,
|
|
43
|
+
chunks: 1,
|
|
44
|
+
mimeType: "text/plain"
|
|
45
|
+
}) });
|
|
46
|
+
channel.onmessage?.({ data: new Blob(["hello"]) });
|
|
47
|
+
channel.onmessage?.({ data: JSON.stringify({ type: "file-complete", name: "hello.txt" }) });
|
|
48
|
+
(0, import_vitest.expect)(handler).toHaveBeenCalledTimes(1);
|
|
49
|
+
const [meta, blob] = handler.mock.calls[0];
|
|
50
|
+
(0, import_vitest.expect)(meta).toEqual({ name: "hello.txt", size: 5, type: "text/plain" });
|
|
51
|
+
(0, import_vitest.expect)(await blob.text()).toBe("hello");
|
|
52
|
+
});
|
|
53
|
+
(0, import_vitest.it)("rejects sendFile when file exceeds maxFileSize", async () => {
|
|
54
|
+
const channel = createChannel();
|
|
55
|
+
const manager = new import_data_manager.DataManager(createPc(channel));
|
|
56
|
+
const file = new File(["too large"], "large.txt");
|
|
57
|
+
await (0, import_vitest.expect)(manager.sendFile(file, { maxFileSize: 1 })).rejects.toThrow(/maxFileSize/);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=data-manager.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"mappings":";AAAA,oBAAyC;AACzC,0BAA4B;AAE5B,SAAS,cAAc,QAAQ,aAA6B;AAC1D,QAAM,YAAY,oBAAI,IAA2C;AACjE,SAAO;AAAA,IACL;AAAA,IACA,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,4BAA4B;AAAA,IAC5B,MAAM,iBAAG,GAAG;AAAA,IACZ,OAAO,iBAAG,GAAG;AAAA,IACb,kBAAkB,iBAAG,GAAG,CAAC,MAAc,OAAiC;AACtE,UAAI,CAAC,UAAU,IAAI,IAAI;AAAG,kBAAU,IAAI,MAAM,oBAAI,IAAI,CAAC;AACvD,gBAAU,IAAI,IAAI,EAAG,IAAI,EAAE;AAAA,IAC7B,CAAC;AAAA,IACD,qBAAqB,iBAAG,GAAG,CAAC,MAAc,OAAiC;AACzE,gBAAU,IAAI,IAAI,GAAG,OAAO,EAAE;AAAA,IAChC,CAAC;AAAA,IACD,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,SAAS;AAAA,EACX;AACF;AAEA,SAAS,SAAS,SAA4C;AAC5D,SAAO;AAAA,IACL,mBAAmB,iBAAG,GAAG,MAAM,OAAO;AAAA,EACxC;AACF;AAAA,IAEA,wBAAS,eAAe,MAAM;AAC5B,wBAAG,4BAA4B,YAAY;AACzC,UAAM,UAAU,cAAc;AAC9B,UAAM,UAAU,IAAI,gCAAY,SAAS,OAAO,CAAC;AACjD,UAAM,UAAU,iBAAG,GAAG;AAEtB,YAAQ,OAAO,OAAO;AACtB,YAAQ,cAAc,WAAW;AAEjC,YAAQ,YAAY,EAAE,MAAM,KAAK,UAAU;AAAA,MACzC,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,UAAU;AAAA,IACZ,CAAC,EAAE,CAAiB;AACpB,YAAQ,YAAY,EAAE,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,CAAiB;AACjE,YAAQ,YAAY,EAAE,MAAM,KAAK,UAAU,EAAE,MAAM,iBAAiB,MAAM,YAAY,CAAC,EAAE,CAAiB;AAE1G,8BAAO,OAAO,EAAE,sBAAsB,CAAC;AACvC,UAAM,CAAC,MAAM,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC;AACzC,8BAAO,IAAI,EAAE,QAAQ,EAAE,MAAM,aAAa,MAAM,GAAG,MAAM,aAAa,CAAC;AACvE,8BAAO,MAAM,KAAK,KAAK,CAAC,EAAE,KAAK,OAAO;AAAA,EACxC,CAAC;AAED,wBAAG,kDAAkD,YAAY;AAC/D,UAAM,UAAU,cAAc;AAC9B,UAAM,UAAU,IAAI,gCAAY,SAAS,OAAO,CAAC;AACjD,UAAM,OAAO,IAAI,KAAK,CAAC,WAAW,GAAG,WAAW;AAEhD,cAAM,sBAAO,QAAQ,SAAS,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC,EAAE,QAAQ,QAAQ,aAAa;AAAA,EACxF,CAAC;AACH,CAAC","names":[],"ignoreList":[],"sources":["../../../src/__test__/data-manager.test.ts"],"sourcesContent":["import { describe, expect, it, vi } from 'vitest';\r\nimport { DataManager } from '../data/data-manager';\r\n\r\nfunction createChannel(label = 'file-test'): RTCDataChannel {\r\n const listeners = new Map<string, Set<(...args: any[]) => void>>();\r\n return {\r\n label,\r\n readyState: 'open',\r\n bufferedAmount: 0,\r\n bufferedAmountLowThreshold: 0,\r\n send: vi.fn(),\r\n close: vi.fn(),\r\n addEventListener: vi.fn((type: string, fn: (...args: any[]) => void) => {\r\n if (!listeners.has(type)) listeners.set(type, new Set());\r\n listeners.get(type)!.add(fn);\r\n }),\r\n removeEventListener: vi.fn((type: string, fn: (...args: any[]) => void) => {\r\n listeners.get(type)?.delete(fn);\r\n }),\r\n onmessage: null,\r\n onopen: null,\r\n onclose: null,\r\n onerror: null,\r\n } as unknown as RTCDataChannel;\r\n}\r\n\r\nfunction createPc(channel: RTCDataChannel): RTCPeerConnection {\r\n return {\r\n createDataChannel: vi.fn(() => channel),\r\n } as unknown as RTCPeerConnection;\r\n}\r\n\r\ndescribe('DataManager', () => {\r\n it('receives a complete file', async () => {\r\n const channel = createChannel();\r\n const manager = new DataManager(createPc(channel));\r\n const handler = vi.fn();\r\n\r\n manager.onFile(handler);\r\n manager.createChannel('file-test');\r\n\r\n channel.onmessage?.({ data: JSON.stringify({\r\n type: 'file-meta',\r\n name: 'hello.txt',\r\n size: 5,\r\n chunks: 1,\r\n mimeType: 'text/plain',\r\n }) } as MessageEvent);\r\n channel.onmessage?.({ data: new Blob(['hello']) } as MessageEvent);\r\n channel.onmessage?.({ data: JSON.stringify({ type: 'file-complete', name: 'hello.txt' }) } as MessageEvent);\r\n\r\n expect(handler).toHaveBeenCalledTimes(1);\r\n const [meta, blob] = handler.mock.calls[0];\r\n expect(meta).toEqual({ name: 'hello.txt', size: 5, type: 'text/plain' });\r\n expect(await blob.text()).toBe('hello');\r\n });\r\n\r\n it('rejects sendFile when file exceeds maxFileSize', async () => {\r\n const channel = createChannel();\r\n const manager = new DataManager(createPc(channel));\r\n const file = new File(['too large'], 'large.txt');\r\n\r\n await expect(manager.sendFile(file, { maxFileSize: 1 })).rejects.toThrow(/maxFileSize/);\r\n });\r\n});\r\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6IiIsIm5hbWVzIjpbXSwiaWdub3JlTGlzdCI6W10sInNvdXJjZXMiOltdLCJzb3VyY2VzQ29udGVudCI6W119"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_vitest = require("vitest");
|
|
3
|
+
var import_fsm = require("../fsm");
|
|
4
|
+
(0, import_vitest.describe)("ConnectionStateMachine", () => {
|
|
5
|
+
(0, import_vitest.it)("starts in idle", () => {
|
|
6
|
+
const fsm = new import_fsm.ConnectionStateMachine();
|
|
7
|
+
(0, import_vitest.expect)(fsm.state).toBe("idle");
|
|
8
|
+
});
|
|
9
|
+
(0, import_vitest.it)("allows idle → signaling → connecting → connected", () => {
|
|
10
|
+
const fsm = new import_fsm.ConnectionStateMachine();
|
|
11
|
+
fsm.transition("signaling");
|
|
12
|
+
fsm.transition("connecting");
|
|
13
|
+
fsm.transition("connected");
|
|
14
|
+
(0, import_vitest.expect)(fsm.state).toBe("connected");
|
|
15
|
+
});
|
|
16
|
+
(0, import_vitest.it)("throws on illegal transition idle → connected", () => {
|
|
17
|
+
const fsm = new import_fsm.ConnectionStateMachine();
|
|
18
|
+
(0, import_vitest.expect)(() => fsm.transition("connected")).toThrow(/Invalid state transition/);
|
|
19
|
+
});
|
|
20
|
+
(0, import_vitest.it)("force sets state without validation", () => {
|
|
21
|
+
const fsm = new import_fsm.ConnectionStateMachine();
|
|
22
|
+
fsm.force("connected");
|
|
23
|
+
(0, import_vitest.expect)(fsm.state).toBe("connected");
|
|
24
|
+
});
|
|
25
|
+
(0, import_vitest.it)("failed can return to idle", () => {
|
|
26
|
+
const fsm = new import_fsm.ConnectionStateMachine();
|
|
27
|
+
fsm.transition("signaling");
|
|
28
|
+
fsm.transition("failed");
|
|
29
|
+
fsm.transition("idle");
|
|
30
|
+
(0, import_vitest.expect)(fsm.state).toBe("idle");
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=fsm.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"mappings":";AAAA,oBAAqC;AACrC,iBAAuC;AAAA,IAEvC,wBAAS,0BAA0B,MAAM;AACvC,wBAAG,kBAAkB,MAAM;AACzB,UAAM,MAAM,IAAI,kCAAuB;AACvC,8BAAO,IAAI,KAAK,EAAE,KAAK,MAAM;AAAA,EAC/B,CAAC;AAED,wBAAG,oDAAoD,MAAM;AAC3D,UAAM,MAAM,IAAI,kCAAuB;AACvC,QAAI,WAAW,WAAW;AAC1B,QAAI,WAAW,YAAY;AAC3B,QAAI,WAAW,WAAW;AAC1B,8BAAO,IAAI,KAAK,EAAE,KAAK,WAAW;AAAA,EACpC,CAAC;AAED,wBAAG,iDAAiD,MAAM;AACxD,UAAM,MAAM,IAAI,kCAAuB;AACvC,8BAAO,MAAM,IAAI,WAAW,WAAW,CAAC,EAAE,QAAQ,0BAA0B;AAAA,EAC9E,CAAC;AAED,wBAAG,uCAAuC,MAAM;AAC9C,UAAM,MAAM,IAAI,kCAAuB;AACvC,QAAI,MAAM,WAAW;AACrB,8BAAO,IAAI,KAAK,EAAE,KAAK,WAAW;AAAA,EACpC,CAAC;AAED,wBAAG,6BAA6B,MAAM;AACpC,UAAM,MAAM,IAAI,kCAAuB;AACvC,QAAI,WAAW,WAAW;AAC1B,QAAI,WAAW,QAAQ;AACvB,QAAI,WAAW,MAAM;AACrB,8BAAO,IAAI,KAAK,EAAE,KAAK,MAAM;AAAA,EAC/B,CAAC;AACH,CAAC","names":[],"ignoreList":[],"sources":["../../../src/__test__/fsm.test.ts"],"sourcesContent":["import { describe, it, expect } from 'vitest';\r\nimport { ConnectionStateMachine } from '../fsm';\r\n\r\ndescribe('ConnectionStateMachine', () => {\r\n it('starts in idle', () => {\r\n const fsm = new ConnectionStateMachine();\r\n expect(fsm.state).toBe('idle');\r\n });\r\n\r\n it('allows idle → signaling → connecting → connected', () => {\r\n const fsm = new ConnectionStateMachine();\r\n fsm.transition('signaling');\r\n fsm.transition('connecting');\r\n fsm.transition('connected');\r\n expect(fsm.state).toBe('connected');\r\n });\r\n\r\n it('throws on illegal transition idle → connected', () => {\r\n const fsm = new ConnectionStateMachine();\r\n expect(() => fsm.transition('connected')).toThrow(/Invalid state transition/);\r\n });\r\n\r\n it('force sets state without validation', () => {\r\n const fsm = new ConnectionStateMachine();\r\n fsm.force('connected');\r\n expect(fsm.state).toBe('connected');\r\n });\r\n\r\n it('failed can return to idle', () => {\r\n const fsm = new ConnectionStateMachine();\r\n fsm.transition('signaling');\r\n fsm.transition('failed');\r\n fsm.transition('idle');\r\n expect(fsm.state).toBe('idle');\r\n });\r\n});\r\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6IiIsIm5hbWVzIjpbXSwiaWdub3JlTGlzdCI6W10sInNvdXJjZXMiOltdLCJzb3VyY2VzQ29udGVudCI6W119"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_vitest = require("vitest");
|
|
3
|
+
var import_media_manager = require("../media/media-manager");
|
|
4
|
+
(0, import_vitest.describe)("MediaManager", () => {
|
|
5
|
+
(0, import_vitest.afterEach)(() => {
|
|
6
|
+
import_vitest.vi.unstubAllGlobals();
|
|
7
|
+
});
|
|
8
|
+
(0, import_vitest.it)("maps display options to getDisplayMedia constraints", async () => {
|
|
9
|
+
const stream = {};
|
|
10
|
+
const getDisplayMedia = import_vitest.vi.fn().mockResolvedValue(stream);
|
|
11
|
+
import_vitest.vi.stubGlobal("navigator", {
|
|
12
|
+
mediaDevices: {
|
|
13
|
+
getDisplayMedia,
|
|
14
|
+
getUserMedia: import_vitest.vi.fn()
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
await import_media_manager.MediaManager.getDisplayMedia({
|
|
18
|
+
width: 1920,
|
|
19
|
+
height: 1080,
|
|
20
|
+
frameRate: 30,
|
|
21
|
+
audio: true
|
|
22
|
+
});
|
|
23
|
+
(0, import_vitest.expect)(getDisplayMedia).toHaveBeenCalledWith({
|
|
24
|
+
video: { width: 1920, height: 1080, frameRate: 30 },
|
|
25
|
+
audio: true
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
(0, import_vitest.it)("instance getDisplayMedia delegates to static implementation", async () => {
|
|
29
|
+
const stream = {};
|
|
30
|
+
const getDisplayMedia = import_vitest.vi.fn().mockResolvedValue(stream);
|
|
31
|
+
import_vitest.vi.stubGlobal("navigator", {
|
|
32
|
+
mediaDevices: {
|
|
33
|
+
getDisplayMedia,
|
|
34
|
+
getUserMedia: import_vitest.vi.fn()
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
const media = new import_media_manager.MediaManager();
|
|
38
|
+
await (0, import_vitest.expect)(media.getDisplayMedia({ audio: false })).resolves.toBe(stream);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=media-manager.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"mappings":";AAAA,oBAAoD;AACpD,2BAA6B;AAAA,IAE7B,wBAAS,gBAAgB,MAAM;AAC7B,+BAAU,MAAM;AACd,qBAAG,iBAAiB;AAAA,EACtB,CAAC;AAED,wBAAG,uDAAuD,YAAY;AACpE,UAAM,SAAS,CAAC;AAChB,UAAM,kBAAkB,iBAAG,GAAG,EAAE,kBAAkB,MAAM;AACxD,qBAAG,WAAW,aAAa;AAAA,MACzB,cAAc;AAAA,QACZ;AAAA,QACA,cAAc,iBAAG,GAAG;AAAA,MACtB;AAAA,IACF,CAAC;AAED,UAAM,kCAAa,gBAAgB;AAAA,MACjC,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,OAAO;AAAA,IACT,CAAC;AAED,8BAAO,eAAe,EAAE,qBAAqB;AAAA,MAC3C,OAAO,EAAE,OAAO,MAAM,QAAQ,MAAM,WAAW,GAAG;AAAA,MAClD,OAAO;AAAA,IACT,CAAC;AAAA,EACH,CAAC;AAED,wBAAG,+DAA+D,YAAY;AAC5E,UAAM,SAAS,CAAC;AAChB,UAAM,kBAAkB,iBAAG,GAAG,EAAE,kBAAkB,MAAM;AACxD,qBAAG,WAAW,aAAa;AAAA,MACzB,cAAc;AAAA,QACZ;AAAA,QACA,cAAc,iBAAG,GAAG;AAAA,MACtB;AAAA,IACF,CAAC;AAED,UAAM,QAAQ,IAAI,kCAAa;AAC/B,cAAM,sBAAO,MAAM,gBAAgB,EAAE,OAAO,MAAM,CAAC,CAAC,EAAE,SAAS,KAAK,MAAM;AAAA,EAC5E,CAAC;AACH,CAAC","names":[],"ignoreList":[],"sources":["../../../src/__test__/media-manager.test.ts"],"sourcesContent":["import { afterEach, describe, expect, it, vi } from 'vitest';\r\nimport { MediaManager } from '../media/media-manager';\r\n\r\ndescribe('MediaManager', () => {\r\n afterEach(() => {\r\n vi.unstubAllGlobals();\r\n });\r\n\r\n it('maps display options to getDisplayMedia constraints', async () => {\r\n const stream = {} as MediaStream;\r\n const getDisplayMedia = vi.fn().mockResolvedValue(stream);\r\n vi.stubGlobal('navigator', {\r\n mediaDevices: {\r\n getDisplayMedia,\r\n getUserMedia: vi.fn(),\r\n },\r\n });\r\n\r\n await MediaManager.getDisplayMedia({\r\n width: 1920,\r\n height: 1080,\r\n frameRate: 30,\r\n audio: true,\r\n });\r\n\r\n expect(getDisplayMedia).toHaveBeenCalledWith({\r\n video: { width: 1920, height: 1080, frameRate: 30 },\r\n audio: true,\r\n });\r\n });\r\n\r\n it('instance getDisplayMedia delegates to static implementation', async () => {\r\n const stream = {} as MediaStream;\r\n const getDisplayMedia = vi.fn().mockResolvedValue(stream);\r\n vi.stubGlobal('navigator', {\r\n mediaDevices: {\r\n getDisplayMedia,\r\n getUserMedia: vi.fn(),\r\n },\r\n });\r\n\r\n const media = new MediaManager();\r\n await expect(media.getDisplayMedia({ audio: false })).resolves.toBe(stream);\r\n });\r\n});\r\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6IiIsIm5hbWVzIjpbXSwiaWdub3JlTGlzdCI6W10sInNvdXJjZXMiOltdLCJzb3VyY2VzQ29udGVudCI6W119"]}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_vitest = require("vitest");
|
|
3
|
+
var import_fsm = require("../fsm");
|
|
4
|
+
var import_signal_manager = require("../signal/signal-manager");
|
|
5
|
+
function createMockPc(overrides = {}) {
|
|
6
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
7
|
+
return {
|
|
8
|
+
signalingState: "stable",
|
|
9
|
+
localDescription: null,
|
|
10
|
+
remoteDescription: null,
|
|
11
|
+
iceConnectionState: "new",
|
|
12
|
+
addEventListener: import_vitest.vi.fn((type, fn) => {
|
|
13
|
+
if (!listeners.has(type))
|
|
14
|
+
listeners.set(type, /* @__PURE__ */ new Set());
|
|
15
|
+
listeners.get(type).add(fn);
|
|
16
|
+
}),
|
|
17
|
+
removeEventListener: import_vitest.vi.fn((type, fn) => {
|
|
18
|
+
listeners.get(type)?.delete(fn);
|
|
19
|
+
}),
|
|
20
|
+
createOffer: import_vitest.vi.fn().mockResolvedValue({ type: "offer", sdp: "mock-offer" }),
|
|
21
|
+
createAnswer: import_vitest.vi.fn().mockResolvedValue({ type: "answer", sdp: "mock-answer" }),
|
|
22
|
+
setLocalDescription: import_vitest.vi.fn().mockImplementation(async (desc) => {
|
|
23
|
+
mock.localDescription = desc;
|
|
24
|
+
}),
|
|
25
|
+
setRemoteDescription: import_vitest.vi.fn().mockImplementation(async (desc) => {
|
|
26
|
+
mock.remoteDescription = desc;
|
|
27
|
+
}),
|
|
28
|
+
addIceCandidate: import_vitest.vi.fn().mockResolvedValue(void 0),
|
|
29
|
+
...overrides
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
let mock;
|
|
33
|
+
function createSignalChannel() {
|
|
34
|
+
const handlers = [];
|
|
35
|
+
return {
|
|
36
|
+
handlers,
|
|
37
|
+
send: import_vitest.vi.fn().mockResolvedValue(void 0),
|
|
38
|
+
onMessage(handler) {
|
|
39
|
+
handlers.push(handler);
|
|
40
|
+
return () => {
|
|
41
|
+
const idx = handlers.indexOf(handler);
|
|
42
|
+
if (idx >= 0)
|
|
43
|
+
handlers.splice(idx, 1);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
(0, import_vitest.describe)("SignalManager", () => {
|
|
49
|
+
let fsm;
|
|
50
|
+
let channel;
|
|
51
|
+
(0, import_vitest.beforeEach)(() => {
|
|
52
|
+
mock = createMockPc();
|
|
53
|
+
fsm = new import_fsm.ConnectionStateMachine();
|
|
54
|
+
channel = createSignalChannel();
|
|
55
|
+
});
|
|
56
|
+
(0, import_vitest.it)("createOffer sends offer and enters signaling", async () => {
|
|
57
|
+
const sm = new import_signal_manager.SignalManager(channel, mock, fsm, true);
|
|
58
|
+
await sm.createOffer();
|
|
59
|
+
(0, import_vitest.expect)(channel.send).toHaveBeenCalledWith(
|
|
60
|
+
import_vitest.expect.objectContaining({ type: "offer", sdp: "mock-offer" })
|
|
61
|
+
);
|
|
62
|
+
(0, import_vitest.expect)(fsm.state).toBe("signaling");
|
|
63
|
+
});
|
|
64
|
+
(0, import_vitest.it)("createOffer does not repeat signaling transition", async () => {
|
|
65
|
+
fsm.transition("signaling");
|
|
66
|
+
const sm = new import_signal_manager.SignalManager(channel, mock, fsm, true);
|
|
67
|
+
await (0, import_vitest.expect)(sm.createOffer()).resolves.toBeUndefined();
|
|
68
|
+
(0, import_vitest.expect)(fsm.state).toBe("signaling");
|
|
69
|
+
});
|
|
70
|
+
(0, import_vitest.it)("handleOffer sends answer", async () => {
|
|
71
|
+
const sm = new import_signal_manager.SignalManager(channel, mock, fsm, true);
|
|
72
|
+
await sm.handleOffer("remote-offer-sdp");
|
|
73
|
+
(0, import_vitest.expect)(mock.setRemoteDescription).toHaveBeenCalled();
|
|
74
|
+
(0, import_vitest.expect)(mock.createAnswer).toHaveBeenCalled();
|
|
75
|
+
(0, import_vitest.expect)(channel.send).toHaveBeenCalledWith(
|
|
76
|
+
import_vitest.expect.objectContaining({ type: "answer" })
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
(0, import_vitest.it)("setPeerConnection rebinds without throwing", () => {
|
|
80
|
+
const sm = new import_signal_manager.SignalManager(channel, mock, fsm, true);
|
|
81
|
+
const newPc = createMockPc();
|
|
82
|
+
(0, import_vitest.expect)(() => sm.setPeerConnection(newPc)).not.toThrow();
|
|
83
|
+
});
|
|
84
|
+
(0, import_vitest.it)("impolite peer ignores colliding offer", async () => {
|
|
85
|
+
mock = createMockPc({ signalingState: "have-local-offer" });
|
|
86
|
+
const sm = new import_signal_manager.SignalManager(channel, mock, fsm, false);
|
|
87
|
+
sm.makingOffer = true;
|
|
88
|
+
await sm.handleOffer("colliding-offer");
|
|
89
|
+
(0, import_vitest.expect)(mock.setRemoteDescription).not.toHaveBeenCalled();
|
|
90
|
+
});
|
|
91
|
+
(0, import_vitest.it)("polite peer rolls back colliding offer", async () => {
|
|
92
|
+
mock = createMockPc({ signalingState: "have-local-offer" });
|
|
93
|
+
const sm = new import_signal_manager.SignalManager(channel, mock, fsm, true);
|
|
94
|
+
sm.makingOffer = true;
|
|
95
|
+
await sm.handleOffer("colliding-offer");
|
|
96
|
+
(0, import_vitest.expect)(mock.setLocalDescription).toHaveBeenCalledWith({ type: "rollback" });
|
|
97
|
+
(0, import_vitest.expect)(mock.setRemoteDescription).toHaveBeenCalledWith({ type: "offer", sdp: "colliding-offer" });
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
//# sourceMappingURL=signal-manager.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"mappings":";AAAA,oBAAqD;AACrD,iBAAuC;AACvC,4BAA8B;AAG9B,SAAS,aAAa,YAAwC,CAAC,GAAsB;AACnF,QAAM,YAAY,oBAAI,IAAgC;AACtD,SAAO;AAAA,IACL,gBAAgB;AAAA,IAChB,kBAAkB;AAAA,IAClB,mBAAmB;AAAA,IACnB,oBAAoB;AAAA,IACpB,kBAAkB,iBAAG,GAAG,CAAC,MAAc,OAAsB;AAC3D,UAAI,CAAC,UAAU,IAAI,IAAI;AAAG,kBAAU,IAAI,MAAM,oBAAI,IAAI,CAAC;AACvD,gBAAU,IAAI,IAAI,EAAG,IAAI,EAAE;AAAA,IAC7B,CAAC;AAAA,IACD,qBAAqB,iBAAG,GAAG,CAAC,MAAc,OAAsB;AAC9D,gBAAU,IAAI,IAAI,GAAG,OAAO,EAAE;AAAA,IAChC,CAAC;AAAA,IACD,aAAa,iBAAG,GAAG,EAAE,kBAAkB,EAAE,MAAM,SAAS,KAAK,aAAa,CAAC;AAAA,IAC3E,cAAc,iBAAG,GAAG,EAAE,kBAAkB,EAAE,MAAM,UAAU,KAAK,cAAc,CAAC;AAAA,IAC9E,qBAAqB,iBAAG,GAAG,EAAE,mBAAmB,OAAO,SAAS;AAC9D,MAAC,KAAa,mBAAmB;AAAA,IACnC,CAAC;AAAA,IACD,sBAAsB,iBAAG,GAAG,EAAE,mBAAmB,OAAO,SAAS;AAC/D,MAAC,KAAa,oBAAoB;AAAA,IACpC,CAAC;AAAA,IACD,iBAAiB,iBAAG,GAAG,EAAE,kBAAkB,MAAS;AAAA,IACpD,GAAG;AAAA,EACL;AACF;AAEA,IAAI;AAEJ,SAAS,sBAAmF;AAC1F,QAAM,WAA0C,CAAC;AACjD,SAAO;AAAA,IACL;AAAA,IACA,MAAM,iBAAG,GAAG,EAAE,kBAAkB,MAAS;AAAA,IACzC,UAAU,SAAiC;AACzC,eAAS,KAAK,OAAO;AACrB,aAAO,MAAM;AACX,cAAM,MAAM,SAAS,QAAQ,OAAO;AACpC,YAAI,OAAO;AAAG,mBAAS,OAAO,KAAK,CAAC;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AACF;AAAA,IAEA,wBAAS,iBAAiB,MAAM;AAC9B,MAAI;AACJ,MAAI;AAEJ,gCAAW,MAAM;AACf,WAAO,aAAa;AACpB,UAAM,IAAI,kCAAuB;AACjC,cAAU,oBAAoB;AAAA,EAChC,CAAC;AAED,wBAAG,gDAAgD,YAAY;AAC7D,UAAM,KAAK,IAAI,oCAAc,SAAS,MAAM,KAAK,IAAI;AACrD,UAAM,GAAG,YAAY;AAErB,8BAAO,QAAQ,IAAI,EAAE;AAAA,MACnB,qBAAO,iBAAiB,EAAE,MAAM,SAAS,KAAK,aAAa,CAAC;AAAA,IAC9D;AACA,8BAAO,IAAI,KAAK,EAAE,KAAK,WAAW;AAAA,EACpC,CAAC;AAED,wBAAG,oDAAoD,YAAY;AACjE,QAAI,WAAW,WAAW;AAC1B,UAAM,KAAK,IAAI,oCAAc,SAAS,MAAM,KAAK,IAAI;AAErD,cAAM,sBAAO,GAAG,YAAY,CAAC,EAAE,SAAS,cAAc;AACtD,8BAAO,IAAI,KAAK,EAAE,KAAK,WAAW;AAAA,EACpC,CAAC;AAED,wBAAG,4BAA4B,YAAY;AACzC,UAAM,KAAK,IAAI,oCAAc,SAAS,MAAM,KAAK,IAAI;AACrD,UAAM,GAAG,YAAY,kBAAkB;AAEvC,8BAAO,KAAK,oBAAoB,EAAE,iBAAiB;AACnD,8BAAO,KAAK,YAAY,EAAE,iBAAiB;AAC3C,8BAAO,QAAQ,IAAI,EAAE;AAAA,MACnB,qBAAO,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAAA,IAC5C;AAAA,EACF,CAAC;AAED,wBAAG,8CAA8C,MAAM;AACrD,UAAM,KAAK,IAAI,oCAAc,SAAS,MAAM,KAAK,IAAI;AACrD,UAAM,QAAQ,aAAa;AAC3B,8BAAO,MAAM,GAAG,kBAAkB,KAAK,CAAC,EAAE,IAAI,QAAQ;AAAA,EACxD,CAAC;AAED,wBAAG,yCAAyC,YAAY;AACtD,WAAO,aAAa,EAAE,gBAAgB,mBAAwC,CAAC;AAC/E,UAAM,KAAK,IAAI,oCAAc,SAAS,MAAM,KAAK,KAAK;AACtD,IAAC,GAAW,cAAc;AAE1B,UAAM,GAAG,YAAY,iBAAiB;AAEtC,8BAAO,KAAK,oBAAoB,EAAE,IAAI,iBAAiB;AAAA,EACzD,CAAC;AAED,wBAAG,0CAA0C,YAAY;AACvD,WAAO,aAAa,EAAE,gBAAgB,mBAAwC,CAAC;AAC/E,UAAM,KAAK,IAAI,oCAAc,SAAS,MAAM,KAAK,IAAI;AACrD,IAAC,GAAW,cAAc;AAE1B,UAAM,GAAG,YAAY,iBAAiB;AAEtC,8BAAO,KAAK,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAC1E,8BAAO,KAAK,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,SAAS,KAAK,kBAAkB,CAAC;AAAA,EAClG,CAAC;AACH,CAAC","names":[],"ignoreList":[],"sources":["../../../src/__test__/signal-manager.test.ts"],"sourcesContent":["import { describe, it, expect, vi, beforeEach } from 'vitest';\r\nimport { ConnectionStateMachine } from '../fsm';\r\nimport { SignalManager } from '../signal/signal-manager';\r\nimport type { SignalChannel } from '../signal/types';\r\n\r\nfunction createMockPc(overrides: Partial<RTCPeerConnection> = {}): RTCPeerConnection {\r\n const listeners = new Map<string, Set<EventListener>>();\r\n return {\r\n signalingState: 'stable',\r\n localDescription: null,\r\n remoteDescription: null,\r\n iceConnectionState: 'new',\r\n addEventListener: vi.fn((type: string, fn: EventListener) => {\r\n if (!listeners.has(type)) listeners.set(type, new Set());\r\n listeners.get(type)!.add(fn);\r\n }),\r\n removeEventListener: vi.fn((type: string, fn: EventListener) => {\r\n listeners.get(type)?.delete(fn);\r\n }),\r\n createOffer: vi.fn().mockResolvedValue({ type: 'offer', sdp: 'mock-offer' }),\r\n createAnswer: vi.fn().mockResolvedValue({ type: 'answer', sdp: 'mock-answer' }),\r\n setLocalDescription: vi.fn().mockImplementation(async (desc) => {\r\n (mock as any).localDescription = desc;\r\n }),\r\n setRemoteDescription: vi.fn().mockImplementation(async (desc) => {\r\n (mock as any).remoteDescription = desc;\r\n }),\r\n addIceCandidate: vi.fn().mockResolvedValue(undefined),\r\n ...overrides,\r\n } as unknown as RTCPeerConnection;\r\n}\r\n\r\nlet mock: RTCPeerConnection;\r\n\r\nfunction createSignalChannel(): SignalChannel & { handlers: Array<(msg: unknown) => void> } {\r\n const handlers: Array<(msg: unknown) => void> = [];\r\n return {\r\n handlers,\r\n send: vi.fn().mockResolvedValue(undefined),\r\n onMessage(handler: (msg: unknown) => void) {\r\n handlers.push(handler);\r\n return () => {\r\n const idx = handlers.indexOf(handler);\r\n if (idx >= 0) handlers.splice(idx, 1);\r\n };\r\n },\r\n };\r\n}\r\n\r\ndescribe('SignalManager', () => {\r\n let fsm: ConnectionStateMachine;\r\n let channel: ReturnType<typeof createSignalChannel>;\r\n\r\n beforeEach(() => {\r\n mock = createMockPc();\r\n fsm = new ConnectionStateMachine();\r\n channel = createSignalChannel();\r\n });\r\n\r\n it('createOffer sends offer and enters signaling', async () => {\r\n const sm = new SignalManager(channel, mock, fsm, true);\r\n await sm.createOffer();\r\n\r\n expect(channel.send).toHaveBeenCalledWith(\r\n expect.objectContaining({ type: 'offer', sdp: 'mock-offer' }),\r\n );\r\n expect(fsm.state).toBe('signaling');\r\n });\r\n\r\n it('createOffer does not repeat signaling transition', async () => {\r\n fsm.transition('signaling');\r\n const sm = new SignalManager(channel, mock, fsm, true);\r\n\r\n await expect(sm.createOffer()).resolves.toBeUndefined();\r\n expect(fsm.state).toBe('signaling');\r\n });\r\n\r\n it('handleOffer sends answer', async () => {\r\n const sm = new SignalManager(channel, mock, fsm, true);\r\n await sm.handleOffer('remote-offer-sdp');\r\n\r\n expect(mock.setRemoteDescription).toHaveBeenCalled();\r\n expect(mock.createAnswer).toHaveBeenCalled();\r\n expect(channel.send).toHaveBeenCalledWith(\r\n expect.objectContaining({ type: 'answer' }),\r\n );\r\n });\r\n\r\n it('setPeerConnection rebinds without throwing', () => {\r\n const sm = new SignalManager(channel, mock, fsm, true);\r\n const newPc = createMockPc();\r\n expect(() => sm.setPeerConnection(newPc)).not.toThrow();\r\n });\r\n\r\n it('impolite peer ignores colliding offer', async () => {\r\n mock = createMockPc({ signalingState: 'have-local-offer' as RTCSignalingState });\r\n const sm = new SignalManager(channel, mock, fsm, false);\r\n (sm as any).makingOffer = true;\r\n\r\n await sm.handleOffer('colliding-offer');\r\n\r\n expect(mock.setRemoteDescription).not.toHaveBeenCalled();\r\n });\r\n\r\n it('polite peer rolls back colliding offer', async () => {\r\n mock = createMockPc({ signalingState: 'have-local-offer' as RTCSignalingState });\r\n const sm = new SignalManager(channel, mock, fsm, true);\r\n (sm as any).makingOffer = true;\r\n\r\n await sm.handleOffer('colliding-offer');\r\n\r\n expect(mock.setLocalDescription).toHaveBeenCalledWith({ type: 'rollback' });\r\n expect(mock.setRemoteDescription).toHaveBeenCalledWith({ type: 'offer', sdp: 'colliding-offer' });\r\n });\r\n});\r\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6IiIsIm5hbWVzIjpbXSwiaWdub3JlTGlzdCI6W10sInNvdXJjZXMiOltdLCJzb3VyY2VzQ29udGVudCI6W119"]}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_vitest = require("vitest");
|
|
3
|
+
var import_wetrtc = require("../wetrtc");
|
|
4
|
+
const pcs = [];
|
|
5
|
+
class MockPeerConnection {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.iceConnectionState = "new";
|
|
8
|
+
this.connectionState = "new";
|
|
9
|
+
this.signalingState = "stable";
|
|
10
|
+
this.localDescription = null;
|
|
11
|
+
this.remoteDescription = null;
|
|
12
|
+
this.senders = [];
|
|
13
|
+
this.transceivers = [];
|
|
14
|
+
this.closed = false;
|
|
15
|
+
this.addEventListener = import_vitest.vi.fn();
|
|
16
|
+
this.removeEventListener = import_vitest.vi.fn();
|
|
17
|
+
this.createDataChannel = import_vitest.vi.fn(() => ({ close: import_vitest.vi.fn(), readyState: "open" }));
|
|
18
|
+
this.createOffer = import_vitest.vi.fn().mockResolvedValue({ type: "offer", sdp: "offer" });
|
|
19
|
+
this.createAnswer = import_vitest.vi.fn().mockResolvedValue({ type: "answer", sdp: "answer" });
|
|
20
|
+
this.setLocalDescription = import_vitest.vi.fn().mockImplementation(async (desc) => {
|
|
21
|
+
this.localDescription = desc;
|
|
22
|
+
});
|
|
23
|
+
this.setRemoteDescription = import_vitest.vi.fn().mockImplementation(async (desc) => {
|
|
24
|
+
this.remoteDescription = desc;
|
|
25
|
+
});
|
|
26
|
+
this.addIceCandidate = import_vitest.vi.fn().mockResolvedValue(void 0);
|
|
27
|
+
this.getStats = import_vitest.vi.fn().mockResolvedValue({ forEach: import_vitest.vi.fn() });
|
|
28
|
+
this.close = import_vitest.vi.fn(() => {
|
|
29
|
+
this.closed = true;
|
|
30
|
+
});
|
|
31
|
+
this.getSenders = import_vitest.vi.fn(() => this.senders);
|
|
32
|
+
this.getTransceivers = import_vitest.vi.fn(() => this.transceivers);
|
|
33
|
+
this.addTransceiver = import_vitest.vi.fn((kind, init) => {
|
|
34
|
+
const transceiver = {
|
|
35
|
+
direction: init?.direction ?? "sendrecv",
|
|
36
|
+
sender: {},
|
|
37
|
+
receiver: {},
|
|
38
|
+
mid: null,
|
|
39
|
+
currentDirection: null,
|
|
40
|
+
stop: import_vitest.vi.fn()
|
|
41
|
+
};
|
|
42
|
+
this.transceivers.push(transceiver);
|
|
43
|
+
return transceiver;
|
|
44
|
+
});
|
|
45
|
+
this.addTrack = import_vitest.vi.fn((track) => {
|
|
46
|
+
const sender = {
|
|
47
|
+
track,
|
|
48
|
+
replaceTrack: import_vitest.vi.fn().mockResolvedValue(void 0)
|
|
49
|
+
};
|
|
50
|
+
const transceiver = {
|
|
51
|
+
direction: "sendrecv",
|
|
52
|
+
sender,
|
|
53
|
+
receiver: {},
|
|
54
|
+
mid: null,
|
|
55
|
+
currentDirection: null,
|
|
56
|
+
stop: import_vitest.vi.fn()
|
|
57
|
+
};
|
|
58
|
+
this.senders.push(sender);
|
|
59
|
+
this.transceivers.push(transceiver);
|
|
60
|
+
return sender;
|
|
61
|
+
});
|
|
62
|
+
this.removeTrack = import_vitest.vi.fn();
|
|
63
|
+
pcs.push(this);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function signal() {
|
|
67
|
+
return {
|
|
68
|
+
send: import_vitest.vi.fn().mockResolvedValue(void 0),
|
|
69
|
+
onMessage: import_vitest.vi.fn(() => () => {
|
|
70
|
+
})
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function streamWithTrack(kind = "video") {
|
|
74
|
+
const track = {
|
|
75
|
+
id: `${kind}-1`,
|
|
76
|
+
kind,
|
|
77
|
+
stop: import_vitest.vi.fn(),
|
|
78
|
+
addEventListener: import_vitest.vi.fn()
|
|
79
|
+
};
|
|
80
|
+
const tracks = [track];
|
|
81
|
+
const stream = {
|
|
82
|
+
getTracks: () => tracks,
|
|
83
|
+
getVideoTracks: () => kind === "video" ? tracks : [],
|
|
84
|
+
addTrack: import_vitest.vi.fn((next) => {
|
|
85
|
+
if (!tracks.includes(next))
|
|
86
|
+
tracks.push(next);
|
|
87
|
+
}),
|
|
88
|
+
removeTrack: import_vitest.vi.fn((next) => {
|
|
89
|
+
const idx = tracks.indexOf(next);
|
|
90
|
+
if (idx >= 0)
|
|
91
|
+
tracks.splice(idx, 1);
|
|
92
|
+
})
|
|
93
|
+
};
|
|
94
|
+
return { stream, track };
|
|
95
|
+
}
|
|
96
|
+
(0, import_vitest.describe)("WetRTC lifecycle", () => {
|
|
97
|
+
(0, import_vitest.beforeEach)(() => {
|
|
98
|
+
pcs.length = 0;
|
|
99
|
+
import_vitest.vi.stubGlobal("RTCPeerConnection", MockPeerConnection);
|
|
100
|
+
});
|
|
101
|
+
(0, import_vitest.afterEach)(() => {
|
|
102
|
+
import_vitest.vi.unstubAllGlobals();
|
|
103
|
+
});
|
|
104
|
+
(0, import_vitest.it)("disconnect rebinds peer connection and restores local tracks", async () => {
|
|
105
|
+
const channel = signal();
|
|
106
|
+
const rtc = new import_wetrtc.WetRTC({ signal: channel, reconnect: false });
|
|
107
|
+
const { stream, track } = streamWithTrack();
|
|
108
|
+
const firstPc = rtc.peerConnection;
|
|
109
|
+
rtc.addTrack(track, stream);
|
|
110
|
+
await rtc.disconnect();
|
|
111
|
+
(0, import_vitest.expect)(channel.send).toHaveBeenCalledWith({ type: "bye" });
|
|
112
|
+
const nextPc = rtc.peerConnection;
|
|
113
|
+
(0, import_vitest.expect)(nextPc).not.toBe(firstPc);
|
|
114
|
+
(0, import_vitest.expect)(firstPc.close).toHaveBeenCalled();
|
|
115
|
+
(0, import_vitest.expect)(nextPc.addTrack).toHaveBeenCalledWith(track, stream);
|
|
116
|
+
(0, import_vitest.expect)(rtc.state).toBe("idle");
|
|
117
|
+
});
|
|
118
|
+
(0, import_vitest.it)("recvonly connect creates recvonly transceivers", async () => {
|
|
119
|
+
const rtc = new import_wetrtc.WetRTC({ signal: signal(), direction: "recvonly", reconnect: false });
|
|
120
|
+
await rtc.connect();
|
|
121
|
+
const pc = rtc.peerConnection;
|
|
122
|
+
(0, import_vitest.expect)(pc.addTransceiver).toHaveBeenCalledWith("video", { direction: "recvonly" });
|
|
123
|
+
(0, import_vitest.expect)(pc.addTransceiver).toHaveBeenCalledWith("audio", { direction: "recvonly" });
|
|
124
|
+
});
|
|
125
|
+
(0, import_vitest.it)("recvonly initiator sends offer", async () => {
|
|
126
|
+
const ch = signal();
|
|
127
|
+
const rtc = new import_wetrtc.WetRTC({
|
|
128
|
+
signal: ch,
|
|
129
|
+
direction: "recvonly",
|
|
130
|
+
initiator: true,
|
|
131
|
+
reconnect: false
|
|
132
|
+
});
|
|
133
|
+
await rtc.connect();
|
|
134
|
+
(0, import_vitest.expect)(ch.send).toHaveBeenCalledWith(
|
|
135
|
+
import_vitest.expect.objectContaining({ type: "offer" })
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
(0, import_vitest.it)("sendonly passive does not send offer", async () => {
|
|
139
|
+
const ch = signal();
|
|
140
|
+
const rtc = new import_wetrtc.WetRTC({
|
|
141
|
+
signal: ch,
|
|
142
|
+
direction: "sendonly",
|
|
143
|
+
initiator: false,
|
|
144
|
+
reconnect: false
|
|
145
|
+
});
|
|
146
|
+
await rtc.connect();
|
|
147
|
+
(0, import_vitest.expect)(ch.send).not.toHaveBeenCalledWith(
|
|
148
|
+
import_vitest.expect.objectContaining({ type: "offer" })
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
//# sourceMappingURL=wetrtc-lifecycle.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"mappings":"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","names":[],"ignoreList":[],"sources":["../../../src/__test__/wetrtc-lifecycle.test.ts"],"sourcesContent":["import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';\r\nimport { WetRTC } from '../wetrtc';\r\nimport type { SignalChannel } from '../signal/types';\r\n\r\nconst pcs: MockPeerConnection[] = [];\r\n\r\nclass MockPeerConnection {\r\n iceConnectionState: RTCIceConnectionState = 'new';\r\n connectionState: RTCPeerConnectionState = 'new';\r\n signalingState: RTCSignalingState = 'stable';\r\n localDescription: RTCSessionDescription | null = null;\r\n remoteDescription: RTCSessionDescription | null = null;\r\n senders: RTCRtpSender[] = [];\r\n transceivers: RTCRtpTransceiver[] = [];\r\n closed = false;\r\n\r\n constructor() {\r\n pcs.push(this);\r\n }\r\n\r\n addEventListener = vi.fn();\r\n removeEventListener = vi.fn();\r\n createDataChannel = vi.fn(() => ({ close: vi.fn(), readyState: 'open' }));\r\n createOffer = vi.fn().mockResolvedValue({ type: 'offer', sdp: 'offer' });\r\n createAnswer = vi.fn().mockResolvedValue({ type: 'answer', sdp: 'answer' });\r\n setLocalDescription = vi.fn().mockImplementation(async (desc) => {\r\n this.localDescription = desc as RTCSessionDescription;\r\n });\r\n setRemoteDescription = vi.fn().mockImplementation(async (desc) => {\r\n this.remoteDescription = desc as RTCSessionDescription;\r\n });\r\n addIceCandidate = vi.fn().mockResolvedValue(undefined);\r\n getStats = vi.fn().mockResolvedValue({ forEach: vi.fn() });\r\n close = vi.fn(() => {\r\n this.closed = true;\r\n });\r\n getSenders = vi.fn(() => this.senders);\r\n getTransceivers = vi.fn(() => this.transceivers);\r\n addTransceiver = vi.fn((kind: string, init?: RTCRtpTransceiverInit) => {\r\n const transceiver = {\r\n direction: init?.direction ?? 'sendrecv',\r\n sender: {},\r\n receiver: {},\r\n mid: null,\r\n currentDirection: null,\r\n stop: vi.fn(),\r\n } as unknown as RTCRtpTransceiver;\r\n this.transceivers.push(transceiver);\r\n return transceiver;\r\n });\r\n addTrack = vi.fn((track: MediaStreamTrack) => {\r\n const sender = {\r\n track,\r\n replaceTrack: vi.fn().mockResolvedValue(undefined),\r\n } as unknown as RTCRtpSender;\r\n const transceiver = {\r\n direction: 'sendrecv',\r\n sender,\r\n receiver: {},\r\n mid: null,\r\n currentDirection: null,\r\n stop: vi.fn(),\r\n } as unknown as RTCRtpTransceiver;\r\n this.senders.push(sender);\r\n this.transceivers.push(transceiver);\r\n return sender;\r\n });\r\n removeTrack = vi.fn();\r\n}\r\n\r\nfunction signal(): SignalChannel {\r\n return {\r\n send: vi.fn().mockResolvedValue(undefined),\r\n onMessage: vi.fn(() => () => {}),\r\n };\r\n}\r\n\r\nfunction streamWithTrack(kind: 'audio' | 'video' = 'video'): { stream: MediaStream; track: MediaStreamTrack } {\r\n const track = {\r\n id: `${kind}-1`,\r\n kind,\r\n stop: vi.fn(),\r\n addEventListener: vi.fn(),\r\n } as unknown as MediaStreamTrack;\r\n const tracks: MediaStreamTrack[] = [track];\r\n const stream = {\r\n getTracks: () => tracks,\r\n getVideoTracks: () => kind === 'video' ? tracks : [],\r\n addTrack: vi.fn((next: MediaStreamTrack) => {\r\n if (!tracks.includes(next)) tracks.push(next);\r\n }),\r\n removeTrack: vi.fn((next: MediaStreamTrack) => {\r\n const idx = tracks.indexOf(next);\r\n if (idx >= 0) tracks.splice(idx, 1);\r\n }),\r\n } as unknown as MediaStream;\r\n return { stream, track };\r\n}\r\n\r\ndescribe('WetRTC lifecycle', () => {\r\n beforeEach(() => {\r\n pcs.length = 0;\r\n vi.stubGlobal('RTCPeerConnection', MockPeerConnection);\r\n });\r\n\r\n afterEach(() => {\r\n vi.unstubAllGlobals();\r\n });\r\n\r\n it('disconnect rebinds peer connection and restores local tracks', async () => {\r\n const channel = signal();\r\n const rtc = new WetRTC({ signal: channel, reconnect: false });\r\n const { stream, track } = streamWithTrack();\r\n const firstPc = rtc.peerConnection as unknown as MockPeerConnection;\r\n\r\n rtc.addTrack(track, stream);\r\n await rtc.disconnect();\r\n\r\n expect(channel.send).toHaveBeenCalledWith({ type: 'bye' });\r\n const nextPc = rtc.peerConnection as unknown as MockPeerConnection;\r\n expect(nextPc).not.toBe(firstPc);\r\n expect(firstPc.close).toHaveBeenCalled();\r\n expect(nextPc.addTrack).toHaveBeenCalledWith(track, stream);\r\n expect(rtc.state).toBe('idle');\r\n });\r\n\r\n it('recvonly connect creates recvonly transceivers', async () => {\r\n const rtc = new WetRTC({ signal: signal(), direction: 'recvonly', reconnect: false });\r\n\r\n await rtc.connect();\r\n\r\n const pc = rtc.peerConnection as unknown as MockPeerConnection;\r\n expect(pc.addTransceiver).toHaveBeenCalledWith('video', { direction: 'recvonly' });\r\n expect(pc.addTransceiver).toHaveBeenCalledWith('audio', { direction: 'recvonly' });\r\n });\r\n\r\n it('recvonly initiator sends offer', async () => {\r\n const ch = signal();\r\n const rtc = new WetRTC({\r\n signal: ch,\r\n direction: 'recvonly',\r\n initiator: true,\r\n reconnect: false,\r\n });\r\n\r\n await rtc.connect();\r\n\r\n expect(ch.send).toHaveBeenCalledWith(\r\n expect.objectContaining({ type: 'offer' }),\r\n );\r\n });\r\n\r\n it('sendonly passive does not send offer', async () => {\r\n const ch = signal();\r\n const rtc = new WetRTC({\r\n signal: ch,\r\n direction: 'sendonly',\r\n initiator: false,\r\n reconnect: false,\r\n });\r\n\r\n await rtc.connect();\r\n\r\n expect(ch.send).not.toHaveBeenCalledWith(\r\n expect.objectContaining({ type: 'offer' }),\r\n );\r\n });\r\n});\r\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6IiIsIm5hbWVzIjpbXSwiaWdub3JlTGlzdCI6W10sInNvdXJjZXMiOltdLCJzb3VyY2VzQ29udGVudCI6W119"]}
|