@absolutejs/voice 0.0.22-beta.322 → 0.0.22-beta.323
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/angular/index.js +164 -0
- package/dist/client/index.js +164 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +730 -437
- package/dist/react/index.js +164 -0
- package/dist/svelte/index.js +164 -0
- package/dist/telephonyMediaRoutes.d.ts +63 -0
- package/dist/testing/index.js +177 -13
- package/dist/vue/index.js +164 -0
- package/package.json +2 -2
package/dist/react/index.js
CHANGED
|
@@ -5050,6 +5050,63 @@ var stringStat = (stat, key) => {
|
|
|
5050
5050
|
};
|
|
5051
5051
|
var statKey = (stat) => String(stat.id ?? stringStat(stat, "ssrc") ?? numericStat(stat, "ssrc") ?? stringStat(stat, "trackIdentifier") ?? stringStat(stat, "mid") ?? "unknown");
|
|
5052
5052
|
var secondsToMs = (value) => value === undefined ? undefined : value * 1000;
|
|
5053
|
+
var DEFAULT_TELEPHONY_FORMAT = {
|
|
5054
|
+
channels: 1,
|
|
5055
|
+
container: "raw",
|
|
5056
|
+
encoding: "mulaw",
|
|
5057
|
+
sampleRateHz: 8000
|
|
5058
|
+
};
|
|
5059
|
+
var bytesToBase64 = (audio) => {
|
|
5060
|
+
const bytes = audio instanceof ArrayBuffer ? new Uint8Array(audio) : new Uint8Array(audio.buffer, audio.byteOffset, audio.byteLength);
|
|
5061
|
+
return Buffer.from(bytes).toString("base64");
|
|
5062
|
+
};
|
|
5063
|
+
var base64ToBytes = (value) => new Uint8Array(Buffer.from(value, "base64"));
|
|
5064
|
+
var unknownRecord = (value) => value && typeof value === "object" ? value : {};
|
|
5065
|
+
var firstString = (records, keys) => {
|
|
5066
|
+
for (const record of records) {
|
|
5067
|
+
for (const key of keys) {
|
|
5068
|
+
const value = record[key];
|
|
5069
|
+
if (typeof value === "string" && value.length > 0) {
|
|
5070
|
+
return value;
|
|
5071
|
+
}
|
|
5072
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
5073
|
+
return String(value);
|
|
5074
|
+
}
|
|
5075
|
+
}
|
|
5076
|
+
}
|
|
5077
|
+
return;
|
|
5078
|
+
};
|
|
5079
|
+
var firstNumber = (records, keys) => {
|
|
5080
|
+
for (const record of records) {
|
|
5081
|
+
for (const key of keys) {
|
|
5082
|
+
const value = record[key];
|
|
5083
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
5084
|
+
return value;
|
|
5085
|
+
}
|
|
5086
|
+
if (typeof value === "string") {
|
|
5087
|
+
const parsed = Number(value);
|
|
5088
|
+
if (Number.isFinite(parsed)) {
|
|
5089
|
+
return parsed;
|
|
5090
|
+
}
|
|
5091
|
+
}
|
|
5092
|
+
}
|
|
5093
|
+
}
|
|
5094
|
+
return;
|
|
5095
|
+
};
|
|
5096
|
+
var telephonyDirection = (track) => {
|
|
5097
|
+
const normalized = track?.toLowerCase();
|
|
5098
|
+
if (!normalized) {
|
|
5099
|
+
return "unknown";
|
|
5100
|
+
}
|
|
5101
|
+
if (normalized.includes("inbound") || normalized.includes("caller") || normalized.includes("in")) {
|
|
5102
|
+
return "inbound";
|
|
5103
|
+
}
|
|
5104
|
+
if (normalized.includes("outbound") || normalized.includes("assistant") || normalized.includes("out")) {
|
|
5105
|
+
return "outbound";
|
|
5106
|
+
}
|
|
5107
|
+
return "unknown";
|
|
5108
|
+
};
|
|
5109
|
+
var telephonyFrameKind = (direction) => direction === "outbound" ? "assistant-audio" : "input-audio";
|
|
5053
5110
|
var normalizeWebRTCStat = (stat) => {
|
|
5054
5111
|
const sample = {};
|
|
5055
5112
|
for (const [key, value] of Object.entries(stat)) {
|
|
@@ -5059,6 +5116,113 @@ var normalizeWebRTCStat = (stat) => {
|
|
|
5059
5116
|
}
|
|
5060
5117
|
return sample;
|
|
5061
5118
|
};
|
|
5119
|
+
var parseTelephonyMediaFrame = (input) => {
|
|
5120
|
+
const envelope = input.envelope;
|
|
5121
|
+
const media = unknownRecord(envelope.media);
|
|
5122
|
+
const payload = firstString([media, envelope], ["payload", "audio", "data"]) ?? firstString([unknownRecord(envelope.message)], ["payload"]);
|
|
5123
|
+
if (!payload) {
|
|
5124
|
+
return;
|
|
5125
|
+
}
|
|
5126
|
+
const carrier = input.carrier ?? firstString([envelope], ["provider"]) ?? "telephony";
|
|
5127
|
+
const streamId = firstString([media, envelope], ["streamSid", "stream_id", "streamId", "streamId", "callSid", "call_id"]);
|
|
5128
|
+
const sequenceNumber = firstString([media, envelope], ["sequenceNumber", "sequence_number", "chunk"]);
|
|
5129
|
+
const track = firstString([media, envelope], ["track", "direction"]);
|
|
5130
|
+
const direction = telephonyDirection(track);
|
|
5131
|
+
const timestamp = firstNumber([media, envelope], ["timestamp", "time", "startedAt"]);
|
|
5132
|
+
return {
|
|
5133
|
+
at: timestamp,
|
|
5134
|
+
audio: base64ToBytes(payload),
|
|
5135
|
+
format: input.format ?? DEFAULT_TELEPHONY_FORMAT,
|
|
5136
|
+
id: [
|
|
5137
|
+
carrier,
|
|
5138
|
+
streamId ?? input.sessionId ?? "stream",
|
|
5139
|
+
sequenceNumber ?? timestamp ?? Date.now()
|
|
5140
|
+
].join(":"),
|
|
5141
|
+
kind: telephonyFrameKind(direction),
|
|
5142
|
+
metadata: {
|
|
5143
|
+
carrier,
|
|
5144
|
+
direction,
|
|
5145
|
+
event: firstString([envelope], ["event", "type"]),
|
|
5146
|
+
sequenceNumber,
|
|
5147
|
+
streamId,
|
|
5148
|
+
track
|
|
5149
|
+
},
|
|
5150
|
+
sessionId: input.sessionId ?? streamId,
|
|
5151
|
+
source: "telephony"
|
|
5152
|
+
};
|
|
5153
|
+
};
|
|
5154
|
+
var serializeTelephonyMediaFrame = (input) => {
|
|
5155
|
+
const carrier = input.carrier ?? input.frame.metadata?.carrier ?? "telephony";
|
|
5156
|
+
const streamId = input.streamId ?? (typeof input.frame.metadata?.streamId === "string" ? input.frame.metadata.streamId : input.frame.sessionId);
|
|
5157
|
+
const sequenceNumber = input.sequenceNumber ?? (typeof input.frame.metadata?.sequenceNumber === "string" || typeof input.frame.metadata?.sequenceNumber === "number" ? input.frame.metadata.sequenceNumber : undefined);
|
|
5158
|
+
const direction = input.frame.kind === "assistant-audio" ? "outbound" : "inbound";
|
|
5159
|
+
const payload = input.frame.audio ? bytesToBase64(input.frame.audio) : "";
|
|
5160
|
+
if (carrier === "twilio") {
|
|
5161
|
+
return {
|
|
5162
|
+
event: "media",
|
|
5163
|
+
sequenceNumber,
|
|
5164
|
+
streamSid: streamId,
|
|
5165
|
+
media: {
|
|
5166
|
+
payload,
|
|
5167
|
+
timestamp: input.frame.at,
|
|
5168
|
+
track: direction
|
|
5169
|
+
}
|
|
5170
|
+
};
|
|
5171
|
+
}
|
|
5172
|
+
if (carrier === "telnyx") {
|
|
5173
|
+
return {
|
|
5174
|
+
event: "media",
|
|
5175
|
+
stream_id: streamId,
|
|
5176
|
+
sequence_number: sequenceNumber,
|
|
5177
|
+
media: {
|
|
5178
|
+
payload,
|
|
5179
|
+
timestamp: input.frame.at,
|
|
5180
|
+
track: direction
|
|
5181
|
+
}
|
|
5182
|
+
};
|
|
5183
|
+
}
|
|
5184
|
+
if (carrier === "plivo") {
|
|
5185
|
+
return {
|
|
5186
|
+
event: "media",
|
|
5187
|
+
streamId,
|
|
5188
|
+
sequenceNumber,
|
|
5189
|
+
media: {
|
|
5190
|
+
payload,
|
|
5191
|
+
timestamp: input.frame.at,
|
|
5192
|
+
track: direction
|
|
5193
|
+
}
|
|
5194
|
+
};
|
|
5195
|
+
}
|
|
5196
|
+
return {
|
|
5197
|
+
event: "media",
|
|
5198
|
+
provider: carrier,
|
|
5199
|
+
sequenceNumber,
|
|
5200
|
+
streamId,
|
|
5201
|
+
media: {
|
|
5202
|
+
payload,
|
|
5203
|
+
timestamp: input.frame.at,
|
|
5204
|
+
track: direction
|
|
5205
|
+
}
|
|
5206
|
+
};
|
|
5207
|
+
};
|
|
5208
|
+
var createTelephonyMediaSerializer = (input) => {
|
|
5209
|
+
const format = input.format ?? DEFAULT_TELEPHONY_FORMAT;
|
|
5210
|
+
return {
|
|
5211
|
+
carrier: input.carrier,
|
|
5212
|
+
format,
|
|
5213
|
+
parse: (envelope) => parseTelephonyMediaFrame({
|
|
5214
|
+
carrier: input.carrier,
|
|
5215
|
+
envelope,
|
|
5216
|
+
format,
|
|
5217
|
+
sessionId: input.sessionId ?? input.streamId
|
|
5218
|
+
}),
|
|
5219
|
+
serialize: (frame) => serializeTelephonyMediaFrame({
|
|
5220
|
+
carrier: input.carrier,
|
|
5221
|
+
frame,
|
|
5222
|
+
streamId: input.streamId
|
|
5223
|
+
})
|
|
5224
|
+
};
|
|
5225
|
+
};
|
|
5062
5226
|
var buildMediaResamplingPlan = (input) => {
|
|
5063
5227
|
const required = !formatMatches(input.inputFormat, input.outputFormat);
|
|
5064
5228
|
return {
|
package/dist/svelte/index.js
CHANGED
|
@@ -3384,6 +3384,63 @@ var stringStat = (stat, key) => {
|
|
|
3384
3384
|
};
|
|
3385
3385
|
var statKey = (stat) => String(stat.id ?? stringStat(stat, "ssrc") ?? numericStat(stat, "ssrc") ?? stringStat(stat, "trackIdentifier") ?? stringStat(stat, "mid") ?? "unknown");
|
|
3386
3386
|
var secondsToMs = (value) => value === undefined ? undefined : value * 1000;
|
|
3387
|
+
var DEFAULT_TELEPHONY_FORMAT = {
|
|
3388
|
+
channels: 1,
|
|
3389
|
+
container: "raw",
|
|
3390
|
+
encoding: "mulaw",
|
|
3391
|
+
sampleRateHz: 8000
|
|
3392
|
+
};
|
|
3393
|
+
var bytesToBase64 = (audio) => {
|
|
3394
|
+
const bytes = audio instanceof ArrayBuffer ? new Uint8Array(audio) : new Uint8Array(audio.buffer, audio.byteOffset, audio.byteLength);
|
|
3395
|
+
return Buffer.from(bytes).toString("base64");
|
|
3396
|
+
};
|
|
3397
|
+
var base64ToBytes = (value) => new Uint8Array(Buffer.from(value, "base64"));
|
|
3398
|
+
var unknownRecord = (value) => value && typeof value === "object" ? value : {};
|
|
3399
|
+
var firstString = (records, keys) => {
|
|
3400
|
+
for (const record of records) {
|
|
3401
|
+
for (const key of keys) {
|
|
3402
|
+
const value = record[key];
|
|
3403
|
+
if (typeof value === "string" && value.length > 0) {
|
|
3404
|
+
return value;
|
|
3405
|
+
}
|
|
3406
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
3407
|
+
return String(value);
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
return;
|
|
3412
|
+
};
|
|
3413
|
+
var firstNumber = (records, keys) => {
|
|
3414
|
+
for (const record of records) {
|
|
3415
|
+
for (const key of keys) {
|
|
3416
|
+
const value = record[key];
|
|
3417
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
3418
|
+
return value;
|
|
3419
|
+
}
|
|
3420
|
+
if (typeof value === "string") {
|
|
3421
|
+
const parsed = Number(value);
|
|
3422
|
+
if (Number.isFinite(parsed)) {
|
|
3423
|
+
return parsed;
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
return;
|
|
3429
|
+
};
|
|
3430
|
+
var telephonyDirection = (track) => {
|
|
3431
|
+
const normalized = track?.toLowerCase();
|
|
3432
|
+
if (!normalized) {
|
|
3433
|
+
return "unknown";
|
|
3434
|
+
}
|
|
3435
|
+
if (normalized.includes("inbound") || normalized.includes("caller") || normalized.includes("in")) {
|
|
3436
|
+
return "inbound";
|
|
3437
|
+
}
|
|
3438
|
+
if (normalized.includes("outbound") || normalized.includes("assistant") || normalized.includes("out")) {
|
|
3439
|
+
return "outbound";
|
|
3440
|
+
}
|
|
3441
|
+
return "unknown";
|
|
3442
|
+
};
|
|
3443
|
+
var telephonyFrameKind = (direction) => direction === "outbound" ? "assistant-audio" : "input-audio";
|
|
3387
3444
|
var normalizeWebRTCStat = (stat) => {
|
|
3388
3445
|
const sample = {};
|
|
3389
3446
|
for (const [key, value] of Object.entries(stat)) {
|
|
@@ -3393,6 +3450,113 @@ var normalizeWebRTCStat = (stat) => {
|
|
|
3393
3450
|
}
|
|
3394
3451
|
return sample;
|
|
3395
3452
|
};
|
|
3453
|
+
var parseTelephonyMediaFrame = (input) => {
|
|
3454
|
+
const envelope = input.envelope;
|
|
3455
|
+
const media = unknownRecord(envelope.media);
|
|
3456
|
+
const payload = firstString([media, envelope], ["payload", "audio", "data"]) ?? firstString([unknownRecord(envelope.message)], ["payload"]);
|
|
3457
|
+
if (!payload) {
|
|
3458
|
+
return;
|
|
3459
|
+
}
|
|
3460
|
+
const carrier = input.carrier ?? firstString([envelope], ["provider"]) ?? "telephony";
|
|
3461
|
+
const streamId = firstString([media, envelope], ["streamSid", "stream_id", "streamId", "streamId", "callSid", "call_id"]);
|
|
3462
|
+
const sequenceNumber = firstString([media, envelope], ["sequenceNumber", "sequence_number", "chunk"]);
|
|
3463
|
+
const track = firstString([media, envelope], ["track", "direction"]);
|
|
3464
|
+
const direction = telephonyDirection(track);
|
|
3465
|
+
const timestamp = firstNumber([media, envelope], ["timestamp", "time", "startedAt"]);
|
|
3466
|
+
return {
|
|
3467
|
+
at: timestamp,
|
|
3468
|
+
audio: base64ToBytes(payload),
|
|
3469
|
+
format: input.format ?? DEFAULT_TELEPHONY_FORMAT,
|
|
3470
|
+
id: [
|
|
3471
|
+
carrier,
|
|
3472
|
+
streamId ?? input.sessionId ?? "stream",
|
|
3473
|
+
sequenceNumber ?? timestamp ?? Date.now()
|
|
3474
|
+
].join(":"),
|
|
3475
|
+
kind: telephonyFrameKind(direction),
|
|
3476
|
+
metadata: {
|
|
3477
|
+
carrier,
|
|
3478
|
+
direction,
|
|
3479
|
+
event: firstString([envelope], ["event", "type"]),
|
|
3480
|
+
sequenceNumber,
|
|
3481
|
+
streamId,
|
|
3482
|
+
track
|
|
3483
|
+
},
|
|
3484
|
+
sessionId: input.sessionId ?? streamId,
|
|
3485
|
+
source: "telephony"
|
|
3486
|
+
};
|
|
3487
|
+
};
|
|
3488
|
+
var serializeTelephonyMediaFrame = (input) => {
|
|
3489
|
+
const carrier = input.carrier ?? input.frame.metadata?.carrier ?? "telephony";
|
|
3490
|
+
const streamId = input.streamId ?? (typeof input.frame.metadata?.streamId === "string" ? input.frame.metadata.streamId : input.frame.sessionId);
|
|
3491
|
+
const sequenceNumber = input.sequenceNumber ?? (typeof input.frame.metadata?.sequenceNumber === "string" || typeof input.frame.metadata?.sequenceNumber === "number" ? input.frame.metadata.sequenceNumber : undefined);
|
|
3492
|
+
const direction = input.frame.kind === "assistant-audio" ? "outbound" : "inbound";
|
|
3493
|
+
const payload = input.frame.audio ? bytesToBase64(input.frame.audio) : "";
|
|
3494
|
+
if (carrier === "twilio") {
|
|
3495
|
+
return {
|
|
3496
|
+
event: "media",
|
|
3497
|
+
sequenceNumber,
|
|
3498
|
+
streamSid: streamId,
|
|
3499
|
+
media: {
|
|
3500
|
+
payload,
|
|
3501
|
+
timestamp: input.frame.at,
|
|
3502
|
+
track: direction
|
|
3503
|
+
}
|
|
3504
|
+
};
|
|
3505
|
+
}
|
|
3506
|
+
if (carrier === "telnyx") {
|
|
3507
|
+
return {
|
|
3508
|
+
event: "media",
|
|
3509
|
+
stream_id: streamId,
|
|
3510
|
+
sequence_number: sequenceNumber,
|
|
3511
|
+
media: {
|
|
3512
|
+
payload,
|
|
3513
|
+
timestamp: input.frame.at,
|
|
3514
|
+
track: direction
|
|
3515
|
+
}
|
|
3516
|
+
};
|
|
3517
|
+
}
|
|
3518
|
+
if (carrier === "plivo") {
|
|
3519
|
+
return {
|
|
3520
|
+
event: "media",
|
|
3521
|
+
streamId,
|
|
3522
|
+
sequenceNumber,
|
|
3523
|
+
media: {
|
|
3524
|
+
payload,
|
|
3525
|
+
timestamp: input.frame.at,
|
|
3526
|
+
track: direction
|
|
3527
|
+
}
|
|
3528
|
+
};
|
|
3529
|
+
}
|
|
3530
|
+
return {
|
|
3531
|
+
event: "media",
|
|
3532
|
+
provider: carrier,
|
|
3533
|
+
sequenceNumber,
|
|
3534
|
+
streamId,
|
|
3535
|
+
media: {
|
|
3536
|
+
payload,
|
|
3537
|
+
timestamp: input.frame.at,
|
|
3538
|
+
track: direction
|
|
3539
|
+
}
|
|
3540
|
+
};
|
|
3541
|
+
};
|
|
3542
|
+
var createTelephonyMediaSerializer = (input) => {
|
|
3543
|
+
const format = input.format ?? DEFAULT_TELEPHONY_FORMAT;
|
|
3544
|
+
return {
|
|
3545
|
+
carrier: input.carrier,
|
|
3546
|
+
format,
|
|
3547
|
+
parse: (envelope) => parseTelephonyMediaFrame({
|
|
3548
|
+
carrier: input.carrier,
|
|
3549
|
+
envelope,
|
|
3550
|
+
format,
|
|
3551
|
+
sessionId: input.sessionId ?? input.streamId
|
|
3552
|
+
}),
|
|
3553
|
+
serialize: (frame) => serializeTelephonyMediaFrame({
|
|
3554
|
+
carrier: input.carrier,
|
|
3555
|
+
frame,
|
|
3556
|
+
streamId: input.streamId
|
|
3557
|
+
})
|
|
3558
|
+
};
|
|
3559
|
+
};
|
|
3396
3560
|
var buildMediaResamplingPlan = (input) => {
|
|
3397
3561
|
const required = !formatMatches(input.inputFormat, input.outputFormat);
|
|
3398
3562
|
return {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Elysia } from 'elysia';
|
|
2
|
+
import type { MediaFrame, MediaTelephonyCarrier, MediaTelephonyEnvelope } from '@absolutejs/media';
|
|
3
|
+
export type VoiceTelephonyMediaStatus = 'fail' | 'pass';
|
|
4
|
+
export type VoiceTelephonyMediaCarrierInput = {
|
|
5
|
+
carrier: MediaTelephonyCarrier;
|
|
6
|
+
envelope?: MediaTelephonyEnvelope;
|
|
7
|
+
};
|
|
8
|
+
export type VoiceTelephonyMediaCarrierReport = {
|
|
9
|
+
audioBytes: number;
|
|
10
|
+
carrier: MediaTelephonyCarrier;
|
|
11
|
+
frame?: MediaFrame;
|
|
12
|
+
issues: string[];
|
|
13
|
+
serialized?: MediaTelephonyEnvelope;
|
|
14
|
+
status: VoiceTelephonyMediaStatus;
|
|
15
|
+
};
|
|
16
|
+
export type VoiceTelephonyMediaReport = {
|
|
17
|
+
carriers: readonly VoiceTelephonyMediaCarrierReport[];
|
|
18
|
+
checkedAt: number;
|
|
19
|
+
issues: string[];
|
|
20
|
+
status: VoiceTelephonyMediaStatus;
|
|
21
|
+
};
|
|
22
|
+
export type VoiceTelephonyMediaRoutesOptions = {
|
|
23
|
+
carriers?: readonly VoiceTelephonyMediaCarrierInput[];
|
|
24
|
+
headers?: HeadersInit;
|
|
25
|
+
htmlPath?: false | string;
|
|
26
|
+
name?: string;
|
|
27
|
+
path?: string;
|
|
28
|
+
title?: string;
|
|
29
|
+
};
|
|
30
|
+
export declare const buildVoiceTelephonyMediaReport: (input?: {
|
|
31
|
+
carriers?: readonly VoiceTelephonyMediaCarrierInput[];
|
|
32
|
+
}) => VoiceTelephonyMediaReport;
|
|
33
|
+
export declare const renderVoiceTelephonyMediaHTML: (report: VoiceTelephonyMediaReport, options?: {
|
|
34
|
+
title?: string;
|
|
35
|
+
}) => string;
|
|
36
|
+
export declare const createVoiceTelephonyMediaRoutes: (options?: VoiceTelephonyMediaRoutesOptions) => Elysia<"", {
|
|
37
|
+
decorator: {};
|
|
38
|
+
store: {};
|
|
39
|
+
derive: {};
|
|
40
|
+
resolve: {};
|
|
41
|
+
}, {
|
|
42
|
+
typebox: {};
|
|
43
|
+
error: {};
|
|
44
|
+
}, {
|
|
45
|
+
schema: {};
|
|
46
|
+
standaloneSchema: {};
|
|
47
|
+
macro: {};
|
|
48
|
+
macroFn: {};
|
|
49
|
+
parser: {};
|
|
50
|
+
response: {};
|
|
51
|
+
}, {}, {
|
|
52
|
+
derive: {};
|
|
53
|
+
resolve: {};
|
|
54
|
+
schema: {};
|
|
55
|
+
standaloneSchema: {};
|
|
56
|
+
response: {};
|
|
57
|
+
}, {
|
|
58
|
+
derive: {};
|
|
59
|
+
resolve: {};
|
|
60
|
+
schema: {};
|
|
61
|
+
standaloneSchema: {};
|
|
62
|
+
response: {};
|
|
63
|
+
}>;
|
package/dist/testing/index.js
CHANGED
|
@@ -2186,6 +2186,63 @@ var stringStat = (stat, key) => {
|
|
|
2186
2186
|
};
|
|
2187
2187
|
var statKey = (stat) => String(stat.id ?? stringStat(stat, "ssrc") ?? numericStat(stat, "ssrc") ?? stringStat(stat, "trackIdentifier") ?? stringStat(stat, "mid") ?? "unknown");
|
|
2188
2188
|
var secondsToMs = (value) => value === undefined ? undefined : value * 1000;
|
|
2189
|
+
var DEFAULT_TELEPHONY_FORMAT = {
|
|
2190
|
+
channels: 1,
|
|
2191
|
+
container: "raw",
|
|
2192
|
+
encoding: "mulaw",
|
|
2193
|
+
sampleRateHz: 8000
|
|
2194
|
+
};
|
|
2195
|
+
var bytesToBase64 = (audio) => {
|
|
2196
|
+
const bytes = audio instanceof ArrayBuffer ? new Uint8Array(audio) : new Uint8Array(audio.buffer, audio.byteOffset, audio.byteLength);
|
|
2197
|
+
return Buffer.from(bytes).toString("base64");
|
|
2198
|
+
};
|
|
2199
|
+
var base64ToBytes = (value) => new Uint8Array(Buffer.from(value, "base64"));
|
|
2200
|
+
var unknownRecord = (value) => value && typeof value === "object" ? value : {};
|
|
2201
|
+
var firstString = (records, keys) => {
|
|
2202
|
+
for (const record of records) {
|
|
2203
|
+
for (const key of keys) {
|
|
2204
|
+
const value = record[key];
|
|
2205
|
+
if (typeof value === "string" && value.length > 0) {
|
|
2206
|
+
return value;
|
|
2207
|
+
}
|
|
2208
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2209
|
+
return String(value);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
return;
|
|
2214
|
+
};
|
|
2215
|
+
var firstNumber = (records, keys) => {
|
|
2216
|
+
for (const record of records) {
|
|
2217
|
+
for (const key of keys) {
|
|
2218
|
+
const value = record[key];
|
|
2219
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2220
|
+
return value;
|
|
2221
|
+
}
|
|
2222
|
+
if (typeof value === "string") {
|
|
2223
|
+
const parsed = Number(value);
|
|
2224
|
+
if (Number.isFinite(parsed)) {
|
|
2225
|
+
return parsed;
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
return;
|
|
2231
|
+
};
|
|
2232
|
+
var telephonyDirection = (track) => {
|
|
2233
|
+
const normalized = track?.toLowerCase();
|
|
2234
|
+
if (!normalized) {
|
|
2235
|
+
return "unknown";
|
|
2236
|
+
}
|
|
2237
|
+
if (normalized.includes("inbound") || normalized.includes("caller") || normalized.includes("in")) {
|
|
2238
|
+
return "inbound";
|
|
2239
|
+
}
|
|
2240
|
+
if (normalized.includes("outbound") || normalized.includes("assistant") || normalized.includes("out")) {
|
|
2241
|
+
return "outbound";
|
|
2242
|
+
}
|
|
2243
|
+
return "unknown";
|
|
2244
|
+
};
|
|
2245
|
+
var telephonyFrameKind = (direction) => direction === "outbound" ? "assistant-audio" : "input-audio";
|
|
2189
2246
|
var normalizeWebRTCStat = (stat) => {
|
|
2190
2247
|
const sample = {};
|
|
2191
2248
|
for (const [key, value] of Object.entries(stat)) {
|
|
@@ -2195,6 +2252,113 @@ var normalizeWebRTCStat = (stat) => {
|
|
|
2195
2252
|
}
|
|
2196
2253
|
return sample;
|
|
2197
2254
|
};
|
|
2255
|
+
var parseTelephonyMediaFrame = (input) => {
|
|
2256
|
+
const envelope = input.envelope;
|
|
2257
|
+
const media = unknownRecord(envelope.media);
|
|
2258
|
+
const payload = firstString([media, envelope], ["payload", "audio", "data"]) ?? firstString([unknownRecord(envelope.message)], ["payload"]);
|
|
2259
|
+
if (!payload) {
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2262
|
+
const carrier = input.carrier ?? firstString([envelope], ["provider"]) ?? "telephony";
|
|
2263
|
+
const streamId = firstString([media, envelope], ["streamSid", "stream_id", "streamId", "streamId", "callSid", "call_id"]);
|
|
2264
|
+
const sequenceNumber = firstString([media, envelope], ["sequenceNumber", "sequence_number", "chunk"]);
|
|
2265
|
+
const track = firstString([media, envelope], ["track", "direction"]);
|
|
2266
|
+
const direction = telephonyDirection(track);
|
|
2267
|
+
const timestamp = firstNumber([media, envelope], ["timestamp", "time", "startedAt"]);
|
|
2268
|
+
return {
|
|
2269
|
+
at: timestamp,
|
|
2270
|
+
audio: base64ToBytes(payload),
|
|
2271
|
+
format: input.format ?? DEFAULT_TELEPHONY_FORMAT,
|
|
2272
|
+
id: [
|
|
2273
|
+
carrier,
|
|
2274
|
+
streamId ?? input.sessionId ?? "stream",
|
|
2275
|
+
sequenceNumber ?? timestamp ?? Date.now()
|
|
2276
|
+
].join(":"),
|
|
2277
|
+
kind: telephonyFrameKind(direction),
|
|
2278
|
+
metadata: {
|
|
2279
|
+
carrier,
|
|
2280
|
+
direction,
|
|
2281
|
+
event: firstString([envelope], ["event", "type"]),
|
|
2282
|
+
sequenceNumber,
|
|
2283
|
+
streamId,
|
|
2284
|
+
track
|
|
2285
|
+
},
|
|
2286
|
+
sessionId: input.sessionId ?? streamId,
|
|
2287
|
+
source: "telephony"
|
|
2288
|
+
};
|
|
2289
|
+
};
|
|
2290
|
+
var serializeTelephonyMediaFrame = (input) => {
|
|
2291
|
+
const carrier = input.carrier ?? input.frame.metadata?.carrier ?? "telephony";
|
|
2292
|
+
const streamId = input.streamId ?? (typeof input.frame.metadata?.streamId === "string" ? input.frame.metadata.streamId : input.frame.sessionId);
|
|
2293
|
+
const sequenceNumber = input.sequenceNumber ?? (typeof input.frame.metadata?.sequenceNumber === "string" || typeof input.frame.metadata?.sequenceNumber === "number" ? input.frame.metadata.sequenceNumber : undefined);
|
|
2294
|
+
const direction = input.frame.kind === "assistant-audio" ? "outbound" : "inbound";
|
|
2295
|
+
const payload = input.frame.audio ? bytesToBase64(input.frame.audio) : "";
|
|
2296
|
+
if (carrier === "twilio") {
|
|
2297
|
+
return {
|
|
2298
|
+
event: "media",
|
|
2299
|
+
sequenceNumber,
|
|
2300
|
+
streamSid: streamId,
|
|
2301
|
+
media: {
|
|
2302
|
+
payload,
|
|
2303
|
+
timestamp: input.frame.at,
|
|
2304
|
+
track: direction
|
|
2305
|
+
}
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
if (carrier === "telnyx") {
|
|
2309
|
+
return {
|
|
2310
|
+
event: "media",
|
|
2311
|
+
stream_id: streamId,
|
|
2312
|
+
sequence_number: sequenceNumber,
|
|
2313
|
+
media: {
|
|
2314
|
+
payload,
|
|
2315
|
+
timestamp: input.frame.at,
|
|
2316
|
+
track: direction
|
|
2317
|
+
}
|
|
2318
|
+
};
|
|
2319
|
+
}
|
|
2320
|
+
if (carrier === "plivo") {
|
|
2321
|
+
return {
|
|
2322
|
+
event: "media",
|
|
2323
|
+
streamId,
|
|
2324
|
+
sequenceNumber,
|
|
2325
|
+
media: {
|
|
2326
|
+
payload,
|
|
2327
|
+
timestamp: input.frame.at,
|
|
2328
|
+
track: direction
|
|
2329
|
+
}
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
return {
|
|
2333
|
+
event: "media",
|
|
2334
|
+
provider: carrier,
|
|
2335
|
+
sequenceNumber,
|
|
2336
|
+
streamId,
|
|
2337
|
+
media: {
|
|
2338
|
+
payload,
|
|
2339
|
+
timestamp: input.frame.at,
|
|
2340
|
+
track: direction
|
|
2341
|
+
}
|
|
2342
|
+
};
|
|
2343
|
+
};
|
|
2344
|
+
var createTelephonyMediaSerializer = (input) => {
|
|
2345
|
+
const format = input.format ?? DEFAULT_TELEPHONY_FORMAT;
|
|
2346
|
+
return {
|
|
2347
|
+
carrier: input.carrier,
|
|
2348
|
+
format,
|
|
2349
|
+
parse: (envelope) => parseTelephonyMediaFrame({
|
|
2350
|
+
carrier: input.carrier,
|
|
2351
|
+
envelope,
|
|
2352
|
+
format,
|
|
2353
|
+
sessionId: input.sessionId ?? input.streamId
|
|
2354
|
+
}),
|
|
2355
|
+
serialize: (frame) => serializeTelephonyMediaFrame({
|
|
2356
|
+
carrier: input.carrier,
|
|
2357
|
+
frame,
|
|
2358
|
+
streamId: input.streamId
|
|
2359
|
+
})
|
|
2360
|
+
};
|
|
2361
|
+
};
|
|
2198
2362
|
var buildMediaResamplingPlan = (input) => {
|
|
2199
2363
|
const required = !formatMatches(input.inputFormat, input.outputFormat);
|
|
2200
2364
|
return {
|
|
@@ -8899,7 +9063,7 @@ var assertVoiceTelephonyWebhookNormalizationEvidence = (input = {}) => {
|
|
|
8899
9063
|
return assertion;
|
|
8900
9064
|
};
|
|
8901
9065
|
var normalizeToken = (value) => typeof value === "string" ? value.trim().toLowerCase().replace(/\s+/g, "-").replace(/_+/g, "-") : undefined;
|
|
8902
|
-
var
|
|
9066
|
+
var firstString2 = (source, keys) => {
|
|
8903
9067
|
for (const key of keys) {
|
|
8904
9068
|
const value = source[key];
|
|
8905
9069
|
if (typeof value === "string" && value.trim()) {
|
|
@@ -8910,7 +9074,7 @@ var firstString = (source, keys) => {
|
|
|
8910
9074
|
}
|
|
8911
9075
|
}
|
|
8912
9076
|
};
|
|
8913
|
-
var
|
|
9077
|
+
var firstNumber2 = (source, keys) => {
|
|
8914
9078
|
for (const key of keys) {
|
|
8915
9079
|
const value = source[key];
|
|
8916
9080
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
@@ -9271,8 +9435,8 @@ var verifyVoiceTelephonyWebhook = async (input) => {
|
|
|
9271
9435
|
var durationMsFromSeconds = (value) => typeof value === "number" ? value * 1000 : undefined;
|
|
9272
9436
|
var parseVoiceTelephonyWebhookEvent = (input) => {
|
|
9273
9437
|
const payload = flattenPayload(input.body);
|
|
9274
|
-
const provider =
|
|
9275
|
-
const status =
|
|
9438
|
+
const provider = firstString2(payload, ["provider", "Provider"]) ?? input.provider;
|
|
9439
|
+
const status = firstString2(payload, [
|
|
9276
9440
|
"CallStatus",
|
|
9277
9441
|
"call_status",
|
|
9278
9442
|
"callStatus",
|
|
@@ -9282,7 +9446,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
|
|
|
9282
9446
|
"event_type",
|
|
9283
9447
|
"type"
|
|
9284
9448
|
]);
|
|
9285
|
-
const durationMs =
|
|
9449
|
+
const durationMs = firstNumber2(payload, ["durationMs", "duration_ms"]) ?? durationMsFromSeconds(firstNumber2(payload, [
|
|
9286
9450
|
"CallDuration",
|
|
9287
9451
|
"call_duration",
|
|
9288
9452
|
"callDuration",
|
|
@@ -9290,16 +9454,16 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
|
|
|
9290
9454
|
"dial_call_duration",
|
|
9291
9455
|
"duration"
|
|
9292
9456
|
]));
|
|
9293
|
-
const sipCode =
|
|
9457
|
+
const sipCode = firstNumber2(payload, [
|
|
9294
9458
|
"SipResponseCode",
|
|
9295
9459
|
"sip_response_code",
|
|
9296
9460
|
"sipCode",
|
|
9297
9461
|
"sip_code",
|
|
9298
9462
|
"hangupCauseCode"
|
|
9299
9463
|
]);
|
|
9300
|
-
const from =
|
|
9301
|
-
const to =
|
|
9302
|
-
const target =
|
|
9464
|
+
const from = firstString2(payload, ["From", "from", "caller_id", "callerId"]);
|
|
9465
|
+
const to = firstString2(payload, ["To", "to", "called_number", "calledNumber"]);
|
|
9466
|
+
const target = firstString2(payload, [
|
|
9303
9467
|
"transferTarget",
|
|
9304
9468
|
"TransferTarget",
|
|
9305
9469
|
"target",
|
|
@@ -9307,7 +9471,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
|
|
|
9307
9471
|
"department"
|
|
9308
9472
|
]);
|
|
9309
9473
|
return {
|
|
9310
|
-
answeredBy:
|
|
9474
|
+
answeredBy: firstString2(payload, [
|
|
9311
9475
|
"AnsweredBy",
|
|
9312
9476
|
"answered_by",
|
|
9313
9477
|
"answeredBy",
|
|
@@ -9321,7 +9485,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
|
|
|
9321
9485
|
...payload
|
|
9322
9486
|
},
|
|
9323
9487
|
provider,
|
|
9324
|
-
reason:
|
|
9488
|
+
reason: firstString2(payload, [
|
|
9325
9489
|
"Reason",
|
|
9326
9490
|
"reason",
|
|
9327
9491
|
"HangupCause",
|
|
@@ -9337,7 +9501,7 @@ var parseVoiceTelephonyWebhookEvent = (input) => {
|
|
|
9337
9501
|
var defaultSessionId = (input) => {
|
|
9338
9502
|
const payload = flattenPayload(input.body);
|
|
9339
9503
|
const metadataSessionId = input.event.metadata?.sessionId;
|
|
9340
|
-
return
|
|
9504
|
+
return firstString2(input.query, ["sessionId", "session_id"]) ?? firstString2(payload, [
|
|
9341
9505
|
"sessionId",
|
|
9342
9506
|
"session_id",
|
|
9343
9507
|
"SessionId",
|
|
@@ -9352,7 +9516,7 @@ var defaultSessionId = (input) => {
|
|
|
9352
9516
|
};
|
|
9353
9517
|
var defaultIdempotencyKey = (input) => {
|
|
9354
9518
|
const payload = flattenPayload(input.body);
|
|
9355
|
-
const eventId =
|
|
9519
|
+
const eventId = firstString2(payload, [
|
|
9356
9520
|
"id",
|
|
9357
9521
|
"event_id",
|
|
9358
9522
|
"eventId",
|