@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.js
CHANGED
|
@@ -29,6 +29,8 @@ __export(browser_exports, {
|
|
|
29
29
|
createProtocolState: () => createProtocolState,
|
|
30
30
|
createReconnectingWebSocket: () => createReconnectingWebSocket,
|
|
31
31
|
handleServerMessage: () => handleServerMessage,
|
|
32
|
+
joinRoom: () => joinRoom,
|
|
33
|
+
parseIncomingCall: () => parseIncomingCall,
|
|
32
34
|
})
|
|
33
35
|
module.exports = __toCommonJS(browser_exports)
|
|
34
36
|
|
|
@@ -958,8 +960,45 @@ async function createWebRtcCall(opts) {
|
|
|
958
960
|
const iceUrl = gateway
|
|
959
961
|
? `${gateway}/webrtc/ice?token=${encodeURIComponent(opts.token)}`
|
|
960
962
|
: `${opts.apiBase}/v1/agents/${encodeURIComponent(opts.agentId)}/webrtc/ice?token=${encodeURIComponent(opts.token)}`
|
|
963
|
+
const teardown = () => {
|
|
964
|
+
if (ended) return
|
|
965
|
+
ended = true
|
|
966
|
+
try {
|
|
967
|
+
mic.getTracks().forEach((t) => t.stop())
|
|
968
|
+
} catch {}
|
|
969
|
+
try {
|
|
970
|
+
pc.close()
|
|
971
|
+
} catch {}
|
|
972
|
+
try {
|
|
973
|
+
audioEl.remove()
|
|
974
|
+
} catch {}
|
|
975
|
+
fireState('ended')
|
|
976
|
+
opts.onEnd?.()
|
|
977
|
+
}
|
|
978
|
+
let callId = null
|
|
979
|
+
const pendingCandidates = []
|
|
980
|
+
const postCandidate = (candidate) => {
|
|
981
|
+
void fetch(iceUrl, {
|
|
982
|
+
method: 'POST',
|
|
983
|
+
headers: { 'content-type': 'application/json' },
|
|
984
|
+
body: JSON.stringify({ callId, candidate }),
|
|
985
|
+
}).catch(() => {})
|
|
986
|
+
}
|
|
987
|
+
pc.onicecandidate = (e) => {
|
|
988
|
+
if (!e.candidate) return
|
|
989
|
+
if (callId) postCandidate(e.candidate)
|
|
990
|
+
else pendingCandidates.push(e.candidate)
|
|
991
|
+
}
|
|
992
|
+
pc.onconnectionstatechange = () => {
|
|
993
|
+
const s = pc.connectionState
|
|
994
|
+
if (s === 'connected') fireState('listening')
|
|
995
|
+
if (s === 'failed' || s === 'disconnected') {
|
|
996
|
+
opts.onError?.({ code: 'socket_error', message: `webrtc connection ${s}` })
|
|
997
|
+
teardown()
|
|
998
|
+
}
|
|
999
|
+
if (s === 'closed' && !ended) teardown()
|
|
1000
|
+
}
|
|
961
1001
|
await pc.setLocalDescription(await pc.createOffer())
|
|
962
|
-
let callId
|
|
963
1002
|
try {
|
|
964
1003
|
const offerRes = await fetch(offerUrl, {
|
|
965
1004
|
method: 'POST',
|
|
@@ -978,6 +1017,7 @@ async function createWebRtcCall(opts) {
|
|
|
978
1017
|
const body = await offerRes.json()
|
|
979
1018
|
callId = body.callId
|
|
980
1019
|
await pc.setRemoteDescription({ type: 'answer', sdp: body.sdp })
|
|
1020
|
+
while (pendingCandidates.length > 0) postCandidate(pendingCandidates.shift())
|
|
981
1021
|
} catch (err) {
|
|
982
1022
|
if (!ended) {
|
|
983
1023
|
opts.onError?.({
|
|
@@ -991,38 +1031,6 @@ async function createWebRtcCall(opts) {
|
|
|
991
1031
|
}
|
|
992
1032
|
throw err
|
|
993
1033
|
}
|
|
994
|
-
pc.onicecandidate = (e) => {
|
|
995
|
-
if (!e.candidate) return
|
|
996
|
-
void fetch(iceUrl, {
|
|
997
|
-
method: 'POST',
|
|
998
|
-
headers: { 'content-type': 'application/json' },
|
|
999
|
-
body: JSON.stringify({ callId, candidate: e.candidate }),
|
|
1000
|
-
}).catch(() => {})
|
|
1001
|
-
}
|
|
1002
|
-
pc.onconnectionstatechange = () => {
|
|
1003
|
-
const s = pc.connectionState
|
|
1004
|
-
if (s === 'connected') fireState('listening')
|
|
1005
|
-
if (s === 'failed' || s === 'disconnected') {
|
|
1006
|
-
opts.onError?.({ code: 'socket_error', message: `webrtc connection ${s}` })
|
|
1007
|
-
teardown()
|
|
1008
|
-
}
|
|
1009
|
-
if (s === 'closed' && !ended) teardown()
|
|
1010
|
-
}
|
|
1011
|
-
const teardown = () => {
|
|
1012
|
-
if (ended) return
|
|
1013
|
-
ended = true
|
|
1014
|
-
try {
|
|
1015
|
-
mic.getTracks().forEach((t) => t.stop())
|
|
1016
|
-
} catch {}
|
|
1017
|
-
try {
|
|
1018
|
-
pc.close()
|
|
1019
|
-
} catch {}
|
|
1020
|
-
try {
|
|
1021
|
-
audioEl.remove()
|
|
1022
|
-
} catch {}
|
|
1023
|
-
fireState('ended')
|
|
1024
|
-
opts.onEnd?.()
|
|
1025
|
-
}
|
|
1026
1034
|
return {
|
|
1027
1035
|
get state() {
|
|
1028
1036
|
return proto.state
|
|
@@ -1047,6 +1055,199 @@ async function createWebRtcCall(opts) {
|
|
|
1047
1055
|
}
|
|
1048
1056
|
}
|
|
1049
1057
|
|
|
1058
|
+
// src/room.ts
|
|
1059
|
+
var import_livekit_client = require('livekit-client')
|
|
1060
|
+
|
|
1061
|
+
// src/roomProtocol.ts
|
|
1062
|
+
var SYSTEM_TOPIC = 'system'
|
|
1063
|
+
var TRANSCRIPT_TOPIC = 'transcript'
|
|
1064
|
+
var decodeSystem = (bytes) => {
|
|
1065
|
+
try {
|
|
1066
|
+
const v = JSON.parse(new TextDecoder().decode(bytes))
|
|
1067
|
+
if (v && typeof v.kind === 'string') return v
|
|
1068
|
+
return null
|
|
1069
|
+
} catch {
|
|
1070
|
+
return null
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
var decodeTranscript = (bytes) => {
|
|
1074
|
+
try {
|
|
1075
|
+
const v = JSON.parse(new TextDecoder().decode(bytes))
|
|
1076
|
+
if (v && v.kind === 'partial') return v
|
|
1077
|
+
return null
|
|
1078
|
+
} catch {
|
|
1079
|
+
return null
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// src/room.ts
|
|
1084
|
+
var identityToPid = (identity) =>
|
|
1085
|
+
identity.startsWith('guest:') ? identity.slice('guest:'.length) : identity
|
|
1086
|
+
var joinRoom = async (opts) => {
|
|
1087
|
+
const exchangeUrl = `${opts.apiBase.replace(/\/+$/, '')}/v1/rooms/${encodeURIComponent(
|
|
1088
|
+
opts.roomId,
|
|
1089
|
+
)}/join`
|
|
1090
|
+
const exchangeRes = await fetch(exchangeUrl, {
|
|
1091
|
+
method: 'POST',
|
|
1092
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1093
|
+
body: JSON.stringify({ code: opts.joinCode, name: opts.name }),
|
|
1094
|
+
})
|
|
1095
|
+
if (!exchangeRes.ok) {
|
|
1096
|
+
const err = await exchangeRes.json().catch(() => ({}))
|
|
1097
|
+
throw new Error(err.error?.code ?? `join_failed_${exchangeRes.status}`)
|
|
1098
|
+
}
|
|
1099
|
+
const exchange = await exchangeRes.json()
|
|
1100
|
+
const handlers = /* @__PURE__ */ new Map()
|
|
1101
|
+
const emit = (e, payload) => {
|
|
1102
|
+
handlers.get(e)?.forEach((h) => {
|
|
1103
|
+
try {
|
|
1104
|
+
h(payload)
|
|
1105
|
+
} catch {}
|
|
1106
|
+
})
|
|
1107
|
+
}
|
|
1108
|
+
const room = new import_livekit_client.Room({ adaptiveStream: true, dynacast: true })
|
|
1109
|
+
room.on(import_livekit_client.RoomEvent.ParticipantConnected, (p) =>
|
|
1110
|
+
emit('participant.joined', {
|
|
1111
|
+
participantId: identityToPid(p.identity),
|
|
1112
|
+
name: p.name ?? '',
|
|
1113
|
+
}),
|
|
1114
|
+
)
|
|
1115
|
+
room.on(import_livekit_client.RoomEvent.ParticipantDisconnected, (p) =>
|
|
1116
|
+
emit('participant.left', {
|
|
1117
|
+
participantId: identityToPid(p.identity),
|
|
1118
|
+
name: p.name ?? '',
|
|
1119
|
+
}),
|
|
1120
|
+
)
|
|
1121
|
+
room.on(import_livekit_client.RoomEvent.Disconnected, () => emit('room.ended', void 0))
|
|
1122
|
+
room.on(import_livekit_client.RoomEvent.DataReceived, (data, _participant, _kind, topic) => {
|
|
1123
|
+
if (topic === SYSTEM_TOPIC) {
|
|
1124
|
+
const m = decodeSystem(data)
|
|
1125
|
+
if (m) emit('system.message', m)
|
|
1126
|
+
} else if (topic === TRANSCRIPT_TOPIC) {
|
|
1127
|
+
const m = decodeTranscript(data)
|
|
1128
|
+
if (m) emit('transcript.partial', m)
|
|
1129
|
+
}
|
|
1130
|
+
})
|
|
1131
|
+
const trackKind = (t) => (t.kind === import_livekit_client.Track.Kind.Video ? 'video' : 'audio')
|
|
1132
|
+
const trackSource = (s) => {
|
|
1133
|
+
switch (s) {
|
|
1134
|
+
case import_livekit_client.Track.Source.Camera:
|
|
1135
|
+
return 'camera'
|
|
1136
|
+
case import_livekit_client.Track.Source.Microphone:
|
|
1137
|
+
return 'microphone'
|
|
1138
|
+
case import_livekit_client.Track.Source.ScreenShare:
|
|
1139
|
+
return 'screen_share'
|
|
1140
|
+
case import_livekit_client.Track.Source.ScreenShareAudio:
|
|
1141
|
+
return 'screen_share_audio'
|
|
1142
|
+
default:
|
|
1143
|
+
return 'unknown'
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
room.on(import_livekit_client.RoomEvent.TrackSubscribed, (track, pub, participant) =>
|
|
1147
|
+
emit('track.subscribed', {
|
|
1148
|
+
participantId: identityToPid(participant.identity),
|
|
1149
|
+
kind: trackKind(track),
|
|
1150
|
+
source: trackSource(pub.source),
|
|
1151
|
+
track,
|
|
1152
|
+
}),
|
|
1153
|
+
)
|
|
1154
|
+
room.on(import_livekit_client.RoomEvent.TrackUnsubscribed, (track, pub, participant) =>
|
|
1155
|
+
emit('track.unsubscribed', {
|
|
1156
|
+
participantId: identityToPid(participant.identity),
|
|
1157
|
+
kind: trackKind(track),
|
|
1158
|
+
source: trackSource(pub.source),
|
|
1159
|
+
track,
|
|
1160
|
+
}),
|
|
1161
|
+
)
|
|
1162
|
+
room.on(import_livekit_client.RoomEvent.ActiveSpeakersChanged, (speakers) =>
|
|
1163
|
+
emit(
|
|
1164
|
+
'active.speakers',
|
|
1165
|
+
speakers.map((p) => identityToPid(p.identity)),
|
|
1166
|
+
),
|
|
1167
|
+
)
|
|
1168
|
+
await room.connect(exchange.livekit.url, exchange.livekit.token)
|
|
1169
|
+
return {
|
|
1170
|
+
participantId: exchange.participantId,
|
|
1171
|
+
get participants() {
|
|
1172
|
+
return [...room.remoteParticipants.values()].map((p) => ({
|
|
1173
|
+
participantId: identityToPid(p.identity),
|
|
1174
|
+
name: p.name ?? '',
|
|
1175
|
+
}))
|
|
1176
|
+
},
|
|
1177
|
+
on(event, handler) {
|
|
1178
|
+
const set = handlers.get(event) ?? /* @__PURE__ */ new Set()
|
|
1179
|
+
set.add(handler)
|
|
1180
|
+
handlers.set(event, set)
|
|
1181
|
+
},
|
|
1182
|
+
publishMic: async () => {
|
|
1183
|
+
await room.localParticipant.setMicrophoneEnabled(true)
|
|
1184
|
+
},
|
|
1185
|
+
publishCamera: async () => {
|
|
1186
|
+
await room.localParticipant.setCameraEnabled(true)
|
|
1187
|
+
},
|
|
1188
|
+
setMicEnabled: async (on) => {
|
|
1189
|
+
await room.localParticipant.setMicrophoneEnabled(on)
|
|
1190
|
+
},
|
|
1191
|
+
setCameraEnabled: async (on) => {
|
|
1192
|
+
await room.localParticipant.setCameraEnabled(on)
|
|
1193
|
+
},
|
|
1194
|
+
isMicEnabled: () => room.localParticipant.isMicrophoneEnabled,
|
|
1195
|
+
isCameraEnabled: () => room.localParticipant.isCameraEnabled,
|
|
1196
|
+
getLocalCameraTrack: () =>
|
|
1197
|
+
room.localParticipant.getTrackPublication(import_livekit_client.Track.Source.Camera)
|
|
1198
|
+
?.videoTrack ?? null,
|
|
1199
|
+
getRemoteTracks: () => {
|
|
1200
|
+
const out = []
|
|
1201
|
+
for (const p of room.remoteParticipants.values()) {
|
|
1202
|
+
for (const pub of p.trackPublications.values()) {
|
|
1203
|
+
const track = pub.track
|
|
1204
|
+
if (!track) continue
|
|
1205
|
+
out.push({
|
|
1206
|
+
participantId: identityToPid(p.identity),
|
|
1207
|
+
kind: trackKind(track),
|
|
1208
|
+
source: trackSource(pub.source),
|
|
1209
|
+
track,
|
|
1210
|
+
})
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
return out
|
|
1214
|
+
},
|
|
1215
|
+
setScreenShareEnabled: async (on, opts2) => {
|
|
1216
|
+
await room.localParticipant.setScreenShareEnabled(on, { audio: opts2?.audio ?? false })
|
|
1217
|
+
},
|
|
1218
|
+
isScreenShareEnabled: () => room.localParticipant.isScreenShareEnabled,
|
|
1219
|
+
getLocalScreenTrack: () =>
|
|
1220
|
+
room.localParticipant.getTrackPublication(import_livekit_client.Track.Source.ScreenShare)
|
|
1221
|
+
?.videoTrack ?? null,
|
|
1222
|
+
leave: async () => {
|
|
1223
|
+
await room.disconnect()
|
|
1224
|
+
},
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// src/incomingCall.ts
|
|
1229
|
+
var parseIncomingCall = (raw) => {
|
|
1230
|
+
if (typeof raw !== 'object' || raw === null) {
|
|
1231
|
+
throw new Error('parseIncomingCall: payload must be an object')
|
|
1232
|
+
}
|
|
1233
|
+
const p = raw
|
|
1234
|
+
if (typeof p.token !== 'string' || !p.token.startsWith('ct_')) {
|
|
1235
|
+
throw new Error('parseIncomingCall: missing or invalid `token` (expected a ct_ string)')
|
|
1236
|
+
}
|
|
1237
|
+
if (typeof p.agentId !== 'string' || p.agentId.length === 0) {
|
|
1238
|
+
throw new Error('parseIncomingCall: missing `agentId`')
|
|
1239
|
+
}
|
|
1240
|
+
const transport = p.transport === 'webrtc' ? 'webrtc' : 'ws'
|
|
1241
|
+
const out = { token: p.token, agentId: p.agentId, transport }
|
|
1242
|
+
if (transport === 'webrtc' && typeof p.webrtcGatewayBase === 'string') {
|
|
1243
|
+
out.webrtcGatewayBase = p.webrtcGatewayBase
|
|
1244
|
+
}
|
|
1245
|
+
if (typeof p.expiresAt === 'number') out.expiresAt = p.expiresAt
|
|
1246
|
+
if (typeof p.agentName === 'string') out.agentName = p.agentName
|
|
1247
|
+
if (typeof p.agentAvatarUrl === 'string') out.agentAvatarUrl = p.agentAvatarUrl
|
|
1248
|
+
return out
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1050
1251
|
// src/browser.ts
|
|
1051
1252
|
var browserWsFactory = (url) => new globalThis.WebSocket(url)
|
|
1052
1253
|
var BrowserVoiceFactory = class {
|
|
@@ -1106,6 +1307,14 @@ var BrowserVoiceFactory = class {
|
|
|
1106
1307
|
await client.start()
|
|
1107
1308
|
return client
|
|
1108
1309
|
}
|
|
1310
|
+
// Multi-party rooms (Phase 7 video).
|
|
1311
|
+
//
|
|
1312
|
+
// The guest's browser calls this with the roomId + joinCode it parsed
|
|
1313
|
+
// out of the invite link. The SDK exchanges the code for a LiveKit
|
|
1314
|
+
// JWT against `${apiBase}/v1/rooms/:roomId/join` (an AUTH-EXEMPT
|
|
1315
|
+
// endpoint — the opaque code is the only credential), then connects
|
|
1316
|
+
// to LiveKit and returns a typed event surface.
|
|
1317
|
+
this.joinRoom = (opts) => joinRoom({ apiBase: this.config.apiBase, ...opts })
|
|
1109
1318
|
this.config = config
|
|
1110
1319
|
}
|
|
1111
1320
|
}
|
|
@@ -1122,5 +1331,7 @@ function configureVoiceClient(config) {
|
|
|
1122
1331
|
createProtocolState,
|
|
1123
1332
|
createReconnectingWebSocket,
|
|
1124
1333
|
handleServerMessage,
|
|
1334
|
+
joinRoom,
|
|
1335
|
+
parseIncomingCall,
|
|
1125
1336
|
})
|
|
1126
1337
|
//# sourceMappingURL=browser.js.map
|