@zero-server/observe 0.9.10 → 1.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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/types/webrtc.d.ts +348 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zero-server/observe",
3
- "version": "0.9.10",
3
+ "version": "1.0.0",
4
4
  "description": "Metrics, structured logging, distributed tracing, health checks.",
5
5
  "keywords": [
6
6
  "zero-server",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "sideEffects": false,
47
47
  "peerDependencies": {
48
- "@zero-server/sdk": ">=0.9.10"
48
+ "@zero-server/sdk": ">=1.0.0"
49
49
  },
50
50
  "peerDependenciesMeta": {
51
51
  "@zero-server/sdk": {
package/types/webrtc.d.ts CHANGED
@@ -45,11 +45,17 @@ export interface SignalingHubOptions {
45
45
  originAllowlist?: string[];
46
46
  joinTokenSecret?: string | Buffer;
47
47
  autoCreateRooms?: boolean;
48
+ topology?: 'mesh' | 'sfu' | 'mcu' | 'auto';
49
+ maxMeshPeers?: number;
50
+ sfu?: SfuAdapter | 'memory' | 'mediasoup' | 'livekit' | string;
51
+ sfuOpts?: Record<string, unknown>;
48
52
  }
49
53
 
50
54
  export interface WebRTCOptions extends SignalingHubOptions {
51
55
  path?: string;
52
56
  iceServers?: IceServerConfig[] | 'auto';
57
+ metrics?: unknown;
58
+ tracer?: unknown;
53
59
  }
54
60
 
55
61
  export interface PeerAttachInfo {
@@ -203,11 +209,14 @@ export declare class Room {
203
209
  readonly name: string;
204
210
  readonly hub: SignalingHub | null;
205
211
  isOpen: boolean;
212
+ topology: 'mesh' | 'sfu' | 'mcu';
213
+ topologyMode: 'mesh' | 'sfu' | 'mcu' | 'auto';
206
214
  constructor(name: string, opts?: { hub?: SignalingHub });
207
215
  open(): this;
208
216
  require(fn: (peer: Peer) => boolean | Promise<boolean>): this;
209
217
  canPublish(fn: (peer: Peer) => boolean): this;
210
218
  canSubscribe(fn: (peer: Peer) => boolean): this;
219
+ setTopology(topology: 'mesh' | 'sfu' | 'mcu'): this;
211
220
  readonly size: number;
212
221
  peers(): Peer[];
213
222
  canJoin(peer: Peer): boolean | Promise<boolean>;
@@ -227,14 +236,67 @@ export interface SignalingHubEvents {
227
236
  wireError: (ev: { peer: Peer; code: string }) => void;
228
237
  e2eeKey: (ev: { peer: Peer; room: Room; epoch: number; key: string }) => void;
229
238
  clusterError: (err: Error) => void;
239
+ 'peer:limit:reached': (ev: { room: Room; size: number; limit: number }) => void;
240
+ 'topology:promoted': (ev: { room: Room; from: string; to: string; size: number }) => void;
241
+ 'topology:demoted': (ev: { room: Room; from: string; to: string; size: number }) => void;
242
+ 'topology:changed': (ev: { room: Room; topology: string; previous: string }) => void;
243
+ }
244
+
245
+ export interface HubStatsRoom {
246
+ name: string;
247
+ size: number;
248
+ topology: 'mesh' | 'sfu' | 'mcu';
249
+ topologyMode: 'mesh' | 'sfu' | 'mcu' | 'auto';
250
+ }
251
+
252
+ export interface HubStats {
253
+ topology: 'mesh' | 'sfu' | 'mcu' | 'auto';
254
+ maxMeshPeers: number;
255
+ peers: number;
256
+ rooms: HubStatsRoom[];
257
+ mediaPlane: SfuStats | { error: string } | null;
258
+ }
259
+
260
+ export interface MediaFacade {
261
+ readonly adapter: SfuAdapter | null;
262
+ readonly configured: boolean;
263
+ onEvent(handler: SfuEventHandler): () => void;
264
+ createRouter(opts?: unknown): Promise<SfuRouter>;
265
+ createTransport(router: SfuRouter, peer: SfuPeerInfo): Promise<SfuTransport>;
266
+ produce(transport: SfuTransport, kind: 'audio' | 'video', rtpParams: unknown): Promise<SfuProducer>;
267
+ consume(transport: SfuTransport, producerId: string, rtpCaps: unknown): Promise<SfuConsumer>;
268
+ pauseProducer(producerId: string): Promise<void>;
269
+ resumeProducer(producerId: string): Promise<void>;
270
+ closeRouter(routerId: string): Promise<void>;
271
+ stats(scope?: string): Promise<SfuStats>;
272
+ setConsumerPreferredLayers(consumerId: string, layers: { spatialLayer: number; temporalLayer?: number }): Promise<void>;
273
+ setConsumerPriority(consumerId: string, priority: number): Promise<void>;
274
+ requestKeyFrame(consumerId: string): Promise<void>;
275
+ pauseConsumer(consumerId: string): Promise<void>;
276
+ resumeConsumer(consumerId: string): Promise<void>;
277
+ setTransportBitrates(transportId: string, opts: { initial?: number; min?: number; max?: number; maxIncoming?: number; maxOutgoing?: number }): Promise<void>;
278
+ produceData(transport: SfuTransport, opts?: { label?: string; protocol?: string; ordered?: boolean }): Promise<SfuDataProducer>;
279
+ consumeData(transport: SfuTransport, dataProducerId: string, opts?: { ordered?: boolean }): Promise<SfuDataConsumer>;
280
+ observeAudioLevels(routerId: string, opts?: { interval?: number; threshold?: number; maxEntries?: number }): Promise<SfuObserver>;
281
+ observeActiveSpeaker(routerId: string, opts?: { interval?: number }): Promise<SfuObserver>;
282
+ pipeToRouter(opts: { producerId: string; localRouterId: string; remoteRouter: SfuRouter }): Promise<SfuPipeHandle>;
283
+ getProducerStats(producerId: string): Promise<Array<Record<string, unknown>>>;
284
+ getConsumerStats(consumerId: string): Promise<Array<Record<string, unknown>>>;
285
+ getTransportStats(transportId: string): Promise<Array<Record<string, unknown>>>;
286
+ enableTraceEvent(routerId: string, types: string[]): Promise<void>;
230
287
  }
231
288
 
232
289
  export declare class SignalingHub extends EventEmitter {
233
290
  constructor(opts?: SignalingHubOptions);
234
291
  readonly size: number;
292
+ readonly sfu: SfuAdapter | null;
293
+ readonly media: MediaFacade;
294
+ defaultTopology: 'mesh' | 'sfu' | 'mcu' | 'auto';
295
+ maxMeshPeers: number;
235
296
  room(name: string): Room;
236
297
  rooms(): Room[];
237
298
  attach(transport: PeerTransport, info?: PeerAttachInfo): Peer;
299
+ stats(): Promise<HubStats>;
238
300
  close(): void;
239
301
  on<E extends keyof SignalingHubEvents>(event: E, listener: SignalingHubEvents[E]): this;
240
302
  on(event: string, listener: (...args: unknown[]) => void): this;
@@ -325,16 +387,42 @@ export interface ClusterAdapter {
325
387
 
326
388
  export interface UseClusterOptions {
327
389
  nodeId?: string;
390
+ region?: string;
391
+ loadProbe?: () => (object | Promise<object>);
392
+ loadIntervalMs?: number;
393
+ }
394
+
395
+ export interface ClusterNodeInfo {
396
+ nodeId: string;
397
+ region: string | null;
398
+ load: Record<string, unknown> | null;
399
+ lastSeen: number;
400
+ }
401
+
402
+ export type BridgeSelectorStrategy =
403
+ | 'local-only'
404
+ | 'least-loaded'
405
+ | 'region-aware'
406
+ | 'region-aware-least-loaded';
407
+
408
+ export interface SelectBridgeOptions {
409
+ strategy?: BridgeSelectorStrategy;
410
+ preferRegion?: string | null;
411
+ compare?: (a: ClusterNodeInfo, b: ClusterNodeInfo) => number;
328
412
  }
329
413
 
330
414
  export declare class ClusterCoordinator {
331
415
  readonly hub: SignalingHub;
332
416
  readonly adapter: ClusterAdapter;
333
417
  readonly nodeId: string;
418
+ readonly region: string | null;
334
419
  constructor(hub: SignalingHub, adapter: ClusterAdapter, opts?: UseClusterOptions);
335
420
  locate(peerId: string): { nodeId: string; room: string } | null;
336
421
  routeDirect(toPeerId: string, type: string, payload: object): boolean;
337
422
  fanoutRoom(roomName: string, type: string, payload: object, excludeId?: string): void;
423
+ publishLoad(): Promise<Record<string, unknown> | null>;
424
+ nodes(): ClusterNodeInfo[];
425
+ selectBridge(opts?: SelectBridgeOptions): string;
338
426
  close(): void;
339
427
  }
340
428
 
@@ -349,6 +437,198 @@ export declare class MemoryClusterAdapter implements ClusterAdapter {
349
437
  subscribe(channel: string, handler: (message: unknown) => void): () => void;
350
438
  }
351
439
 
440
+ // ---------------------------------------------------------------------------
441
+ // Cross-node SFU cascade
442
+
443
+ export interface UseCascadeOptions {
444
+ nodeId?: string;
445
+ sfu?: SfuAdapter;
446
+ listenInfo?: Record<string, unknown>;
447
+ enableSrtp?: boolean;
448
+ }
449
+
450
+ export interface CascadeRemoteProducer {
451
+ producerId: string;
452
+ room: string;
453
+ nodeId: string;
454
+ routerId: string;
455
+ kind: string;
456
+ rtpParameters: Record<string, unknown> | null;
457
+ }
458
+
459
+ export interface CascadeBridgeStats {
460
+ room: string;
461
+ routerId: string;
462
+ localProducers: number;
463
+ peers: number;
464
+ pipes: number;
465
+ }
466
+
467
+ export interface CascadeStats {
468
+ nodeId: string;
469
+ bridges: CascadeBridgeStats[];
470
+ remoteProducers: number;
471
+ }
472
+
473
+ export declare class CascadeCoordinator {
474
+ readonly hub: SignalingHub;
475
+ readonly sfu: SfuAdapter;
476
+ readonly nodeId: string;
477
+ readonly listenInfo: Record<string, unknown>;
478
+ readonly enableSrtp: boolean;
479
+ constructor(hub: SignalingHub, opts?: UseCascadeOptions);
480
+ registerLocalBridge(roomName: string, router: { id?: string; routerId?: string }): unknown;
481
+ closeLocalBridge(roomName: string): void;
482
+ announceProducer(roomName: string, producer: { id?: string; producerId?: string; kind?: string; rtpParameters?: unknown }): void;
483
+ retractProducer(roomName: string, producerId: string): void;
484
+ locateRemoteProducer(producerId: string): CascadeRemoteProducer | null;
485
+ stats(): CascadeStats;
486
+ close(): void;
487
+ }
488
+
489
+ export declare function useCascade(hub: SignalingHub, opts?: UseCascadeOptions): CascadeCoordinator;
490
+
491
+ export declare const CH_CASCADE: string;
492
+
493
+ // ---------------------------------------------------------------------------
494
+ // MCU (multipoint control unit)
495
+
496
+ export type McuLayout = 'grid' | 'presenter' | 'presenter-strip' | 'dominant' | 'pip' | 'audio-only' | string;
497
+
498
+ export interface McuMixOptions {
499
+ producerIds?: string[];
500
+ kind?: 'audio' | 'video' | 'av';
501
+ layout?: McuLayout | { name: string };
502
+ inputs?: Array<Record<string, unknown>>;
503
+ outputs?: Array<Record<string, unknown>>;
504
+ output?: Record<string, unknown>;
505
+ args?: string[];
506
+ }
507
+
508
+ export interface McuMixResult {
509
+ mixedProducerId: string;
510
+ kind: string;
511
+ layout: McuLayout;
512
+ sources: string[];
513
+ pid?: number;
514
+ }
515
+
516
+ export interface McuStats {
517
+ mixes: Array<{
518
+ id: string;
519
+ room?: string;
520
+ sources: string[];
521
+ layout: McuLayout;
522
+ kind: string;
523
+ pid?: number;
524
+ exited?: boolean;
525
+ }>;
526
+ }
527
+
528
+ export declare class McuAdapter {
529
+ readonly sfu: SfuAdapter | null;
530
+ readonly name: string;
531
+ constructor(opts?: { sfu?: SfuAdapter; name?: string });
532
+ mix(roomId: string, opts?: McuMixOptions): Promise<McuMixResult>;
533
+ unmix(mixedProducerId: string): Promise<boolean>;
534
+ setLayout(mixedProducerId: string, layout: McuLayout | { name: string }): Promise<McuLayout>;
535
+ addSource(mixedProducerId: string, producerId: string): Promise<number>;
536
+ removeSource(mixedProducerId: string, producerId: string): Promise<number>;
537
+ stats(): McuStats;
538
+ close(): Promise<void>;
539
+ }
540
+
541
+ export declare class MemoryMcuAdapter extends McuAdapter {}
542
+
543
+ export declare class FfmpegMcuAdapter extends McuAdapter {
544
+ constructor(opts?: { sfu?: SfuAdapter; ffmpegPath?: string; spawn?: (...args: unknown[]) => unknown });
545
+ }
546
+
547
+ // ---------------------------------------------------------------------------
548
+ // Recording / Egress / Ingress facade
549
+ // ---------------------------------------------------------------------------
550
+
551
+ export type RecordingPipeline = 'livekit' | 'livekit-track' | 'ffmpeg' | 'memory';
552
+
553
+ export interface RecordingStartOptions {
554
+ pipeline?: RecordingPipeline;
555
+ kind?: string;
556
+ layout?: 'grid' | 'presenter' | 'presenter-strip' | 'dominant' | 'speaker' | 'audio-only' | string;
557
+ format?: 'mp4' | 'webm' | 'mka' | 'ogg' | 'hls' | string;
558
+ sink?: { file?: string; url?: string; format?: string };
559
+ inputs?: Array<{ sdp?: string; url?: string }>;
560
+ args?: string[];
561
+ trackId?: string;
562
+ [extra: string]: unknown;
563
+ }
564
+
565
+ export interface RecordingInfo {
566
+ id: string;
567
+ roomName: string;
568
+ kind: string;
569
+ backend: RecordingPipeline | string;
570
+ status: 'starting' | 'recording' | 'stopping' | 'stopped' | 'failed';
571
+ startedAt: number;
572
+ stoppedAt: number | null;
573
+ pid?: number;
574
+ native: { id: string | null } | null;
575
+ error: string | null;
576
+ }
577
+
578
+ export interface RecordingHandle {
579
+ id: string;
580
+ status: string;
581
+ stop(): Promise<boolean>;
582
+ info(): RecordingInfo;
583
+ }
584
+
585
+ export interface RecordingStats {
586
+ recording: number;
587
+ stopped: number;
588
+ failed: number;
589
+ total: number;
590
+ }
591
+
592
+ export declare class RecordingManager {
593
+ constructor(opts: { adapter: SfuAdapter | object; spawn?: (...args: unknown[]) => unknown; ffmpegPath?: string });
594
+ startRecording(roomName: string, opts?: RecordingStartOptions): Promise<RecordingHandle>;
595
+ stopRecording(id: string): Promise<boolean>;
596
+ list(): RecordingInfo[];
597
+ stats(): RecordingStats;
598
+ close(): Promise<void>;
599
+ }
600
+
601
+ export interface IngressStartOptions {
602
+ kind?: 'rtmp' | 'whip' | 'url-pull' | 'sip' | string;
603
+ inputType?: string;
604
+ roomName?: string;
605
+ room?: string;
606
+ name?: string;
607
+ [extra: string]: unknown;
608
+ }
609
+
610
+ export interface IngressInfo {
611
+ id: string;
612
+ kind: string;
613
+ roomName: string | null;
614
+ createdAt: number;
615
+ native: { id: string | null; url: string | null } | null;
616
+ }
617
+
618
+ export interface IngressHandle {
619
+ id: string;
620
+ native: unknown;
621
+ info(): IngressInfo;
622
+ }
623
+
624
+ export declare class IngressManager {
625
+ constructor(opts: { adapter: SfuAdapter | object });
626
+ createIngress(opts: IngressStartOptions): Promise<IngressHandle>;
627
+ deleteIngress(id: string): Promise<boolean>;
628
+ list(): IngressInfo[];
629
+ close(): Promise<void>;
630
+ }
631
+
352
632
  // ---------------------------------------------------------------------------
353
633
  // CLI
354
634
  // ---------------------------------------------------------------------------
@@ -408,6 +688,46 @@ export interface SfuStats {
408
688
  [key: string]: unknown;
409
689
  }
410
690
 
691
+ export interface SfuDataProducer {
692
+ id: string;
693
+ dataProducerId: string;
694
+ transportId: string;
695
+ label: string;
696
+ protocol: string;
697
+ ordered: boolean;
698
+ }
699
+
700
+ export interface SfuDataConsumer {
701
+ id: string;
702
+ dataConsumerId: string;
703
+ transportId: string;
704
+ dataProducerId: string;
705
+ label: string;
706
+ protocol: string;
707
+ ordered: boolean;
708
+ }
709
+
710
+ export interface SfuObserver {
711
+ id: string;
712
+ routerId: string;
713
+ kind: 'audio-level' | 'active-speaker';
714
+ interval: number;
715
+ threshold?: number;
716
+ maxEntries?: number;
717
+ close(): void;
718
+ emit(payload: unknown): void;
719
+ }
720
+
721
+ export interface SfuPipeHandle {
722
+ id: string;
723
+ pipeId: string;
724
+ producerId: string;
725
+ localRouterId: string;
726
+ remoteRouterId: string;
727
+ pipeProducerId: string;
728
+ pipeConsumerId: string;
729
+ }
730
+
411
731
  export type SfuEventHandler = (event: string, payload: unknown) => void;
412
732
 
413
733
  export declare class SfuAdapter {
@@ -420,6 +740,21 @@ export declare class SfuAdapter {
420
740
  resumeProducer(producerId: string): Promise<void>;
421
741
  closeRouter(routerId: string): Promise<void>;
422
742
  stats(scope?: string): Promise<SfuStats>;
743
+ setConsumerPreferredLayers(consumerId: string, layers: { spatialLayer: number; temporalLayer?: number }): Promise<void>;
744
+ setConsumerPriority(consumerId: string, priority: number): Promise<void>;
745
+ requestKeyFrame(consumerId: string): Promise<void>;
746
+ pauseConsumer(consumerId: string): Promise<void>;
747
+ resumeConsumer(consumerId: string): Promise<void>;
748
+ setTransportBitrates(transportId: string, opts: { initial?: number; min?: number; max?: number; maxIncoming?: number; maxOutgoing?: number }): Promise<void>;
749
+ produceData(transport: SfuTransport, opts?: { label?: string; protocol?: string; ordered?: boolean }): Promise<SfuDataProducer>;
750
+ consumeData(transport: SfuTransport, dataProducerId: string, opts?: { ordered?: boolean }): Promise<SfuDataConsumer>;
751
+ observeAudioLevels(routerId: string, opts?: { interval?: number; threshold?: number; maxEntries?: number }): Promise<SfuObserver>;
752
+ observeActiveSpeaker(routerId: string, opts?: { interval?: number }): Promise<SfuObserver>;
753
+ pipeToRouter(opts: { producerId: string; localRouterId: string; remoteRouter: SfuRouter }): Promise<SfuPipeHandle>;
754
+ getProducerStats(producerId: string): Promise<Array<Record<string, unknown>>>;
755
+ getConsumerStats(consumerId: string): Promise<Array<Record<string, unknown>>>;
756
+ getTransportStats(transportId: string): Promise<Array<Record<string, unknown>>>;
757
+ enableTraceEvent(routerId: string, types: string[]): Promise<void>;
423
758
  onEvent(handler: SfuEventHandler): () => void;
424
759
  }
425
760
 
@@ -433,6 +768,8 @@ export interface MediasoupAdapterOptions {
433
768
  workerSettings?: Record<string, unknown>;
434
769
  mediaCodecs?: Array<Record<string, unknown>>;
435
770
  webRtcTransportOptions?: Record<string, unknown>;
771
+ webRtcServer?: unknown;
772
+ webRtcServerOptions?: Record<string, unknown>;
436
773
  }
437
774
 
438
775
  export declare class MediasoupSfuAdapter extends SfuAdapter {
@@ -453,6 +790,17 @@ export interface LiveKitAdapterOptions {
453
790
 
454
791
  export declare class LiveKitSfuAdapter extends SfuAdapter {
455
792
  constructor(opts: LiveKitAdapterOptions);
793
+ getRoomInfo(routerId: string): Promise<Record<string, unknown> | null>;
794
+ listParticipants(routerId: string): Promise<Array<Record<string, unknown>>>;
795
+ removeParticipant(routerId: string, identity: string): Promise<void>;
796
+ updateRoomMetadata(routerId: string, metadata: string | Record<string, unknown>): Promise<void>;
797
+ sendData(routerId: string, payload: unknown, opts?: { kind?: number; destinationIdentities?: string[]; destinations?: string[] }): Promise<void>;
798
+ startRoomCompositeEgress(routerId: string, opts?: Record<string, unknown>): Promise<Record<string, unknown>>;
799
+ startTrackEgress(routerId: string, trackId: string, opts?: Record<string, unknown>): Promise<Record<string, unknown>>;
800
+ stopEgress(egressId: string): Promise<Record<string, unknown>>;
801
+ listEgress(opts?: Record<string, unknown>): Promise<Array<Record<string, unknown>>>;
802
+ createIngress(opts: Record<string, unknown>): Promise<Record<string, unknown>>;
803
+ deleteIngress(ingressId: string): Promise<Record<string, unknown>>;
456
804
  }
457
805
 
458
806
  export declare function loadSfuAdapter(