@craftedxp/voice-js 0.4.1 → 0.5.4
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/CONSUMING.md +10 -2
- package/README.md +72 -11
- package/dist/browser.d.mts +178 -1
- package/dist/browser.d.ts +208 -0
- package/dist/browser.js +244 -33
- package/dist/browser.js.map +1 -1
- package/dist/browser.mjs +252 -38
- package/dist/browser.mjs.map +1 -1
- package/dist/embed.iife.js +22677 -452
- package/dist/node.d.mts +177 -1
- package/dist/node.d.ts +199 -0
- package/dist/node.js +30 -0
- package/dist/node.js.map +1 -1
- package/dist/node.mjs +30 -1
- package/dist/node.mjs.map +1 -1
- package/package.json +4 -1
package/dist/browser.mjs
CHANGED
|
@@ -916,8 +916,49 @@ async function createWebRtcCall(opts) {
|
|
|
916
916
|
const gateway = opts.webrtcGatewayBase || "";
|
|
917
917
|
const offerUrl = gateway ? `${gateway}/webrtc/offer?token=${encodeURIComponent(opts.token)}` : `${opts.apiBase}/v1/agents/${encodeURIComponent(opts.agentId)}/webrtc/offer?token=${encodeURIComponent(opts.token)}`;
|
|
918
918
|
const iceUrl = gateway ? `${gateway}/webrtc/ice?token=${encodeURIComponent(opts.token)}` : `${opts.apiBase}/v1/agents/${encodeURIComponent(opts.agentId)}/webrtc/ice?token=${encodeURIComponent(opts.token)}`;
|
|
919
|
+
const teardown = () => {
|
|
920
|
+
if (ended) return;
|
|
921
|
+
ended = true;
|
|
922
|
+
try {
|
|
923
|
+
mic.getTracks().forEach((t) => t.stop());
|
|
924
|
+
} catch {
|
|
925
|
+
}
|
|
926
|
+
try {
|
|
927
|
+
pc.close();
|
|
928
|
+
} catch {
|
|
929
|
+
}
|
|
930
|
+
try {
|
|
931
|
+
audioEl.remove();
|
|
932
|
+
} catch {
|
|
933
|
+
}
|
|
934
|
+
fireState("ended");
|
|
935
|
+
opts.onEnd?.();
|
|
936
|
+
};
|
|
937
|
+
let callId = null;
|
|
938
|
+
const pendingCandidates = [];
|
|
939
|
+
const postCandidate = (candidate) => {
|
|
940
|
+
void fetch(iceUrl, {
|
|
941
|
+
method: "POST",
|
|
942
|
+
headers: { "content-type": "application/json" },
|
|
943
|
+
body: JSON.stringify({ callId, candidate })
|
|
944
|
+
}).catch(() => {
|
|
945
|
+
});
|
|
946
|
+
};
|
|
947
|
+
pc.onicecandidate = (e) => {
|
|
948
|
+
if (!e.candidate) return;
|
|
949
|
+
if (callId) postCandidate(e.candidate);
|
|
950
|
+
else pendingCandidates.push(e.candidate);
|
|
951
|
+
};
|
|
952
|
+
pc.onconnectionstatechange = () => {
|
|
953
|
+
const s = pc.connectionState;
|
|
954
|
+
if (s === "connected") fireState("listening");
|
|
955
|
+
if (s === "failed" || s === "disconnected") {
|
|
956
|
+
opts.onError?.({ code: "socket_error", message: `webrtc connection ${s}` });
|
|
957
|
+
teardown();
|
|
958
|
+
}
|
|
959
|
+
if (s === "closed" && !ended) teardown();
|
|
960
|
+
};
|
|
919
961
|
await pc.setLocalDescription(await pc.createOffer());
|
|
920
|
-
let callId;
|
|
921
962
|
try {
|
|
922
963
|
const offerRes = await fetch(offerUrl, {
|
|
923
964
|
method: "POST",
|
|
@@ -936,6 +977,7 @@ async function createWebRtcCall(opts) {
|
|
|
936
977
|
const body = await offerRes.json();
|
|
937
978
|
callId = body.callId;
|
|
938
979
|
await pc.setRemoteDescription({ type: "answer", sdp: body.sdp });
|
|
980
|
+
while (pendingCandidates.length > 0) postCandidate(pendingCandidates.shift());
|
|
939
981
|
} catch (err) {
|
|
940
982
|
if (!ended) {
|
|
941
983
|
opts.onError?.({
|
|
@@ -949,42 +991,6 @@ async function createWebRtcCall(opts) {
|
|
|
949
991
|
}
|
|
950
992
|
throw err;
|
|
951
993
|
}
|
|
952
|
-
pc.onicecandidate = (e) => {
|
|
953
|
-
if (!e.candidate) return;
|
|
954
|
-
void fetch(iceUrl, {
|
|
955
|
-
method: "POST",
|
|
956
|
-
headers: { "content-type": "application/json" },
|
|
957
|
-
body: JSON.stringify({ callId, candidate: e.candidate })
|
|
958
|
-
}).catch(() => {
|
|
959
|
-
});
|
|
960
|
-
};
|
|
961
|
-
pc.onconnectionstatechange = () => {
|
|
962
|
-
const s = pc.connectionState;
|
|
963
|
-
if (s === "connected") fireState("listening");
|
|
964
|
-
if (s === "failed" || s === "disconnected") {
|
|
965
|
-
opts.onError?.({ code: "socket_error", message: `webrtc connection ${s}` });
|
|
966
|
-
teardown();
|
|
967
|
-
}
|
|
968
|
-
if (s === "closed" && !ended) teardown();
|
|
969
|
-
};
|
|
970
|
-
const teardown = () => {
|
|
971
|
-
if (ended) return;
|
|
972
|
-
ended = true;
|
|
973
|
-
try {
|
|
974
|
-
mic.getTracks().forEach((t) => t.stop());
|
|
975
|
-
} catch {
|
|
976
|
-
}
|
|
977
|
-
try {
|
|
978
|
-
pc.close();
|
|
979
|
-
} catch {
|
|
980
|
-
}
|
|
981
|
-
try {
|
|
982
|
-
audioEl.remove();
|
|
983
|
-
} catch {
|
|
984
|
-
}
|
|
985
|
-
fireState("ended");
|
|
986
|
-
opts.onEnd?.();
|
|
987
|
-
};
|
|
988
994
|
return {
|
|
989
995
|
get state() {
|
|
990
996
|
return proto.state;
|
|
@@ -1009,6 +1015,204 @@ async function createWebRtcCall(opts) {
|
|
|
1009
1015
|
};
|
|
1010
1016
|
}
|
|
1011
1017
|
|
|
1018
|
+
// src/room.ts
|
|
1019
|
+
import {
|
|
1020
|
+
Room,
|
|
1021
|
+
RoomEvent,
|
|
1022
|
+
Track
|
|
1023
|
+
} from "livekit-client";
|
|
1024
|
+
|
|
1025
|
+
// src/roomProtocol.ts
|
|
1026
|
+
var SYSTEM_TOPIC = "system";
|
|
1027
|
+
var TRANSCRIPT_TOPIC = "transcript";
|
|
1028
|
+
var decodeSystem = (bytes) => {
|
|
1029
|
+
try {
|
|
1030
|
+
const v = JSON.parse(new TextDecoder().decode(bytes));
|
|
1031
|
+
if (v && typeof v.kind === "string") return v;
|
|
1032
|
+
return null;
|
|
1033
|
+
} catch {
|
|
1034
|
+
return null;
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
var decodeTranscript = (bytes) => {
|
|
1038
|
+
try {
|
|
1039
|
+
const v = JSON.parse(new TextDecoder().decode(bytes));
|
|
1040
|
+
if (v && v.kind === "partial") return v;
|
|
1041
|
+
return null;
|
|
1042
|
+
} catch {
|
|
1043
|
+
return null;
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
|
|
1047
|
+
// src/room.ts
|
|
1048
|
+
var identityToPid = (identity) => identity.startsWith("guest:") ? identity.slice("guest:".length) : identity;
|
|
1049
|
+
var joinRoom = async (opts) => {
|
|
1050
|
+
const exchangeUrl = `${opts.apiBase.replace(/\/+$/, "")}/v1/rooms/${encodeURIComponent(
|
|
1051
|
+
opts.roomId
|
|
1052
|
+
)}/join`;
|
|
1053
|
+
const exchangeRes = await fetch(exchangeUrl, {
|
|
1054
|
+
method: "POST",
|
|
1055
|
+
headers: { "Content-Type": "application/json" },
|
|
1056
|
+
body: JSON.stringify({ code: opts.joinCode, name: opts.name })
|
|
1057
|
+
});
|
|
1058
|
+
if (!exchangeRes.ok) {
|
|
1059
|
+
const err = await exchangeRes.json().catch(() => ({}));
|
|
1060
|
+
throw new Error(err.error?.code ?? `join_failed_${exchangeRes.status}`);
|
|
1061
|
+
}
|
|
1062
|
+
const exchange = await exchangeRes.json();
|
|
1063
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
1064
|
+
const emit = (e, payload) => {
|
|
1065
|
+
handlers.get(e)?.forEach((h) => {
|
|
1066
|
+
try {
|
|
1067
|
+
h(payload);
|
|
1068
|
+
} catch {
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
};
|
|
1072
|
+
const room = new Room({ adaptiveStream: true, dynacast: true });
|
|
1073
|
+
room.on(
|
|
1074
|
+
RoomEvent.ParticipantConnected,
|
|
1075
|
+
(p) => emit("participant.joined", {
|
|
1076
|
+
participantId: identityToPid(p.identity),
|
|
1077
|
+
name: p.name ?? ""
|
|
1078
|
+
})
|
|
1079
|
+
);
|
|
1080
|
+
room.on(
|
|
1081
|
+
RoomEvent.ParticipantDisconnected,
|
|
1082
|
+
(p) => emit("participant.left", {
|
|
1083
|
+
participantId: identityToPid(p.identity),
|
|
1084
|
+
name: p.name ?? ""
|
|
1085
|
+
})
|
|
1086
|
+
);
|
|
1087
|
+
room.on(RoomEvent.Disconnected, () => emit("room.ended", void 0));
|
|
1088
|
+
room.on(RoomEvent.DataReceived, (data, _participant, _kind, topic) => {
|
|
1089
|
+
if (topic === SYSTEM_TOPIC) {
|
|
1090
|
+
const m = decodeSystem(data);
|
|
1091
|
+
if (m) emit("system.message", m);
|
|
1092
|
+
} else if (topic === TRANSCRIPT_TOPIC) {
|
|
1093
|
+
const m = decodeTranscript(data);
|
|
1094
|
+
if (m) emit("transcript.partial", m);
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
const trackKind = (t) => t.kind === Track.Kind.Video ? "video" : "audio";
|
|
1098
|
+
const trackSource = (s) => {
|
|
1099
|
+
switch (s) {
|
|
1100
|
+
case Track.Source.Camera:
|
|
1101
|
+
return "camera";
|
|
1102
|
+
case Track.Source.Microphone:
|
|
1103
|
+
return "microphone";
|
|
1104
|
+
case Track.Source.ScreenShare:
|
|
1105
|
+
return "screen_share";
|
|
1106
|
+
case Track.Source.ScreenShareAudio:
|
|
1107
|
+
return "screen_share_audio";
|
|
1108
|
+
default:
|
|
1109
|
+
return "unknown";
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
room.on(
|
|
1113
|
+
RoomEvent.TrackSubscribed,
|
|
1114
|
+
(track, pub, participant) => emit("track.subscribed", {
|
|
1115
|
+
participantId: identityToPid(participant.identity),
|
|
1116
|
+
kind: trackKind(track),
|
|
1117
|
+
source: trackSource(pub.source),
|
|
1118
|
+
track
|
|
1119
|
+
})
|
|
1120
|
+
);
|
|
1121
|
+
room.on(
|
|
1122
|
+
RoomEvent.TrackUnsubscribed,
|
|
1123
|
+
(track, pub, participant) => emit("track.unsubscribed", {
|
|
1124
|
+
participantId: identityToPid(participant.identity),
|
|
1125
|
+
kind: trackKind(track),
|
|
1126
|
+
source: trackSource(pub.source),
|
|
1127
|
+
track
|
|
1128
|
+
})
|
|
1129
|
+
);
|
|
1130
|
+
room.on(
|
|
1131
|
+
RoomEvent.ActiveSpeakersChanged,
|
|
1132
|
+
(speakers) => emit(
|
|
1133
|
+
"active.speakers",
|
|
1134
|
+
speakers.map((p) => identityToPid(p.identity))
|
|
1135
|
+
)
|
|
1136
|
+
);
|
|
1137
|
+
await room.connect(exchange.livekit.url, exchange.livekit.token);
|
|
1138
|
+
return {
|
|
1139
|
+
participantId: exchange.participantId,
|
|
1140
|
+
get participants() {
|
|
1141
|
+
return [...room.remoteParticipants.values()].map((p) => ({
|
|
1142
|
+
participantId: identityToPid(p.identity),
|
|
1143
|
+
name: p.name ?? ""
|
|
1144
|
+
}));
|
|
1145
|
+
},
|
|
1146
|
+
on(event, handler) {
|
|
1147
|
+
const set = handlers.get(event) ?? /* @__PURE__ */ new Set();
|
|
1148
|
+
set.add(handler);
|
|
1149
|
+
handlers.set(event, set);
|
|
1150
|
+
},
|
|
1151
|
+
publishMic: async () => {
|
|
1152
|
+
await room.localParticipant.setMicrophoneEnabled(true);
|
|
1153
|
+
},
|
|
1154
|
+
publishCamera: async () => {
|
|
1155
|
+
await room.localParticipant.setCameraEnabled(true);
|
|
1156
|
+
},
|
|
1157
|
+
setMicEnabled: async (on) => {
|
|
1158
|
+
await room.localParticipant.setMicrophoneEnabled(on);
|
|
1159
|
+
},
|
|
1160
|
+
setCameraEnabled: async (on) => {
|
|
1161
|
+
await room.localParticipant.setCameraEnabled(on);
|
|
1162
|
+
},
|
|
1163
|
+
isMicEnabled: () => room.localParticipant.isMicrophoneEnabled,
|
|
1164
|
+
isCameraEnabled: () => room.localParticipant.isCameraEnabled,
|
|
1165
|
+
getLocalCameraTrack: () => room.localParticipant.getTrackPublication(Track.Source.Camera)?.videoTrack ?? null,
|
|
1166
|
+
getRemoteTracks: () => {
|
|
1167
|
+
const out = [];
|
|
1168
|
+
for (const p of room.remoteParticipants.values()) {
|
|
1169
|
+
for (const pub of p.trackPublications.values()) {
|
|
1170
|
+
const track = pub.track;
|
|
1171
|
+
if (!track) continue;
|
|
1172
|
+
out.push({
|
|
1173
|
+
participantId: identityToPid(p.identity),
|
|
1174
|
+
kind: trackKind(track),
|
|
1175
|
+
source: trackSource(pub.source),
|
|
1176
|
+
track
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
return out;
|
|
1181
|
+
},
|
|
1182
|
+
setScreenShareEnabled: async (on, opts2) => {
|
|
1183
|
+
await room.localParticipant.setScreenShareEnabled(on, { audio: opts2?.audio ?? false });
|
|
1184
|
+
},
|
|
1185
|
+
isScreenShareEnabled: () => room.localParticipant.isScreenShareEnabled,
|
|
1186
|
+
getLocalScreenTrack: () => room.localParticipant.getTrackPublication(Track.Source.ScreenShare)?.videoTrack ?? null,
|
|
1187
|
+
leave: async () => {
|
|
1188
|
+
await room.disconnect();
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
};
|
|
1192
|
+
|
|
1193
|
+
// src/incomingCall.ts
|
|
1194
|
+
var parseIncomingCall = (raw) => {
|
|
1195
|
+
if (typeof raw !== "object" || raw === null) {
|
|
1196
|
+
throw new Error("parseIncomingCall: payload must be an object");
|
|
1197
|
+
}
|
|
1198
|
+
const p = raw;
|
|
1199
|
+
if (typeof p.token !== "string" || !p.token.startsWith("ct_")) {
|
|
1200
|
+
throw new Error("parseIncomingCall: missing or invalid `token` (expected a ct_ string)");
|
|
1201
|
+
}
|
|
1202
|
+
if (typeof p.agentId !== "string" || p.agentId.length === 0) {
|
|
1203
|
+
throw new Error("parseIncomingCall: missing `agentId`");
|
|
1204
|
+
}
|
|
1205
|
+
const transport = p.transport === "webrtc" ? "webrtc" : "ws";
|
|
1206
|
+
const out = { token: p.token, agentId: p.agentId, transport };
|
|
1207
|
+
if (transport === "webrtc" && typeof p.webrtcGatewayBase === "string") {
|
|
1208
|
+
out.webrtcGatewayBase = p.webrtcGatewayBase;
|
|
1209
|
+
}
|
|
1210
|
+
if (typeof p.expiresAt === "number") out.expiresAt = p.expiresAt;
|
|
1211
|
+
if (typeof p.agentName === "string") out.agentName = p.agentName;
|
|
1212
|
+
if (typeof p.agentAvatarUrl === "string") out.agentAvatarUrl = p.agentAvatarUrl;
|
|
1213
|
+
return out;
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1012
1216
|
// src/browser.ts
|
|
1013
1217
|
var browserWsFactory = (url) => new globalThis.WebSocket(url);
|
|
1014
1218
|
var BrowserVoiceFactory = class {
|
|
@@ -1066,6 +1270,14 @@ var BrowserVoiceFactory = class {
|
|
|
1066
1270
|
await client.start();
|
|
1067
1271
|
return client;
|
|
1068
1272
|
};
|
|
1273
|
+
// Multi-party rooms (Phase 7 video).
|
|
1274
|
+
//
|
|
1275
|
+
// The guest's browser calls this with the roomId + joinCode it parsed
|
|
1276
|
+
// out of the invite link. The SDK exchanges the code for a LiveKit
|
|
1277
|
+
// JWT against `${apiBase}/v1/rooms/:roomId/join` (an AUTH-EXEMPT
|
|
1278
|
+
// endpoint — the opaque code is the only credential), then connects
|
|
1279
|
+
// to LiveKit and returns a typed event surface.
|
|
1280
|
+
this.joinRoom = (opts) => joinRoom({ apiBase: this.config.apiBase, ...opts });
|
|
1069
1281
|
this.config = config;
|
|
1070
1282
|
}
|
|
1071
1283
|
};
|
|
@@ -1079,6 +1291,8 @@ export {
|
|
|
1079
1291
|
createAudioPlayback,
|
|
1080
1292
|
createProtocolState,
|
|
1081
1293
|
createReconnectingWebSocket,
|
|
1082
|
-
handleServerMessage
|
|
1294
|
+
handleServerMessage,
|
|
1295
|
+
joinRoom,
|
|
1296
|
+
parseIncomingCall
|
|
1083
1297
|
};
|
|
1084
1298
|
//# sourceMappingURL=browser.mjs.map
|