@craftedxp/voice-js 0.6.0 → 0.10.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/dist/room.d.ts ADDED
@@ -0,0 +1,156 @@
1
+ import { RemoteTrack, LocalVideoTrack } from 'livekit-client';
2
+
3
+ type SystemMessage = {
4
+ kind: 'room.starting';
5
+ at: string;
6
+ } | {
7
+ kind: 'room.ending.soon';
8
+ minutesRemaining: 5 | 1;
9
+ } | {
10
+ kind: 'room.ended';
11
+ reason: 'duration_reached' | 'manual' | 'empty';
12
+ } | {
13
+ kind: 'role.promoted';
14
+ participantId: string;
15
+ name: string;
16
+ } | {
17
+ kind: 'role.demoted';
18
+ participantId: string;
19
+ name: string;
20
+ } | {
21
+ kind: 'participant.removed';
22
+ participantId: string;
23
+ name: string;
24
+ byHost?: string;
25
+ } | {
26
+ kind: 'notetaker.connected';
27
+ } | {
28
+ kind: 'notetaker.disconnected';
29
+ } | {
30
+ kind: 'notetaker.partial_degraded';
31
+ participantId: string;
32
+ };
33
+ type TranscriptMessage = {
34
+ kind: 'partial';
35
+ participantId: string;
36
+ speakerName: string;
37
+ text: string;
38
+ startedAt: string;
39
+ };
40
+ type AnalysisMessage = {
41
+ kind: 'analysis';
42
+ participantId: string;
43
+ label: string;
44
+ score?: number;
45
+ note?: string;
46
+ producer?: string;
47
+ };
48
+
49
+ interface JoinRoomOptions {
50
+ /** Full HTTPS URL of the Voissia server. Same shape as VoiceClientConfig.apiBase. */
51
+ apiBase: string;
52
+ /** Server-generated room id (`rm_…`). */
53
+ roomId: string;
54
+ /** Shared room join token from the invite link. Mints a fresh participant each call. */
55
+ joinCode: string;
56
+ /** Display name the joiner registers under for this participant. */
57
+ name: string;
58
+ }
59
+ interface RoomParticipantInfo {
60
+ /** Stable participant id (`p_…`); strips any `guest:` LiveKit identity prefix. */
61
+ participantId: string;
62
+ /** Display name as the worker registered it; may be empty. */
63
+ name: string;
64
+ }
65
+ type RoomTrackKind = 'audio' | 'video';
66
+ /** What a track is — lets consumers tell a camera apart from a screen share
67
+ * (a participant can publish both at once). Mirrors livekit `Track.Source`. */
68
+ type RoomTrackSource = 'camera' | 'microphone' | 'screen_share' | 'screen_share_audio' | 'unknown';
69
+ interface RoomTrackEvent {
70
+ /** Stable participant id (`p_…`); `guest:` prefix stripped. */
71
+ participantId: string;
72
+ kind: RoomTrackKind;
73
+ /** Distinguishes camera vs screen_share so each can render as its own tile. */
74
+ source: RoomTrackSource;
75
+ /** livekit-client track — call `.attach(el)` / `.detach()` to render. */
76
+ track: RemoteTrack;
77
+ }
78
+ type RoomEventName = 'participant.joined' | 'participant.left' | 'transcript.partial' | 'transcript.final' | 'system.message' | 'room.ended' | 'track.subscribed' | 'track.unsubscribed' | 'active.speakers' | 'analysis';
79
+ interface RoomEventPayloads {
80
+ 'participant.joined': RoomParticipantInfo;
81
+ 'participant.left': RoomParticipantInfo;
82
+ 'transcript.partial': TranscriptMessage;
83
+ /**
84
+ * Reserved — the worker currently emits only partials over the transcript
85
+ * topic. Final-utterance events will land in Phase 8 once the worker
86
+ * publishes a `final` kind; the SDK keeps the slot reserved so consumers
87
+ * can register handlers today.
88
+ */
89
+ 'transcript.final': {
90
+ participantId: string;
91
+ speakerName: string;
92
+ text: string;
93
+ startedAt: string;
94
+ };
95
+ 'system.message': SystemMessage;
96
+ 'room.ended': undefined;
97
+ 'track.subscribed': RoomTrackEvent;
98
+ 'track.unsubscribed': RoomTrackEvent;
99
+ /** participantIds currently speaking (drives an active-speaker UI). */
100
+ 'active.speakers': string[];
101
+ /** Live analysis result (e.g. hirability). Delivered only to host
102
+ * participants by the worker; non-host participants never receive it. */
103
+ analysis: AnalysisMessage;
104
+ }
105
+ type Handler<E extends RoomEventName> = (payload: RoomEventPayloads[E]) => void;
106
+ interface RoomSession {
107
+ /** This session's own stable participant id (`p_…`). Useful to filter
108
+ * yourself out of `active.speakers`, which includes the local participant. */
109
+ readonly participantId: string;
110
+ /** Room kind from the join exchange. 'audio' rooms are voice-only. */
111
+ readonly kind: 'audio' | 'video';
112
+ /** Snapshot of the remote participants currently connected. */
113
+ readonly participants: RoomParticipantInfo[];
114
+ /** Subscribe to a typed event. No unsubscribe surface yet (mirrors `Call.onX`). */
115
+ on<E extends RoomEventName>(event: E, handler: Handler<E>): void;
116
+ /** Publish the local mic track. Resolves once the track is live on LiveKit. */
117
+ publishMic(): Promise<void>;
118
+ /** Publish the local camera track. */
119
+ publishCamera(): Promise<void>;
120
+ /** Mid-call mute/unmute of the local mic. */
121
+ setMicEnabled(on: boolean): Promise<void>;
122
+ /** Mid-call camera on/off. */
123
+ setCameraEnabled(on: boolean): Promise<void>;
124
+ /** Current local mic state (for toggle UI). */
125
+ isMicEnabled(): boolean;
126
+ /** Current local camera state (for toggle UI). */
127
+ isCameraEnabled(): boolean;
128
+ /** The local camera track for self-view, or null before publishCamera resolves. */
129
+ getLocalCameraTrack(): LocalVideoTrack | null;
130
+ /**
131
+ * Remote tracks already subscribed at this moment. A late joiner misses the
132
+ * live `track.subscribed` events for tracks published before it connected
133
+ * (LiveKit delivers them during `connect`, before consumer listeners attach).
134
+ * Call this right after registering `track.subscribed` to backfill them.
135
+ */
136
+ getRemoteTracks(): RoomTrackEvent[];
137
+ /**
138
+ * Start/stop sharing the screen (via `getDisplayMedia`). Pass `{ audio: true }`
139
+ * to also capture shared/system audio where the browser allows it (Chrome:
140
+ * tab or system audio; macOS Chrome is tab-audio only; Safari/Firefox don't
141
+ * capture share audio). Publishes a `screen_share` video track (+ optional
142
+ * `screen_share_audio`); remote peers receive them via `track.subscribed`.
143
+ */
144
+ setScreenShareEnabled(on: boolean, opts?: {
145
+ audio?: boolean;
146
+ }): Promise<void>;
147
+ /** Current local screen-share state (for toggle UI). */
148
+ isScreenShareEnabled(): boolean;
149
+ /** The local screen-share video track for self-preview, or null when off. */
150
+ getLocalScreenTrack(): LocalVideoTrack | null;
151
+ /** Disconnect from LiveKit. Idempotent. Triggers `room.ended` via Disconnected. */
152
+ leave(): Promise<void>;
153
+ }
154
+ declare const joinRoom: (opts: JoinRoomOptions) => Promise<RoomSession>;
155
+
156
+ export { type AnalysisMessage, type JoinRoomOptions, type RoomEventName, type RoomEventPayloads, type RoomParticipantInfo, type RoomSession, type SystemMessage, type TranscriptMessage, joinRoom };
package/dist/room.js ADDED
@@ -0,0 +1,236 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/roomEntry.ts
31
+ var roomEntry_exports = {};
32
+ __export(roomEntry_exports, {
33
+ joinRoom: () => joinRoom
34
+ });
35
+ module.exports = __toCommonJS(roomEntry_exports);
36
+
37
+ // src/roomProtocol.ts
38
+ var SYSTEM_TOPIC = "system";
39
+ var TRANSCRIPT_TOPIC = "transcript";
40
+ var ANALYSIS_TOPIC = "analysis";
41
+ var decodeSystem = (bytes) => {
42
+ try {
43
+ const v = JSON.parse(new TextDecoder().decode(bytes));
44
+ if (v && typeof v.kind === "string") return v;
45
+ return null;
46
+ } catch {
47
+ return null;
48
+ }
49
+ };
50
+ var decodeTranscript = (bytes) => {
51
+ try {
52
+ const v = JSON.parse(new TextDecoder().decode(bytes));
53
+ if (v && v.kind === "partial") return v;
54
+ return null;
55
+ } catch {
56
+ return null;
57
+ }
58
+ };
59
+ var decodeAnalysis = (bytes) => {
60
+ try {
61
+ const v = JSON.parse(new TextDecoder().decode(bytes));
62
+ if (v && v.kind === "analysis" && typeof v.participantId === "string" && typeof v.label === "string") {
63
+ return v;
64
+ }
65
+ return null;
66
+ } catch {
67
+ return null;
68
+ }
69
+ };
70
+
71
+ // src/room.ts
72
+ var lkPromise;
73
+ var loadLiveKit = () => {
74
+ lkPromise ?? (lkPromise = import("livekit-client").catch((e) => {
75
+ lkPromise = void 0;
76
+ const msg = "joinRoom requires the optional peer dependency 'livekit-client' \u2014 npm install livekit-client";
77
+ const wrapped = new Error(msg);
78
+ wrapped.cause = e;
79
+ throw wrapped;
80
+ }));
81
+ return lkPromise;
82
+ };
83
+ var identityToPid = (identity) => identity.startsWith("guest:") ? identity.slice("guest:".length) : identity;
84
+ var joinRoom = async (opts) => {
85
+ const exchangeUrl = `${opts.apiBase.replace(/\/+$/, "")}/v1/rooms/${encodeURIComponent(
86
+ opts.roomId
87
+ )}/join`;
88
+ const exchangeRes = await fetch(exchangeUrl, {
89
+ method: "POST",
90
+ headers: { "Content-Type": "application/json" },
91
+ body: JSON.stringify({ code: opts.joinCode, name: opts.name })
92
+ });
93
+ if (!exchangeRes.ok) {
94
+ const err = await exchangeRes.json().catch(() => ({}));
95
+ throw new Error(err.error?.code ?? `join_failed_${exchangeRes.status}`);
96
+ }
97
+ const exchange = await exchangeRes.json();
98
+ const handlers = /* @__PURE__ */ new Map();
99
+ const emit = (e, payload) => {
100
+ handlers.get(e)?.forEach((h) => {
101
+ try {
102
+ h(payload);
103
+ } catch {
104
+ }
105
+ });
106
+ };
107
+ const lk = await loadLiveKit();
108
+ const trackKind = (t) => t.kind === lk.Track.Kind.Video ? "video" : "audio";
109
+ const trackSource = (s) => {
110
+ switch (s) {
111
+ case lk.Track.Source.Camera:
112
+ return "camera";
113
+ case lk.Track.Source.Microphone:
114
+ return "microphone";
115
+ case lk.Track.Source.ScreenShare:
116
+ return "screen_share";
117
+ case lk.Track.Source.ScreenShareAudio:
118
+ return "screen_share_audio";
119
+ default:
120
+ return "unknown";
121
+ }
122
+ };
123
+ const room = new lk.Room({ adaptiveStream: true, dynacast: true });
124
+ room.on(
125
+ lk.RoomEvent.ParticipantConnected,
126
+ (p) => emit("participant.joined", {
127
+ participantId: identityToPid(p.identity),
128
+ name: p.name ?? ""
129
+ })
130
+ );
131
+ room.on(
132
+ lk.RoomEvent.ParticipantDisconnected,
133
+ (p) => emit("participant.left", {
134
+ participantId: identityToPid(p.identity),
135
+ name: p.name ?? ""
136
+ })
137
+ );
138
+ room.on(lk.RoomEvent.Disconnected, () => emit("room.ended", void 0));
139
+ room.on(lk.RoomEvent.DataReceived, (data, _participant, _kind, topic) => {
140
+ if (topic === SYSTEM_TOPIC) {
141
+ const m = decodeSystem(data);
142
+ if (m) emit("system.message", m);
143
+ } else if (topic === TRANSCRIPT_TOPIC) {
144
+ const m = decodeTranscript(data);
145
+ if (m) emit("transcript.partial", m);
146
+ } else if (topic === ANALYSIS_TOPIC) {
147
+ const m = decodeAnalysis(data);
148
+ if (m) emit("analysis", m);
149
+ }
150
+ });
151
+ room.on(
152
+ lk.RoomEvent.TrackSubscribed,
153
+ (track, pub, participant) => emit("track.subscribed", {
154
+ participantId: identityToPid(participant.identity),
155
+ kind: trackKind(track),
156
+ source: trackSource(pub.source),
157
+ track
158
+ })
159
+ );
160
+ room.on(
161
+ lk.RoomEvent.TrackUnsubscribed,
162
+ (track, pub, participant) => emit("track.unsubscribed", {
163
+ participantId: identityToPid(participant.identity),
164
+ kind: trackKind(track),
165
+ source: trackSource(pub.source),
166
+ track
167
+ })
168
+ );
169
+ room.on(
170
+ lk.RoomEvent.ActiveSpeakersChanged,
171
+ (speakers) => emit(
172
+ "active.speakers",
173
+ speakers.map((p) => identityToPid(p.identity))
174
+ )
175
+ );
176
+ await room.connect(exchange.livekit.url, exchange.livekit.token);
177
+ return {
178
+ participantId: exchange.participantId,
179
+ kind: exchange.kind ?? "video",
180
+ get participants() {
181
+ return [...room.remoteParticipants.values()].map((p) => ({
182
+ participantId: identityToPid(p.identity),
183
+ name: p.name ?? ""
184
+ }));
185
+ },
186
+ on(event, handler) {
187
+ const set = handlers.get(event) ?? /* @__PURE__ */ new Set();
188
+ set.add(handler);
189
+ handlers.set(event, set);
190
+ },
191
+ publishMic: async () => {
192
+ await room.localParticipant.setMicrophoneEnabled(true);
193
+ },
194
+ publishCamera: async () => {
195
+ await room.localParticipant.setCameraEnabled(true);
196
+ },
197
+ setMicEnabled: async (on) => {
198
+ await room.localParticipant.setMicrophoneEnabled(on);
199
+ },
200
+ setCameraEnabled: async (on) => {
201
+ await room.localParticipant.setCameraEnabled(on);
202
+ },
203
+ isMicEnabled: () => room.localParticipant.isMicrophoneEnabled,
204
+ isCameraEnabled: () => room.localParticipant.isCameraEnabled,
205
+ getLocalCameraTrack: () => room.localParticipant.getTrackPublication(lk.Track.Source.Camera)?.videoTrack ?? null,
206
+ getRemoteTracks: () => {
207
+ const out = [];
208
+ for (const p of room.remoteParticipants.values()) {
209
+ for (const pub of p.trackPublications.values()) {
210
+ const track = pub.track;
211
+ if (!track) continue;
212
+ out.push({
213
+ participantId: identityToPid(p.identity),
214
+ kind: trackKind(track),
215
+ source: trackSource(pub.source),
216
+ track
217
+ });
218
+ }
219
+ }
220
+ return out;
221
+ },
222
+ setScreenShareEnabled: async (on, opts2) => {
223
+ await room.localParticipant.setScreenShareEnabled(on, { audio: opts2?.audio ?? false });
224
+ },
225
+ isScreenShareEnabled: () => room.localParticipant.isScreenShareEnabled,
226
+ getLocalScreenTrack: () => room.localParticipant.getTrackPublication(lk.Track.Source.ScreenShare)?.videoTrack ?? null,
227
+ leave: async () => {
228
+ await room.disconnect();
229
+ }
230
+ };
231
+ };
232
+ // Annotate the CommonJS export names for ESM import in node:
233
+ 0 && (module.exports = {
234
+ joinRoom
235
+ });
236
+ //# sourceMappingURL=room.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/roomEntry.ts","../src/roomProtocol.ts","../src/room.ts"],"sourcesContent":["// Room entry — `import { joinRoom } from '@craftedxp/voice-js/room'`.\n// The only entry that touches livekit-client (lazy, optional peer).\nexport { joinRoom } from './room'\nexport type {\n JoinRoomOptions,\n RoomEventName,\n RoomEventPayloads,\n RoomParticipantInfo,\n RoomSession,\n} from './room'\nexport type { SystemMessage, TranscriptMessage, AnalysisMessage } from './roomProtocol'\n","// DO NOT EDIT — single source of truth is server/src/room/protocol.ts.\n// Re-vendor (byte-for-byte copy) when the upstream changes.\n//\n// Why vendored: when this package is published to npm, its build artifacts\n// (`dist/`) cannot reach across to `server/src/room/protocol.ts`. Importing\n// via a cross-package relative path resolves at compile time but breaks at\n// consumer install time. Followup: extract to `@craftedxp/room-protocol`\n// and depend on it from both server and SDK so drift is impossible.\n\n// server/src/room/protocol.ts\n//\n// Wire format for LiveKit data-message topics \"system\", \"transcript\", and \"analysis\".\n// Worker publishes; SDK consumes. Single source of truth — do not duplicate\n// in the SDK package.\n\nexport const SYSTEM_TOPIC = 'system'\nexport const TRANSCRIPT_TOPIC = 'transcript'\nexport const ANALYSIS_TOPIC = 'analysis'\n\nexport type SystemMessage =\n | { kind: 'room.starting'; at: string }\n | { kind: 'room.ending.soon'; minutesRemaining: 5 | 1 }\n | { kind: 'room.ended'; reason: 'duration_reached' | 'manual' | 'empty' }\n | { kind: 'role.promoted'; participantId: string; name: string }\n | { kind: 'role.demoted'; participantId: string; name: string }\n | { kind: 'participant.removed'; participantId: string; name: string; byHost?: string }\n | { kind: 'notetaker.connected' }\n | { kind: 'notetaker.disconnected' }\n | { kind: 'notetaker.partial_degraded'; participantId: string }\n\nexport type TranscriptMessage = {\n kind: 'partial'\n participantId: string\n speakerName: string\n text: string\n startedAt: string\n}\n\n// Reserved for future live per-utterance analysis (e.g. interview \"technical\n// depth\" gauge). No worker publishes this today — clients may subscribe\n// defensively. See docs/superpowers/specs/2026-06-02-audio-call-rooms-design.md.\nexport type AnalysisMessage = {\n kind: 'analysis'\n participantId: string\n label: string\n score?: number\n note?: string\n producer?: string\n}\n\nexport const encode = <T>(msg: T): Uint8Array => new TextEncoder().encode(JSON.stringify(msg))\n\nexport const decodeSystem = (bytes: Uint8Array): SystemMessage | null => {\n try {\n const v = JSON.parse(new TextDecoder().decode(bytes))\n if (v && typeof v.kind === 'string') return v as SystemMessage\n return null\n } catch {\n return null\n }\n}\n\nexport const decodeTranscript = (bytes: Uint8Array): TranscriptMessage | null => {\n try {\n const v = JSON.parse(new TextDecoder().decode(bytes))\n if (v && v.kind === 'partial') return v as TranscriptMessage\n return null\n } catch {\n return null\n }\n}\n\nexport const decodeAnalysis = (bytes: Uint8Array): AnalysisMessage | null => {\n try {\n const v = JSON.parse(new TextDecoder().decode(bytes))\n if (\n v &&\n v.kind === 'analysis' &&\n typeof v.participantId === 'string' &&\n typeof v.label === 'string'\n ) {\n return v as AnalysisMessage\n }\n return null\n } catch {\n return null\n }\n}\n","// Multi-party room support: exchange a joinCode for a LiveKit JWT, connect,\n// expose typed events.\n//\n// Auth flow (mirrors server/src/routes/v1/roomsJoinRoutes.ts):\n// POST {apiBase}/v1/rooms/:roomId/join { code, name }\n// → { roomId, participantId, name, role, livekit: { url, token, identity } }\n// The endpoint is AUTH-EXEMPT — the shared joinCode is the only credential.\n// It mints a FRESH participant each call (shared-room-link join model).\n//\n// Once the JWT is in hand, we hand it to livekit-client's `Room` and surface\n// a small typed event surface on top: participant.{joined,left},\n// transcript.{partial,final}, system.message, room.ended.\n//\n// Wire format for the system / transcript data-messages lives in\n// `./roomProtocol` (vendored copy of server/src/room/protocol.ts — see\n// the header note there).\n\nimport type {\n Track,\n RemoteParticipant,\n RemoteTrack,\n RemoteTrackPublication,\n LocalVideoTrack,\n Participant,\n} from 'livekit-client'\n\n// livekit-client is an OPTIONAL peer dependency — loaded on first joinRoom()\n// so assistant/transcribe consumers (and the deprecated root barrel) never\n// pull it into their module graph or bundle.\ntype LiveKit = typeof import('livekit-client')\nlet lkPromise: Promise<LiveKit> | undefined\nconst loadLiveKit = (): Promise<LiveKit> => {\n lkPromise ??= import('livekit-client').catch((e: unknown) => {\n lkPromise = undefined\n const msg =\n \"joinRoom requires the optional peer dependency 'livekit-client' — npm install livekit-client\"\n const wrapped = new Error(msg)\n ;(wrapped as Error & { cause?: unknown }).cause = e\n throw wrapped\n })\n return lkPromise\n}\n\nimport {\n decodeSystem,\n decodeTranscript,\n decodeAnalysis,\n SYSTEM_TOPIC,\n TRANSCRIPT_TOPIC,\n ANALYSIS_TOPIC,\n type SystemMessage,\n type TranscriptMessage,\n type AnalysisMessage,\n} from './roomProtocol'\n\nexport interface JoinRoomOptions {\n /** Full HTTPS URL of the Voissia server. Same shape as VoiceClientConfig.apiBase. */\n apiBase: string\n /** Server-generated room id (`rm_…`). */\n roomId: string\n /** Shared room join token from the invite link. Mints a fresh participant each call. */\n joinCode: string\n /** Display name the joiner registers under for this participant. */\n name: string\n}\n\nexport interface RoomParticipantInfo {\n /** Stable participant id (`p_…`); strips any `guest:` LiveKit identity prefix. */\n participantId: string\n /** Display name as the worker registered it; may be empty. */\n name: string\n}\n\nexport type RoomTrackKind = 'audio' | 'video'\n\n/** What a track is — lets consumers tell a camera apart from a screen share\n * (a participant can publish both at once). Mirrors livekit `Track.Source`. */\nexport type RoomTrackSource =\n | 'camera'\n | 'microphone'\n | 'screen_share'\n | 'screen_share_audio'\n | 'unknown'\n\nexport interface RoomTrackEvent {\n /** Stable participant id (`p_…`); `guest:` prefix stripped. */\n participantId: string\n kind: RoomTrackKind\n /** Distinguishes camera vs screen_share so each can render as its own tile. */\n source: RoomTrackSource\n /** livekit-client track — call `.attach(el)` / `.detach()` to render. */\n track: RemoteTrack\n}\n\nexport type RoomEventName =\n | 'participant.joined'\n | 'participant.left'\n | 'transcript.partial'\n | 'transcript.final'\n | 'system.message'\n | 'room.ended'\n | 'track.subscribed'\n | 'track.unsubscribed'\n | 'active.speakers'\n | 'analysis'\n\nexport interface RoomEventPayloads {\n 'participant.joined': RoomParticipantInfo\n 'participant.left': RoomParticipantInfo\n 'transcript.partial': TranscriptMessage\n /**\n * Reserved — the worker currently emits only partials over the transcript\n * topic. Final-utterance events will land in Phase 8 once the worker\n * publishes a `final` kind; the SDK keeps the slot reserved so consumers\n * can register handlers today.\n */\n 'transcript.final': {\n participantId: string\n speakerName: string\n text: string\n startedAt: string\n }\n 'system.message': SystemMessage\n 'room.ended': undefined\n 'track.subscribed': RoomTrackEvent\n 'track.unsubscribed': RoomTrackEvent\n /** participantIds currently speaking (drives an active-speaker UI). */\n 'active.speakers': string[]\n /** Live analysis result (e.g. hirability). Delivered only to host\n * participants by the worker; non-host participants never receive it. */\n analysis: AnalysisMessage\n}\n\ntype Handler<E extends RoomEventName> = (payload: RoomEventPayloads[E]) => void\n\nexport interface RoomSession {\n /** This session's own stable participant id (`p_…`). Useful to filter\n * yourself out of `active.speakers`, which includes the local participant. */\n readonly participantId: string\n /** Room kind from the join exchange. 'audio' rooms are voice-only. */\n readonly kind: 'audio' | 'video'\n /** Snapshot of the remote participants currently connected. */\n readonly participants: RoomParticipantInfo[]\n /** Subscribe to a typed event. No unsubscribe surface yet (mirrors `Call.onX`). */\n on<E extends RoomEventName>(event: E, handler: Handler<E>): void\n /** Publish the local mic track. Resolves once the track is live on LiveKit. */\n publishMic(): Promise<void>\n /** Publish the local camera track. */\n publishCamera(): Promise<void>\n /** Mid-call mute/unmute of the local mic. */\n setMicEnabled(on: boolean): Promise<void>\n /** Mid-call camera on/off. */\n setCameraEnabled(on: boolean): Promise<void>\n /** Current local mic state (for toggle UI). */\n isMicEnabled(): boolean\n /** Current local camera state (for toggle UI). */\n isCameraEnabled(): boolean\n /** The local camera track for self-view, or null before publishCamera resolves. */\n getLocalCameraTrack(): LocalVideoTrack | null\n /**\n * Remote tracks already subscribed at this moment. A late joiner misses the\n * live `track.subscribed` events for tracks published before it connected\n * (LiveKit delivers them during `connect`, before consumer listeners attach).\n * Call this right after registering `track.subscribed` to backfill them.\n */\n getRemoteTracks(): RoomTrackEvent[]\n /**\n * Start/stop sharing the screen (via `getDisplayMedia`). Pass `{ audio: true }`\n * to also capture shared/system audio where the browser allows it (Chrome:\n * tab or system audio; macOS Chrome is tab-audio only; Safari/Firefox don't\n * capture share audio). Publishes a `screen_share` video track (+ optional\n * `screen_share_audio`); remote peers receive them via `track.subscribed`.\n */\n setScreenShareEnabled(on: boolean, opts?: { audio?: boolean }): Promise<void>\n /** Current local screen-share state (for toggle UI). */\n isScreenShareEnabled(): boolean\n /** The local screen-share video track for self-preview, or null when off. */\n getLocalScreenTrack(): LocalVideoTrack | null\n /** Disconnect from LiveKit. Idempotent. Triggers `room.ended` via Disconnected. */\n leave(): Promise<void>\n}\n\n/**\n * LiveKit identities are minted as `guest:p_xxx` by the server-side\n * RoomService. SDK consumers only care about the stable `p_xxx` half.\n */\nconst identityToPid = (identity: string): string =>\n identity.startsWith('guest:') ? identity.slice('guest:'.length) : identity\n\ninterface JoinExchangeResponse {\n roomId: string\n participantId: string\n name: string\n role: 'host' | 'guest' | 'observer' | 'notetaker'\n kind?: 'audio' | 'video'\n livekit: {\n url: string\n token: string\n identity: string\n }\n}\n\nexport const joinRoom = async (opts: JoinRoomOptions): Promise<RoomSession> => {\n // 1. Exchange the join code for a LiveKit JWT.\n const exchangeUrl = `${opts.apiBase.replace(/\\/+$/, '')}/v1/rooms/${encodeURIComponent(\n opts.roomId,\n )}/join`\n\n const exchangeRes = await fetch(exchangeUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ code: opts.joinCode, name: opts.name }),\n })\n\n if (!exchangeRes.ok) {\n const err = (await exchangeRes.json().catch(() => ({}))) as {\n error?: { code?: string; message?: string }\n }\n throw new Error(err.error?.code ?? `join_failed_${exchangeRes.status}`)\n }\n\n const exchange = (await exchangeRes.json()) as JoinExchangeResponse\n\n // 2. Build the typed event surface (simple Map<event, Set<handler>>).\n const handlers = new Map<RoomEventName, Set<(p: unknown) => void>>()\n const emit = <E extends RoomEventName>(e: E, payload: RoomEventPayloads[E]) => {\n handlers.get(e)?.forEach((h) => {\n try {\n h(payload)\n } catch {\n // Swallow handler errors — one bad subscriber shouldn't take down\n // the rest. Consumers are expected to handle their own throws.\n }\n })\n }\n\n // 3. Lazy-load livekit-client (optional peer dep) then connect.\n const lk = await loadLiveKit()\n\n const trackKind = (t: RemoteTrack): RoomTrackKind =>\n t.kind === lk.Track.Kind.Video ? 'video' : 'audio'\n\n const trackSource = (s: Track.Source): RoomTrackSource => {\n switch (s) {\n case lk.Track.Source.Camera:\n return 'camera'\n case lk.Track.Source.Microphone:\n return 'microphone'\n case lk.Track.Source.ScreenShare:\n return 'screen_share'\n case lk.Track.Source.ScreenShareAudio:\n return 'screen_share_audio'\n default:\n return 'unknown'\n }\n }\n\n // adaptiveStream + dynacast are the recommended defaults for multi-party video.\n const room = new lk.Room({ adaptiveStream: true, dynacast: true })\n\n room.on(lk.RoomEvent.ParticipantConnected, (p: RemoteParticipant) =>\n emit('participant.joined', {\n participantId: identityToPid(p.identity),\n name: p.name ?? '',\n }),\n )\n\n room.on(lk.RoomEvent.ParticipantDisconnected, (p: RemoteParticipant) =>\n emit('participant.left', {\n participantId: identityToPid(p.identity),\n name: p.name ?? '',\n }),\n )\n\n room.on(lk.RoomEvent.Disconnected, () => emit('room.ended', undefined))\n\n // DataReceived signature in livekit-client@2.x:\n // (payload: Uint8Array, participant?: RemoteParticipant,\n // kind?: DataPacket_Kind, topic?: string, encryptionType?: Encryption_Type) => void\n // We only key off `topic` and `payload`.\n room.on(lk.RoomEvent.DataReceived, (data: Uint8Array, _participant, _kind, topic?: string) => {\n if (topic === SYSTEM_TOPIC) {\n const m = decodeSystem(data)\n if (m) emit('system.message', m)\n } else if (topic === TRANSCRIPT_TOPIC) {\n const m = decodeTranscript(data)\n if (m) emit('transcript.partial', m)\n } else if (topic === ANALYSIS_TOPIC) {\n const m = decodeAnalysis(data)\n if (m) emit('analysis', m)\n }\n })\n\n room.on(\n lk.RoomEvent.TrackSubscribed,\n (track: RemoteTrack, pub: RemoteTrackPublication, participant: RemoteParticipant) =>\n emit('track.subscribed', {\n participantId: identityToPid(participant.identity),\n kind: trackKind(track),\n source: trackSource(pub.source),\n track,\n }),\n )\n\n room.on(\n lk.RoomEvent.TrackUnsubscribed,\n (track: RemoteTrack, pub: RemoteTrackPublication, participant: RemoteParticipant) =>\n emit('track.unsubscribed', {\n participantId: identityToPid(participant.identity),\n kind: trackKind(track),\n source: trackSource(pub.source),\n track,\n }),\n )\n\n room.on(lk.RoomEvent.ActiveSpeakersChanged, (speakers: Participant[]) =>\n emit(\n 'active.speakers',\n speakers.map((p) => identityToPid(p.identity)),\n ),\n )\n\n await room.connect(exchange.livekit.url, exchange.livekit.token)\n\n return {\n participantId: exchange.participantId,\n kind: exchange.kind ?? 'video',\n get participants(): RoomParticipantInfo[] {\n return [...room.remoteParticipants.values()].map((p) => ({\n participantId: identityToPid(p.identity),\n name: p.name ?? '',\n }))\n },\n on<E extends RoomEventName>(event: E, handler: Handler<E>) {\n const set = handlers.get(event) ?? new Set<(p: unknown) => void>()\n set.add(handler as (p: unknown) => void)\n handlers.set(event, set)\n },\n publishMic: async () => {\n await room.localParticipant.setMicrophoneEnabled(true)\n },\n publishCamera: async () => {\n await room.localParticipant.setCameraEnabled(true)\n },\n setMicEnabled: async (on: boolean) => {\n await room.localParticipant.setMicrophoneEnabled(on)\n },\n setCameraEnabled: async (on: boolean) => {\n await room.localParticipant.setCameraEnabled(on)\n },\n isMicEnabled: () => room.localParticipant.isMicrophoneEnabled,\n isCameraEnabled: () => room.localParticipant.isCameraEnabled,\n getLocalCameraTrack: () =>\n (room.localParticipant.getTrackPublication(lk.Track.Source.Camera)?.videoTrack as\n | LocalVideoTrack\n | undefined) ?? null,\n getRemoteTracks: (): RoomTrackEvent[] => {\n const out: RoomTrackEvent[] = []\n for (const p of room.remoteParticipants.values()) {\n for (const pub of p.trackPublications.values()) {\n const track = pub.track\n if (!track) continue\n out.push({\n participantId: identityToPid(p.identity),\n kind: trackKind(track),\n source: trackSource(pub.source),\n track,\n })\n }\n }\n return out\n },\n setScreenShareEnabled: async (on: boolean, opts?: { audio?: boolean }) => {\n await room.localParticipant.setScreenShareEnabled(on, { audio: opts?.audio ?? false })\n },\n isScreenShareEnabled: () => room.localParticipant.isScreenShareEnabled,\n getLocalScreenTrack: () =>\n (room.localParticipant.getTrackPublication(lk.Track.Source.ScreenShare)?.videoTrack as\n | LocalVideoTrack\n | undefined) ?? null,\n leave: async () => {\n await room.disconnect()\n },\n }\n}\n"],"mappings":"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","names":["opts"]}
package/dist/room.mjs ADDED
@@ -0,0 +1,7 @@
1
+ import {
2
+ joinRoom
3
+ } from "./chunk-LV7JGPYW.mjs";
4
+ export {
5
+ joinRoom
6
+ };
7
+ //# sourceMappingURL=room.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,14 @@
1
+ import { m as StartCallOptions, V as VoiceClientConfig } from './config-D2TbvIqT.mjs';
2
+ export { f as CallState, o as TranscriptEntry } from './config-D2TbvIqT.mjs';
3
+
4
+ interface DictationSession {
5
+ end: () => void;
6
+ mute: () => void;
7
+ unmute: () => void;
8
+ }
9
+ type StartDictationOptions = Pick<StartCallOptions, 'agentId' | 'userId' | 'onTranscript' | 'onError' | 'onStateChange' | 'onEnd'>;
10
+ declare function configureTranscribeClient(config: VoiceClientConfig): {
11
+ startDictation: (opts: StartDictationOptions) => Promise<DictationSession>;
12
+ };
13
+
14
+ export { type DictationSession, type StartDictationOptions, configureTranscribeClient };
@@ -0,0 +1,14 @@
1
+ import { m as StartCallOptions, V as VoiceClientConfig } from './config-D2TbvIqT.js';
2
+ export { f as CallState, o as TranscriptEntry } from './config-D2TbvIqT.js';
3
+
4
+ interface DictationSession {
5
+ end: () => void;
6
+ mute: () => void;
7
+ unmute: () => void;
8
+ }
9
+ type StartDictationOptions = Pick<StartCallOptions, 'agentId' | 'userId' | 'onTranscript' | 'onError' | 'onStateChange' | 'onEnd'>;
10
+ declare function configureTranscribeClient(config: VoiceClientConfig): {
11
+ startDictation: (opts: StartDictationOptions) => Promise<DictationSession>;
12
+ };
13
+
14
+ export { type DictationSession, type StartDictationOptions, configureTranscribeClient };