@agatx/serenada-core 0.6.10

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 (156) hide show
  1. package/dist/ConsoleLogger.d.ts +6 -0
  2. package/dist/ConsoleLogger.d.ts.map +1 -0
  3. package/dist/ConsoleLogger.js +21 -0
  4. package/dist/ConsoleLogger.js.map +1 -0
  5. package/dist/RoomWatcher.d.ts +34 -0
  6. package/dist/RoomWatcher.d.ts.map +1 -0
  7. package/dist/RoomWatcher.js +103 -0
  8. package/dist/RoomWatcher.js.map +1 -0
  9. package/dist/SerenadaCore.d.ts +47 -0
  10. package/dist/SerenadaCore.d.ts.map +1 -0
  11. package/dist/SerenadaCore.js +141 -0
  12. package/dist/SerenadaCore.js.map +1 -0
  13. package/dist/SerenadaDiagnostics.d.ts +49 -0
  14. package/dist/SerenadaDiagnostics.d.ts.map +1 -0
  15. package/dist/SerenadaDiagnostics.js +421 -0
  16. package/dist/SerenadaDiagnostics.js.map +1 -0
  17. package/dist/SerenadaServerProvider.d.ts +48 -0
  18. package/dist/SerenadaServerProvider.d.ts.map +1 -0
  19. package/dist/SerenadaServerProvider.js +296 -0
  20. package/dist/SerenadaServerProvider.js.map +1 -0
  21. package/dist/SerenadaSession.d.ts +180 -0
  22. package/dist/SerenadaSession.d.ts.map +1 -0
  23. package/dist/SerenadaSession.js +1082 -0
  24. package/dist/SerenadaSession.js.map +1 -0
  25. package/dist/SignalingProvider.d.ts +132 -0
  26. package/dist/SignalingProvider.d.ts.map +1 -0
  27. package/dist/SignalingProvider.js +50 -0
  28. package/dist/SignalingProvider.js.map +1 -0
  29. package/dist/api/roomApi.d.ts +2 -0
  30. package/dist/api/roomApi.d.ts.map +1 -0
  31. package/dist/api/roomApi.js +14 -0
  32. package/dist/api/roomApi.js.map +1 -0
  33. package/dist/cameraModes.d.ts +13 -0
  34. package/dist/cameraModes.d.ts.map +1 -0
  35. package/dist/cameraModes.js +35 -0
  36. package/dist/cameraModes.js.map +1 -0
  37. package/dist/configValidation.d.ts +10 -0
  38. package/dist/configValidation.d.ts.map +1 -0
  39. package/dist/configValidation.js +24 -0
  40. package/dist/configValidation.js.map +1 -0
  41. package/dist/constants.d.ts +33 -0
  42. package/dist/constants.d.ts.map +1 -0
  43. package/dist/constants.js +65 -0
  44. package/dist/constants.js.map +1 -0
  45. package/dist/formatError.d.ts +3 -0
  46. package/dist/formatError.d.ts.map +1 -0
  47. package/dist/formatError.js +7 -0
  48. package/dist/formatError.js.map +1 -0
  49. package/dist/iceServers.d.ts +2 -0
  50. package/dist/iceServers.d.ts.map +1 -0
  51. package/dist/iceServers.js +21 -0
  52. package/dist/iceServers.js.map +1 -0
  53. package/dist/index.d.ts +55 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +44 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/layout/computeLayout.d.ts +81 -0
  58. package/dist/layout/computeLayout.d.ts.map +1 -0
  59. package/dist/layout/computeLayout.js +380 -0
  60. package/dist/layout/computeLayout.js.map +1 -0
  61. package/dist/media/AudioLevelMonitor.d.ts +51 -0
  62. package/dist/media/AudioLevelMonitor.d.ts.map +1 -0
  63. package/dist/media/AudioLevelMonitor.js +179 -0
  64. package/dist/media/AudioLevelMonitor.js.map +1 -0
  65. package/dist/media/MediaEngine.d.ts +137 -0
  66. package/dist/media/MediaEngine.d.ts.map +1 -0
  67. package/dist/media/MediaEngine.js +1224 -0
  68. package/dist/media/MediaEngine.js.map +1 -0
  69. package/dist/media/callStats.d.ts +16 -0
  70. package/dist/media/callStats.d.ts.map +1 -0
  71. package/dist/media/callStats.js +214 -0
  72. package/dist/media/callStats.js.map +1 -0
  73. package/dist/media/localVideoRecovery.d.ts +16 -0
  74. package/dist/media/localVideoRecovery.d.ts.map +1 -0
  75. package/dist/media/localVideoRecovery.js +14 -0
  76. package/dist/media/localVideoRecovery.js.map +1 -0
  77. package/dist/recoveryStorage.d.ts +33 -0
  78. package/dist/recoveryStorage.d.ts.map +1 -0
  79. package/dist/recoveryStorage.js +88 -0
  80. package/dist/recoveryStorage.js.map +1 -0
  81. package/dist/serverUrls.d.ts +8 -0
  82. package/dist/serverUrls.d.ts.map +1 -0
  83. package/dist/serverUrls.js +65 -0
  84. package/dist/serverUrls.js.map +1 -0
  85. package/dist/signaling/SignalingEngine.d.ts +126 -0
  86. package/dist/signaling/SignalingEngine.d.ts.map +1 -0
  87. package/dist/signaling/SignalingEngine.js +720 -0
  88. package/dist/signaling/SignalingEngine.js.map +1 -0
  89. package/dist/signaling/payloads.d.ts +76 -0
  90. package/dist/signaling/payloads.d.ts.map +1 -0
  91. package/dist/signaling/payloads.js +160 -0
  92. package/dist/signaling/payloads.js.map +1 -0
  93. package/dist/signaling/roomStatuses.d.ts +9 -0
  94. package/dist/signaling/roomStatuses.d.ts.map +1 -0
  95. package/dist/signaling/roomStatuses.js +71 -0
  96. package/dist/signaling/roomStatuses.js.map +1 -0
  97. package/dist/signaling/transportConfig.d.ts +3 -0
  98. package/dist/signaling/transportConfig.d.ts.map +1 -0
  99. package/dist/signaling/transportConfig.js +27 -0
  100. package/dist/signaling/transportConfig.js.map +1 -0
  101. package/dist/signaling/transports/index.d.ts +13 -0
  102. package/dist/signaling/transports/index.d.ts.map +1 -0
  103. package/dist/signaling/transports/index.js +11 -0
  104. package/dist/signaling/transports/index.js.map +1 -0
  105. package/dist/signaling/transports/sse.d.ts +26 -0
  106. package/dist/signaling/transports/sse.d.ts.map +1 -0
  107. package/dist/signaling/transports/sse.js +131 -0
  108. package/dist/signaling/transports/sse.js.map +1 -0
  109. package/dist/signaling/transports/types.d.ts +17 -0
  110. package/dist/signaling/transports/types.d.ts.map +1 -0
  111. package/dist/signaling/transports/types.js +2 -0
  112. package/dist/signaling/transports/types.js.map +1 -0
  113. package/dist/signaling/transports/ws.d.ts +21 -0
  114. package/dist/signaling/transports/ws.d.ts.map +1 -0
  115. package/dist/signaling/transports/ws.js +93 -0
  116. package/dist/signaling/transports/ws.js.map +1 -0
  117. package/dist/signaling/types.d.ts +53 -0
  118. package/dist/signaling/types.d.ts.map +1 -0
  119. package/dist/signaling/types.js +2 -0
  120. package/dist/signaling/types.js.map +1 -0
  121. package/dist/types.d.ts +279 -0
  122. package/dist/types.d.ts.map +1 -0
  123. package/dist/types.js +3 -0
  124. package/dist/types.js.map +1 -0
  125. package/package.json +43 -0
  126. package/src/ConsoleLogger.ts +14 -0
  127. package/src/RoomWatcher.ts +127 -0
  128. package/src/SerenadaCore.ts +163 -0
  129. package/src/SerenadaDiagnostics.ts +485 -0
  130. package/src/SerenadaServerProvider.ts +362 -0
  131. package/src/SerenadaSession.ts +1258 -0
  132. package/src/SignalingProvider.ts +207 -0
  133. package/src/api/roomApi.ts +16 -0
  134. package/src/cameraModes.ts +34 -0
  135. package/src/configValidation.ts +35 -0
  136. package/src/constants.ts +77 -0
  137. package/src/formatError.ts +5 -0
  138. package/src/iceServers.ts +20 -0
  139. package/src/index.ts +155 -0
  140. package/src/layout/computeLayout.ts +639 -0
  141. package/src/media/AudioLevelMonitor.ts +190 -0
  142. package/src/media/MediaEngine.ts +1183 -0
  143. package/src/media/callStats.ts +260 -0
  144. package/src/media/localVideoRecovery.ts +39 -0
  145. package/src/recoveryStorage.ts +101 -0
  146. package/src/serverUrls.ts +69 -0
  147. package/src/signaling/SignalingEngine.ts +762 -0
  148. package/src/signaling/payloads.ts +215 -0
  149. package/src/signaling/roomStatuses.ts +89 -0
  150. package/src/signaling/transportConfig.ts +30 -0
  151. package/src/signaling/transports/index.ts +26 -0
  152. package/src/signaling/transports/sse.ts +146 -0
  153. package/src/signaling/transports/types.ts +19 -0
  154. package/src/signaling/transports/ws.ts +108 -0
  155. package/src/signaling/types.ts +68 -0
  156. package/src/types.ts +299 -0
@@ -0,0 +1,207 @@
1
+ import type { ParticipantConnectionStatus } from './signaling/types.js';
2
+
3
+ export interface ProviderCapabilities {
4
+ handlesReconnection?: boolean;
5
+ }
6
+
7
+ export interface ConnectionInfo {
8
+ transport?: string;
9
+ }
10
+
11
+ export interface JoinOptions {
12
+ reconnectPeerId?: string;
13
+ maxParticipants?: number;
14
+ displayName?: string;
15
+ /**
16
+ * Host-supplied stable identity. Distinct from `peerId`/cid (which is per-call
17
+ * and server-issued) — lets host applications correlate a participant to
18
+ * their own user identity (avatar lookup, telemetry).
19
+ */
20
+ appPeerId?: string;
21
+ }
22
+
23
+ export interface SignalingProviderParticipant {
24
+ peerId: string;
25
+ joinedAt?: number;
26
+ displayName?: string;
27
+ /** Host-supplied stable identity — see {@link JoinOptions.appPeerId}. */
28
+ appPeerId?: string;
29
+ audioEnabled?: boolean;
30
+ videoEnabled?: boolean;
31
+ // Wire-reported signaling transport status. Absent = active.
32
+ connectionStatus?: ParticipantConnectionStatus;
33
+ }
34
+
35
+ export interface JoinedEvent {
36
+ peerId: string;
37
+ participants: SignalingProviderParticipant[];
38
+ hostPeerId?: string;
39
+ maxParticipants?: number;
40
+ }
41
+
42
+ export interface RoomStateEvent {
43
+ participants: SignalingProviderParticipant[];
44
+ hostPeerId?: string;
45
+ maxParticipants?: number;
46
+ }
47
+
48
+ export interface PeerEvent {
49
+ peerId: string;
50
+ joinedAt?: number;
51
+ displayName?: string;
52
+ /** Host-supplied stable identity — see {@link JoinOptions.appPeerId}. */
53
+ appPeerId?: string;
54
+ }
55
+
56
+ export interface PeerMessage {
57
+ from: string;
58
+ type: string;
59
+ payload: unknown;
60
+ }
61
+
62
+ export interface RoomEndedEvent {
63
+ by?: string;
64
+ reason: string;
65
+ }
66
+
67
+ export interface SignalingErrorEvent {
68
+ code: string;
69
+ message: string;
70
+ }
71
+
72
+ /**
73
+ * Server tells an active peer that a previously-suspended peer has reattached
74
+ * AND there was pending negotiation traffic to it during the suspension. The
75
+ * SDK should perform glare-safe fresh negotiation / ICE restart for the named
76
+ * CID. The wire payload field `with` is mapped to the explicit `withCid` here
77
+ * to avoid the JavaScript reserved-word association and to match the Android
78
+ * / iOS event shapes.
79
+ */
80
+ export interface NegotiationDirtyEvent {
81
+ /** The CID that needs fresh renegotiation. */
82
+ withCid: string;
83
+ }
84
+
85
+ /** Server tells the sender it could not deliver a relay because the target had no transport. */
86
+ export interface RelayFailedEvent {
87
+ /** Server-assigned reason code, e.g. `"target_suspended"`. */
88
+ reason: string;
89
+ /** Target CIDs the relay could not reach. */
90
+ targets: string[];
91
+ /** Original signaling type that failed, e.g. `"offer" | "answer" | "ice"`. */
92
+ of?: string;
93
+ }
94
+
95
+ export interface SignalingProviderEventMap {
96
+ connected: ConnectionInfo | undefined;
97
+ disconnected: string | undefined;
98
+ joined: JoinedEvent;
99
+ roomStateUpdated: RoomStateEvent;
100
+ peerJoined: PeerEvent;
101
+ peerLeft: PeerEvent;
102
+ message: PeerMessage;
103
+ roomEnded: RoomEndedEvent;
104
+ error: SignalingErrorEvent;
105
+ iceServersChanged: RTCIceServer[];
106
+ negotiationDirty: NegotiationDirtyEvent;
107
+ relayFailed: RelayFailedEvent;
108
+ }
109
+
110
+ export type SignalingProviderEventName = keyof SignalingProviderEventMap;
111
+
112
+ export interface SignalingProvider {
113
+ readonly version: number;
114
+ readonly capabilities?: ProviderCapabilities;
115
+ connect(): void;
116
+ disconnect(): void;
117
+ joinRoom(roomId: string, options?: JoinOptions): void;
118
+ leaveRoom(): void;
119
+ endRoom(): void;
120
+ sendToPeer(peerId: string, type: string, payload: unknown): void;
121
+ broadcast(type: string, payload: unknown): void;
122
+ getIceServers(): Promise<RTCIceServer[]>;
123
+ /**
124
+ * Optional hook: install a gate that returns `false` to skip a scheduled
125
+ * TURN-credential refresh. Providers without periodic refresh (e.g.,
126
+ * loopback/test) may omit this.
127
+ */
128
+ setTurnRefreshGate?(gate: (() => Promise<boolean>) | null): void;
129
+ on<K extends SignalingProviderEventName>(
130
+ event: K,
131
+ cb: (data: SignalingProviderEventMap[K]) => void,
132
+ ): void;
133
+ off<K extends SignalingProviderEventName>(
134
+ event: K,
135
+ cb: (data: SignalingProviderEventMap[K]) => void,
136
+ ): void;
137
+ }
138
+
139
+ export class SignalingProviderEmitter implements SignalingProvider {
140
+ readonly version = 1;
141
+ readonly capabilities?: ProviderCapabilities;
142
+ private readonly listeners = new Map<SignalingProviderEventName, Set<(data: unknown) => void>>();
143
+
144
+ connect(): void {
145
+ throw new Error('Not implemented');
146
+ }
147
+
148
+ disconnect(): void {
149
+ throw new Error('Not implemented');
150
+ }
151
+
152
+ joinRoom(_roomId: string, _options?: JoinOptions): void {
153
+ throw new Error('Not implemented');
154
+ }
155
+
156
+ leaveRoom(): void {
157
+ throw new Error('Not implemented');
158
+ }
159
+
160
+ endRoom(): void {
161
+ throw new Error('Not implemented');
162
+ }
163
+
164
+ sendToPeer(_peerId: string, _type: string, _payload: unknown): void {
165
+ throw new Error('Not implemented');
166
+ }
167
+
168
+ broadcast(_type: string, _payload: unknown): void {
169
+ throw new Error('Not implemented');
170
+ }
171
+
172
+ async getIceServers(): Promise<RTCIceServer[]> {
173
+ throw new Error('Not implemented');
174
+ }
175
+
176
+ on<K extends SignalingProviderEventName>(
177
+ event: K,
178
+ cb: (data: SignalingProviderEventMap[K]) => void,
179
+ ): void {
180
+ let listeners = this.listeners.get(event);
181
+ if (!listeners) {
182
+ listeners = new Set();
183
+ this.listeners.set(event, listeners);
184
+ }
185
+ listeners.add(cb as (data: unknown) => void);
186
+ }
187
+
188
+ off<K extends SignalingProviderEventName>(
189
+ event: K,
190
+ cb: (data: SignalingProviderEventMap[K]) => void,
191
+ ): void {
192
+ this.listeners.get(event)?.delete(cb as (data: unknown) => void);
193
+ }
194
+
195
+ protected emit<K extends SignalingProviderEventName>(
196
+ event: K,
197
+ data: SignalingProviderEventMap[K],
198
+ ): void {
199
+ const listeners = this.listeners.get(event);
200
+ if (!listeners) {
201
+ return;
202
+ }
203
+ for (const listener of listeners) {
204
+ listener(data);
205
+ }
206
+ }
207
+ }
@@ -0,0 +1,16 @@
1
+ import { buildApiUrl } from '../serverUrls.js';
2
+
3
+ export const createRoomId = async (serverHost: string): Promise<string> => {
4
+ const apiUrl = buildApiUrl(serverHost, '/api/room-id');
5
+
6
+ const res = await fetch(apiUrl, { method: 'POST' });
7
+ if (!res.ok) {
8
+ throw new Error(`Room ID request failed: ${res.status}`);
9
+ }
10
+
11
+ const data = await res.json();
12
+ if (!data?.roomId) {
13
+ throw new Error('Room ID missing from response');
14
+ }
15
+ return data.roomId;
16
+ };
@@ -0,0 +1,34 @@
1
+ import { DEFAULT_CAMERA_MODES, type ConfigurableCameraMode, type SerenadaConfig } from './types.js';
2
+
3
+ /**
4
+ * Resolve the configured {@link SerenadaConfig.cameraModes} list into the set
5
+ * of modes actually available on this platform, in the configured order.
6
+ * Web never supports `composite`, so it is silently dropped. `screenShare`
7
+ * is likewise rejected (screen sharing is controlled separately). Duplicates
8
+ * are dropped, preserving the first occurrence. Returning an empty array is
9
+ * valid and signals that video is disabled entirely.
10
+ */
11
+ export function resolveCameraModes(configured: readonly ConfigurableCameraMode[] | undefined): ConfigurableCameraMode[] {
12
+ const source = configured ?? DEFAULT_CAMERA_MODES;
13
+ const seen = new Set<ConfigurableCameraMode>();
14
+ const result: ConfigurableCameraMode[] = [];
15
+ for (const mode of source) {
16
+ if (mode === 'composite') continue;
17
+ if (mode !== 'selfie' && mode !== 'world') continue;
18
+ if (seen.has(mode)) continue;
19
+ seen.add(mode);
20
+ result.push(mode);
21
+ }
22
+ return result;
23
+ }
24
+
25
+ /** Next mode in the cycle, or `null` if cycling is not possible. */
26
+ export function nextCameraMode(
27
+ modes: readonly ConfigurableCameraMode[],
28
+ current: ConfigurableCameraMode,
29
+ ): ConfigurableCameraMode | null {
30
+ if (modes.length <= 1) return null;
31
+ const index = modes.indexOf(current);
32
+ if (index === -1) return modes[0];
33
+ return modes[(index + 1) % modes.length];
34
+ }
@@ -0,0 +1,35 @@
1
+ import type { SignalingProvider } from './SignalingProvider.js';
2
+ import type { SerenadaConfig } from './types.js';
3
+
4
+ export const SUPPORTED_SIGNALING_PROVIDER_VERSION = 1;
5
+
6
+ export interface ResolvedSerenadaConfig {
7
+ serverHost: string | null;
8
+ signalingProvider: SignalingProvider | null;
9
+ }
10
+
11
+ export function resolveSerenadaConfig(config: SerenadaConfig): ResolvedSerenadaConfig {
12
+ const trimmedHost = typeof config.serverHost === 'string' ? config.serverHost.trim() : '';
13
+ const serverHost = trimmedHost.length > 0 ? trimmedHost : null;
14
+ const signalingProvider = config.signalingProvider ?? null;
15
+
16
+ if (serverHost && signalingProvider) {
17
+ throw new Error('Provide exactly one of serverHost or signalingProvider');
18
+ }
19
+ if (!serverHost && !signalingProvider) {
20
+ throw new Error('Provide exactly one of serverHost or signalingProvider');
21
+ }
22
+ if (signalingProvider && signalingProvider.version !== SUPPORTED_SIGNALING_PROVIDER_VERSION) {
23
+ throw new Error(`Unsupported signalingProvider version: ${signalingProvider.version}`);
24
+ }
25
+
26
+ return { serverHost, signalingProvider };
27
+ }
28
+
29
+ export function requireServerHost(config: SerenadaConfig): string {
30
+ const { serverHost } = resolveSerenadaConfig(config);
31
+ if (!serverHost) {
32
+ throw new Error('requires serverHost');
33
+ }
34
+ return serverHost;
35
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Canonical WebRTC resilience constants shared across all Serenada clients.
3
+ * @internal
4
+ * @module
5
+ */
6
+
7
+ // Signaling
8
+ export const RECONNECT_BACKOFF_BASE_MS = 500;
9
+ export const RECONNECT_BACKOFF_CAP_MS = 5000;
10
+ export const CONNECT_TIMEOUT_MS = 2000;
11
+ export const PING_INTERVAL_MS = 12000;
12
+ export const PONG_MISS_THRESHOLD = 2;
13
+ export const WS_FALLBACK_CONSECUTIVE_FAILURES = 3;
14
+
15
+ // Join
16
+ export const JOIN_CONNECT_KICKSTART_MS = 1200;
17
+ export const JOIN_RECOVERY_MS = 4000;
18
+ export const JOIN_HARD_TIMEOUT_MS = 15000;
19
+
20
+ // Peer Connection
21
+ export const OFFER_TIMEOUT_MS = 8000;
22
+ export const ICE_RESTART_COOLDOWN_MS = 10000;
23
+ export const NON_HOST_FALLBACK_DELAY_MS = 4000;
24
+ export const NON_HOST_FALLBACK_MAX_ATTEMPTS = 2;
25
+ export const ICE_CANDIDATE_BUFFER_MAX = 50;
26
+
27
+ // TURN
28
+ export const TURN_FETCH_TIMEOUT_MS = 2000;
29
+ export const TURN_REFRESH_TRIGGER_RATIO = 0.8;
30
+ export const ICE_FETCH_RETRY_DELAYS_MS = [0, 1000, 2000, 4000];
31
+
32
+ // Session
33
+ export const ENDING_SCREEN_MS = 3000;
34
+
35
+ // Snapshot
36
+ export const SNAPSHOT_PREPARE_TIMEOUT_MS = 2000;
37
+
38
+ // Foreground / Doze recovery
39
+ // After the app returns to foreground from background, the SDK issues a
40
+ // synthetic ping and waits this long for a pong before force-closing the
41
+ // transport and triggering the normal reconnect path.
42
+ export const FOREGROUND_FORCE_PING_TIMEOUT_MS = 2000;
43
+
44
+ // Post-reconnect snapshot resync
45
+ // After signaling reconnects, the SDK waits this long for an authoritative
46
+ // `room_state` snapshot before falling back to firing ICE restart against the
47
+ // last-known peer map.
48
+ export const EPOCH_RESYNC_TIMEOUT_MS = 5000;
49
+
50
+ // Suspended-peer presentation
51
+ // After a remote peer transitions to `signalingStatus="suspended"`, the SDK
52
+ // starts a per-CID UI presentation timer. When this timer expires the
53
+ // participant is flagged `presumedLost=true` so call UIs can move them out
54
+ // of the active grid. The peer connection itself stays open so media can
55
+ // resume immediately if the peer reattaches.
56
+ export const PEER_SUSPENDED_UI_TIMEOUT_MS = 30000;
57
+
58
+ // Server hard-eviction window
59
+ // Mirrors `suspendHardEvictionTimeout` on the Go server. Used SDK-side to
60
+ // compute `estimatedHardEvictionAtMs` for the `signalingState.suspended`
61
+ // surface so apps can render a countdown.
62
+ export const SUSPEND_HARD_EVICTION_TIMEOUT_MS = 600000;
63
+
64
+ // Media-liveness emission cadence
65
+ // Active SDKs broadcast `media_liveness{cids:[..]}` every interval for
66
+ // remote CIDs whose inbound media is currently flowing. The server uses
67
+ // this hint to defer hard-eviction of suspended peers whose media is still
68
+ // being received. 10s leaves headroom under the server's 30s freshness
69
+ // window (`mediaLivenessFreshnessWindow`) for missed emissions.
70
+ export const MEDIA_LIVENESS_INTERVAL_MS = 10000;
71
+
72
+ // Connection Status
73
+ export const CONNECTION_RETRYING_DELAY_MS = 10_000;
74
+
75
+ // Local Video Recovery
76
+ export const LOCAL_VIDEO_RESUME_GAP_MS = 15_000;
77
+ export const LOCAL_VIDEO_HEARTBEAT_INTERVAL_MS = 5_000;
@@ -0,0 +1,5 @@
1
+ /** Safely extract a human-readable message from an unknown catch-block value. */
2
+ export function formatError(err: unknown): string {
3
+ if (err instanceof Error) return err.message;
4
+ return String(err);
5
+ }
@@ -0,0 +1,20 @@
1
+ export function normalizeIceServers(iceServers: RTCIceServer[], turnsOnly: boolean): RTCIceServer[] {
2
+ const normalized: RTCIceServer[] = [];
3
+ for (const iceServer of iceServers) {
4
+ const urls = Array.isArray(iceServer.urls) ? iceServer.urls : [iceServer.urls];
5
+ const filteredUrls = urls.filter((url): url is string => {
6
+ if (typeof url !== 'string' || url.length === 0) {
7
+ return false;
8
+ }
9
+ return !turnsOnly || url.toLowerCase().startsWith('turns:');
10
+ });
11
+ if (filteredUrls.length === 0) {
12
+ continue;
13
+ }
14
+ normalized.push({
15
+ ...iceServer,
16
+ urls: filteredUrls,
17
+ });
18
+ }
19
+ return normalized;
20
+ }
package/src/index.ts ADDED
@@ -0,0 +1,155 @@
1
+ /**
2
+ * @agatx/serenada-core — headless call engine.
3
+ * Vanilla TypeScript — no React dependency.
4
+ */
5
+ export const SERENADA_CORE_VERSION = '0.6.10';
6
+
7
+ // Public API
8
+ export { SerenadaCore } from './SerenadaCore.js';
9
+ export { SerenadaSession } from './SerenadaSession.js';
10
+ export { SerenadaDiagnostics } from './SerenadaDiagnostics.js';
11
+ export { RoomWatcher } from './RoomWatcher.js';
12
+ export { SerenadaServerProvider } from './SerenadaServerProvider.js';
13
+ export { SignalingProviderEmitter } from './SignalingProvider.js';
14
+
15
+ // Factory functions (match documented API)
16
+ import { SerenadaCore as _SerenadaCore } from './SerenadaCore.js';
17
+ import { SerenadaDiagnostics as _SerenadaDiagnostics } from './SerenadaDiagnostics.js';
18
+ import type { SerenadaConfig } from './types.js';
19
+ export function createSerenadaCore(config: SerenadaConfig): _SerenadaCore { return new _SerenadaCore(config); }
20
+ export function createSerenadaDiagnostics(config: SerenadaConfig): _SerenadaDiagnostics { return new _SerenadaDiagnostics(config); }
21
+
22
+ // Public types
23
+ export { DEFAULT_CAMERA_MODES } from './types.js';
24
+ export type {
25
+ CallPhase,
26
+ ConnectionStatus,
27
+ ActiveTransport,
28
+ CameraMode,
29
+ ConfigurableCameraMode,
30
+ MediaCapability,
31
+ PeerConnectionState,
32
+ Participant,
33
+ LocalParticipant,
34
+ CallError,
35
+ CallErrorCode,
36
+ CallState,
37
+ SerenadaConfig,
38
+ CreateRoomResult,
39
+ SerenadaSessionHandle,
40
+ CallStats,
41
+ DiagnosticCheckResult,
42
+ DiagnosticsReport,
43
+ CheckOutcome,
44
+ ConnectivityReport,
45
+ IceProbeReport,
46
+ RoomOccupancy,
47
+ RoomWatcherState,
48
+ SerenadaLogLevel,
49
+ SerenadaLogger,
50
+ } from './types.js';
51
+ export type { RecoveryRecord } from './recoveryStorage.js';
52
+ export type {
53
+ ProviderCapabilities,
54
+ ConnectionInfo,
55
+ JoinOptions,
56
+ SignalingProviderParticipant,
57
+ JoinedEvent,
58
+ RoomStateEvent,
59
+ PeerEvent,
60
+ PeerMessage,
61
+ RoomEndedEvent,
62
+ SignalingErrorEvent,
63
+ SignalingProviderEventMap,
64
+ SignalingProviderEventName,
65
+ SignalingProvider,
66
+ } from './SignalingProvider.js';
67
+
68
+ export { ConsoleSerenadaLogger } from './ConsoleLogger.js';
69
+
70
+ // Public utilities
71
+ export {
72
+ computeLayout, computeStageLayout, clampStageTileAspectRatio,
73
+ MIN_STAGE_TILE_ASPECT, MAX_STAGE_TILE_ASPECT, DEFAULT_STAGE_TILE_ASPECT, STAGE_TILE_GAP_PX,
74
+ } from './layout/computeLayout.js';
75
+ export type {
76
+ CallScene,
77
+ SceneParticipant,
78
+ ContentSource,
79
+ UserLayoutPrefs,
80
+ Insets,
81
+ LayoutMode,
82
+ FitMode,
83
+ LayoutResult,
84
+ TileLayout,
85
+ PipLayout,
86
+ Rect,
87
+ StageTileSpec,
88
+ StageTileLayout,
89
+ StageRowLayout,
90
+ } from './layout/computeLayout.js';
91
+
92
+ /** @internal Signaling types re-exported for advanced usage */
93
+ export type { TransportKind } from './signaling/transports/types.js';
94
+ /** @internal */
95
+ export type { RoomState, SignalingMessage } from './signaling/types.js';
96
+ /** @internal */
97
+ export type {
98
+ JoinedPayload,
99
+ ErrorPayload,
100
+ TurnRefreshedPayload,
101
+ OfferPayload,
102
+ AnswerPayload,
103
+ IceCandidatePayload,
104
+ } from './signaling/payloads.js';
105
+ /** @internal */
106
+ export {
107
+ parseJoinedPayload,
108
+ parseRoomStatePayload,
109
+ parseErrorPayload,
110
+ parseTurnRefreshedPayload,
111
+ parseOfferPayload,
112
+ parseAnswerPayload,
113
+ parseIceCandidatePayload,
114
+ } from './signaling/payloads.js';
115
+ /** @internal */
116
+ export type { RoomStatus, RoomStatuses } from './signaling/roomStatuses.js';
117
+ /** @internal */
118
+ export { getRoomStatusState, mergeRoomStatusesPayload, mergeRoomStatusUpdatePayload } from './signaling/roomStatuses.js';
119
+ /** @internal */
120
+ export { parseTransportOrder } from './signaling/transportConfig.js';
121
+
122
+ /**
123
+ * @internal
124
+ * Resilience constants
125
+ */
126
+ export {
127
+ RECONNECT_BACKOFF_BASE_MS, RECONNECT_BACKOFF_CAP_MS,
128
+ CONNECT_TIMEOUT_MS, PING_INTERVAL_MS, PONG_MISS_THRESHOLD,
129
+ WS_FALLBACK_CONSECUTIVE_FAILURES,
130
+ JOIN_CONNECT_KICKSTART_MS, JOIN_RECOVERY_MS, JOIN_HARD_TIMEOUT_MS,
131
+ OFFER_TIMEOUT_MS, ICE_RESTART_COOLDOWN_MS,
132
+ NON_HOST_FALLBACK_DELAY_MS, NON_HOST_FALLBACK_MAX_ATTEMPTS,
133
+ ICE_CANDIDATE_BUFFER_MAX,
134
+ TURN_FETCH_TIMEOUT_MS, TURN_REFRESH_TRIGGER_RATIO, ICE_FETCH_RETRY_DELAYS_MS,
135
+ SNAPSHOT_PREPARE_TIMEOUT_MS,
136
+ CONNECTION_RETRYING_DELAY_MS,
137
+ LOCAL_VIDEO_RESUME_GAP_MS, LOCAL_VIDEO_HEARTBEAT_INTERVAL_MS,
138
+ } from './constants.js';
139
+
140
+ /** @internal Local video recovery utilities */
141
+ export { shouldForceLocalVideoRefresh, shouldRecoverLocalVideo } from './media/localVideoRecovery.js';
142
+
143
+ // Audio level monitoring (used by call UI for activity indicators)
144
+ export { AudioLevelMonitor } from './media/AudioLevelMonitor.js';
145
+ export type { AudioLevelMonitorOptions } from './media/AudioLevelMonitor.js';
146
+
147
+ /** @internal */
148
+ export { createRoomId } from './api/roomApi.js';
149
+ /** @internal */
150
+ export { buildApiUrl, buildRoomUrl, resolveServerBaseUrl, resolveServerUrls } from './serverUrls.js';
151
+
152
+ /** @internal Advanced host-app usage */
153
+ export { SignalingEngine } from './signaling/SignalingEngine.js';
154
+ /** @internal */
155
+ export type { SignalingEngineConfig } from './signaling/SignalingEngine.js';