@sayna-ai/node-sdk 0.0.4 → 0.0.6
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/README.md +29 -22
- package/dist/index.js +166 -21
- package/dist/index.js.map +6 -5
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/{index.d.ts → src/index.d.ts} +4 -2
- package/dist/src/index.d.ts.map +1 -0
- package/dist/{sayna-client.d.ts → src/sayna-client.d.ts} +14 -12
- package/dist/src/sayna-client.d.ts.map +1 -0
- package/dist/{types.d.ts → src/types.d.ts} +47 -4
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/webhook-receiver.d.ts +203 -0
- package/dist/src/webhook-receiver.d.ts.map +1 -0
- package/dist/tests/errors.test.d.ts +2 -0
- package/dist/tests/errors.test.d.ts.map +1 -0
- package/dist/tests/sayna-client.test.d.ts +2 -0
- package/dist/tests/sayna-client.test.d.ts.map +1 -0
- package/dist/tests/types.test.d.ts +2 -0
- package/dist/tests/types.test.d.ts.map +1 -0
- package/dist/tests/webhook-receiver.test.d.ts +2 -0
- package/dist/tests/webhook-receiver.test.d.ts.map +1 -0
- package/package.json +7 -5
- package/dist/errors.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/sayna-client.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
- /package/dist/{errors.d.ts → src/errors.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -52,6 +52,7 @@ Performs a health check on the Sayna server.
|
|
|
52
52
|
**Returns**: `Promise<{ status: string }>` - Status object with "OK" when healthy.
|
|
53
53
|
|
|
54
54
|
**Example**:
|
|
55
|
+
|
|
55
56
|
```typescript
|
|
56
57
|
const health = await client.health();
|
|
57
58
|
console.log(health.status); // "OK"
|
|
@@ -64,10 +65,14 @@ Retrieves the catalogue of text-to-speech voices grouped by provider.
|
|
|
64
65
|
**Returns**: `Promise<Record<string, Voice[]>>` - Object where keys are provider names and values are arrays of voice descriptors.
|
|
65
66
|
|
|
66
67
|
**Example**:
|
|
68
|
+
|
|
67
69
|
```typescript
|
|
68
70
|
const voices = await client.getVoices();
|
|
69
71
|
for (const [provider, voiceList] of Object.entries(voices)) {
|
|
70
|
-
console.log(
|
|
72
|
+
console.log(
|
|
73
|
+
`${provider}:`,
|
|
74
|
+
voiceList.map((v) => v.name)
|
|
75
|
+
);
|
|
71
76
|
}
|
|
72
77
|
```
|
|
73
78
|
|
|
@@ -75,14 +80,15 @@ for (const [provider, voiceList] of Object.entries(voices)) {
|
|
|
75
80
|
|
|
76
81
|
Synthesizes text into audio using the REST API. This is a standalone method that doesn't require an active WebSocket connection.
|
|
77
82
|
|
|
78
|
-
| parameter
|
|
79
|
-
|
|
|
80
|
-
| `text`
|
|
81
|
-
| `ttsConfig` | `TTSConfig` | Text-to-speech provider configuration.
|
|
83
|
+
| parameter | type | purpose |
|
|
84
|
+
| ----------- | ----------- | --------------------------------------- |
|
|
85
|
+
| `text` | `string` | Text to synthesize (must be non-empty). |
|
|
86
|
+
| `ttsConfig` | `TTSConfig` | Text-to-speech provider configuration. |
|
|
82
87
|
|
|
83
88
|
**Returns**: `Promise<ArrayBuffer>` - Raw audio data.
|
|
84
89
|
|
|
85
90
|
**Example**:
|
|
91
|
+
|
|
86
92
|
```typescript
|
|
87
93
|
const audioBuffer = await client.speakRest("Hello, world!", {
|
|
88
94
|
provider: "elevenlabs",
|
|
@@ -93,7 +99,7 @@ const audioBuffer = await client.speakRest("Hello, world!", {
|
|
|
93
99
|
sample_rate: 24000,
|
|
94
100
|
connection_timeout: 30,
|
|
95
101
|
request_timeout: 60,
|
|
96
|
-
pronunciations: []
|
|
102
|
+
pronunciations: [],
|
|
97
103
|
});
|
|
98
104
|
```
|
|
99
105
|
|
|
@@ -101,15 +107,16 @@ const audioBuffer = await client.speakRest("Hello, world!", {
|
|
|
101
107
|
|
|
102
108
|
Issues a LiveKit access token for a participant.
|
|
103
109
|
|
|
104
|
-
| parameter
|
|
105
|
-
|
|
|
106
|
-
| `roomName`
|
|
107
|
-
| `participantName`
|
|
110
|
+
| parameter | type | purpose |
|
|
111
|
+
| --------------------- | -------- | -------------------------------------- |
|
|
112
|
+
| `roomName` | `string` | LiveKit room to join or create. |
|
|
113
|
+
| `participantName` | `string` | Display name for the participant. |
|
|
108
114
|
| `participantIdentity` | `string` | Unique identifier for the participant. |
|
|
109
115
|
|
|
110
116
|
**Returns**: `Promise<LiveKitTokenResponse>` - Object containing token, room name, participant identity, and LiveKit URL.
|
|
111
117
|
|
|
112
118
|
**Example**:
|
|
119
|
+
|
|
113
120
|
```typescript
|
|
114
121
|
const tokenInfo = await client.getLiveKitToken(
|
|
115
122
|
"my-room",
|
|
@@ -128,13 +135,13 @@ These methods require an active WebSocket connection:
|
|
|
128
135
|
|
|
129
136
|
### `new SaynaClient(url, sttConfig, ttsConfig, livekitConfig?, withoutAudio?)`
|
|
130
137
|
|
|
131
|
-
| parameter
|
|
132
|
-
|
|
|
133
|
-
| `url`
|
|
134
|
-
| `sttConfig`
|
|
135
|
-
| `ttsConfig`
|
|
136
|
-
| `livekitConfig` | `LiveKitConfig` | Optional LiveKit room configuration.
|
|
137
|
-
| `withoutAudio`
|
|
138
|
+
| parameter | type | purpose |
|
|
139
|
+
| --------------- | --------------- | ------------------------------------------------------- |
|
|
140
|
+
| `url` | `string` | Sayna server URL (http://, https://, ws://, or wss://). |
|
|
141
|
+
| `sttConfig` | `STTConfig` | Speech-to-text provider configuration. |
|
|
142
|
+
| `ttsConfig` | `TTSConfig` | Text-to-speech provider configuration. |
|
|
143
|
+
| `livekitConfig` | `LiveKitConfig` | Optional LiveKit room configuration. |
|
|
144
|
+
| `withoutAudio` | `boolean` | Disable audio streaming (defaults to `false`). |
|
|
138
145
|
|
|
139
146
|
### `await client.connect()`
|
|
140
147
|
|
|
@@ -168,11 +175,11 @@ Registers a callback for TTS playback completion events.
|
|
|
168
175
|
|
|
169
176
|
Sends text to be synthesized as speech.
|
|
170
177
|
|
|
171
|
-
| parameter
|
|
172
|
-
|
|
|
173
|
-
| `text`
|
|
174
|
-
| `flush`
|
|
175
|
-
| `allowInterruption` | `boolean` | `true`
|
|
178
|
+
| parameter | type | default | purpose |
|
|
179
|
+
| ------------------- | --------- | ------- | -------------------------------- |
|
|
180
|
+
| `text` | `string` | - | Text to synthesize. |
|
|
181
|
+
| `flush` | `boolean` | `true` | Clear TTS queue before speaking. |
|
|
182
|
+
| `allowInterruption` | `boolean` | `true` | Allow speech to be interrupted. |
|
|
176
183
|
|
|
177
184
|
### `await client.onAudioInput(audioData)`
|
|
178
185
|
|
package/dist/index.js
CHANGED
|
@@ -56,6 +56,7 @@ class SaynaClient {
|
|
|
56
56
|
ttsConfig;
|
|
57
57
|
livekitConfig;
|
|
58
58
|
withoutAudio;
|
|
59
|
+
apiKey;
|
|
59
60
|
websocket;
|
|
60
61
|
isConnected = false;
|
|
61
62
|
isReady = false;
|
|
@@ -71,18 +72,24 @@ class SaynaClient {
|
|
|
71
72
|
ttsPlaybackCompleteCallback;
|
|
72
73
|
readyPromiseResolve;
|
|
73
74
|
readyPromiseReject;
|
|
74
|
-
constructor(url, sttConfig, ttsConfig, livekitConfig, withoutAudio = false) {
|
|
75
|
+
constructor(url, sttConfig, ttsConfig, livekitConfig, withoutAudio = false, apiKey) {
|
|
75
76
|
if (!url || typeof url !== "string") {
|
|
76
77
|
throw new SaynaValidationError("URL must be a non-empty string");
|
|
77
78
|
}
|
|
78
79
|
if (!url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("ws://") && !url.startsWith("wss://")) {
|
|
79
80
|
throw new SaynaValidationError("URL must start with http://, https://, ws://, or wss://");
|
|
80
81
|
}
|
|
82
|
+
if (!withoutAudio) {
|
|
83
|
+
if (!sttConfig || !ttsConfig) {
|
|
84
|
+
throw new SaynaValidationError("sttConfig and ttsConfig are required when withoutAudio=false (audio streaming enabled). " + "Either provide both configs or set withoutAudio=true for non-audio use cases.");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
81
87
|
this.url = url;
|
|
82
88
|
this.sttConfig = sttConfig;
|
|
83
89
|
this.ttsConfig = ttsConfig;
|
|
84
90
|
this.livekitConfig = livekitConfig;
|
|
85
91
|
this.withoutAudio = withoutAudio;
|
|
92
|
+
this.apiKey = apiKey ?? process.env.SAYNA_API_KEY;
|
|
86
93
|
}
|
|
87
94
|
async connect() {
|
|
88
95
|
if (this.isConnected) {
|
|
@@ -93,7 +100,9 @@ class SaynaClient {
|
|
|
93
100
|
this.readyPromiseResolve = resolve;
|
|
94
101
|
this.readyPromiseReject = reject;
|
|
95
102
|
try {
|
|
96
|
-
this.
|
|
103
|
+
const headers = this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : undefined;
|
|
104
|
+
const WebSocketConstructor = WebSocket;
|
|
105
|
+
this.websocket = headers ? new WebSocketConstructor(wsUrl, undefined, { headers }) : new WebSocket(wsUrl);
|
|
97
106
|
this.websocket.onopen = () => {
|
|
98
107
|
this.isConnected = true;
|
|
99
108
|
const configMessage = {
|
|
@@ -123,14 +132,18 @@ class SaynaClient {
|
|
|
123
132
|
await this.ttsCallback(buffer);
|
|
124
133
|
}
|
|
125
134
|
} else {
|
|
135
|
+
if (typeof event.data !== "string") {
|
|
136
|
+
throw new Error("Expected string data for JSON messages");
|
|
137
|
+
}
|
|
126
138
|
const data = JSON.parse(event.data);
|
|
127
139
|
await this.handleJsonMessage(data);
|
|
128
140
|
}
|
|
129
141
|
} catch (error) {
|
|
130
142
|
if (this.errorCallback) {
|
|
143
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
131
144
|
await this.errorCallback({
|
|
132
145
|
type: "error",
|
|
133
|
-
message: `Failed to process message: ${
|
|
146
|
+
message: `Failed to process message: ${errorMessage}`
|
|
134
147
|
});
|
|
135
148
|
}
|
|
136
149
|
}
|
|
@@ -234,6 +247,10 @@ class SaynaClient {
|
|
|
234
247
|
const headers = {
|
|
235
248
|
...options.headers
|
|
236
249
|
};
|
|
250
|
+
const hasAuthHeader = Object.keys(headers).some((key) => key.toLowerCase() === "authorization");
|
|
251
|
+
if (this.apiKey && !hasAuthHeader) {
|
|
252
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
253
|
+
}
|
|
237
254
|
if (options.method === "POST" && options.body && !headers["Content-Type"]) {
|
|
238
255
|
headers["Content-Type"] = "application/json";
|
|
239
256
|
}
|
|
@@ -243,10 +260,14 @@ class SaynaClient {
|
|
|
243
260
|
headers
|
|
244
261
|
});
|
|
245
262
|
if (!response.ok) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
263
|
+
let errorMessage;
|
|
264
|
+
try {
|
|
265
|
+
const errorData = await response.json();
|
|
266
|
+
errorMessage = errorData && typeof errorData === "object" && "error" in errorData && typeof errorData.error === "string" ? errorData.error : `Request failed: ${response.status} ${response.statusText}`;
|
|
267
|
+
} catch {
|
|
268
|
+
errorMessage = `Request failed: ${response.status} ${response.statusText}`;
|
|
269
|
+
}
|
|
270
|
+
throw new SaynaServerError(errorMessage);
|
|
250
271
|
}
|
|
251
272
|
if (responseType === "arrayBuffer") {
|
|
252
273
|
return await response.arrayBuffer();
|
|
@@ -260,7 +281,7 @@ class SaynaClient {
|
|
|
260
281
|
throw new SaynaConnectionError(`Failed to fetch from ${endpoint}`, error);
|
|
261
282
|
}
|
|
262
283
|
}
|
|
263
|
-
|
|
284
|
+
disconnect() {
|
|
264
285
|
if (this.websocket) {
|
|
265
286
|
this.websocket.onopen = null;
|
|
266
287
|
this.websocket.onmessage = null;
|
|
@@ -273,7 +294,7 @@ class SaynaClient {
|
|
|
273
294
|
}
|
|
274
295
|
this.cleanup();
|
|
275
296
|
}
|
|
276
|
-
|
|
297
|
+
onAudioInput(audioData) {
|
|
277
298
|
if (!this.isConnected || !this.websocket) {
|
|
278
299
|
throw new SaynaNotConnectedError;
|
|
279
300
|
}
|
|
@@ -310,7 +331,7 @@ class SaynaClient {
|
|
|
310
331
|
registerOnTtsPlaybackComplete(callback) {
|
|
311
332
|
this.ttsPlaybackCompleteCallback = callback;
|
|
312
333
|
}
|
|
313
|
-
|
|
334
|
+
speak(text, flush = true, allowInterruption = true) {
|
|
314
335
|
if (!this.isConnected || !this.websocket) {
|
|
315
336
|
throw new SaynaNotConnectedError;
|
|
316
337
|
}
|
|
@@ -332,7 +353,7 @@ class SaynaClient {
|
|
|
332
353
|
throw new SaynaConnectionError("Failed to send speak command", error);
|
|
333
354
|
}
|
|
334
355
|
}
|
|
335
|
-
|
|
356
|
+
clear() {
|
|
336
357
|
if (!this.isConnected || !this.websocket) {
|
|
337
358
|
throw new SaynaNotConnectedError;
|
|
338
359
|
}
|
|
@@ -348,10 +369,10 @@ class SaynaClient {
|
|
|
348
369
|
throw new SaynaConnectionError("Failed to send clear command", error);
|
|
349
370
|
}
|
|
350
371
|
}
|
|
351
|
-
|
|
352
|
-
|
|
372
|
+
ttsFlush(allowInterruption = true) {
|
|
373
|
+
this.speak("", true, allowInterruption);
|
|
353
374
|
}
|
|
354
|
-
|
|
375
|
+
sendMessage(message, role, topic, debug) {
|
|
355
376
|
if (!this.isConnected || !this.websocket) {
|
|
356
377
|
throw new SaynaNotConnectedError;
|
|
357
378
|
}
|
|
@@ -378,16 +399,16 @@ class SaynaClient {
|
|
|
378
399
|
}
|
|
379
400
|
}
|
|
380
401
|
async health() {
|
|
381
|
-
return
|
|
402
|
+
return this.fetchFromSayna("");
|
|
382
403
|
}
|
|
383
404
|
async getVoices() {
|
|
384
|
-
return
|
|
405
|
+
return this.fetchFromSayna("voices");
|
|
385
406
|
}
|
|
386
407
|
async speakRest(text, ttsConfig) {
|
|
387
408
|
if (!text || text.trim().length === 0) {
|
|
388
409
|
throw new SaynaValidationError("Text cannot be empty");
|
|
389
410
|
}
|
|
390
|
-
return
|
|
411
|
+
return this.fetchFromSayna("speak", {
|
|
391
412
|
method: "POST",
|
|
392
413
|
body: JSON.stringify({
|
|
393
414
|
text,
|
|
@@ -405,7 +426,7 @@ class SaynaClient {
|
|
|
405
426
|
if (!participantIdentity || participantIdentity.trim().length === 0) {
|
|
406
427
|
throw new SaynaValidationError("participant_identity cannot be empty");
|
|
407
428
|
}
|
|
408
|
-
return
|
|
429
|
+
return this.fetchFromSayna("livekit/token", {
|
|
409
430
|
method: "POST",
|
|
410
431
|
body: JSON.stringify({
|
|
411
432
|
room_name: roomName,
|
|
@@ -433,15 +454,139 @@ class SaynaClient {
|
|
|
433
454
|
return this._saynaParticipantName;
|
|
434
455
|
}
|
|
435
456
|
}
|
|
457
|
+
// src/webhook-receiver.ts
|
|
458
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
459
|
+
var MIN_SECRET_LENGTH = 16;
|
|
460
|
+
var TIMESTAMP_TOLERANCE_SECONDS = 300;
|
|
461
|
+
|
|
462
|
+
class WebhookReceiver {
|
|
463
|
+
secret;
|
|
464
|
+
constructor(secret) {
|
|
465
|
+
const effectiveSecret = secret ?? process.env.SAYNA_WEBHOOK_SECRET;
|
|
466
|
+
if (!effectiveSecret) {
|
|
467
|
+
throw new SaynaValidationError("Webhook secret is required. Provide it as a constructor parameter or set SAYNA_WEBHOOK_SECRET environment variable.");
|
|
468
|
+
}
|
|
469
|
+
const trimmedSecret = effectiveSecret.trim();
|
|
470
|
+
if (trimmedSecret.length < MIN_SECRET_LENGTH) {
|
|
471
|
+
throw new SaynaValidationError(`Webhook secret must be at least ${MIN_SECRET_LENGTH} characters long. ` + `Received ${trimmedSecret.length} characters. ` + `Generate a secure secret with: openssl rand -hex 32`);
|
|
472
|
+
}
|
|
473
|
+
this.secret = trimmedSecret;
|
|
474
|
+
}
|
|
475
|
+
receive(headers, body) {
|
|
476
|
+
const normalizedHeaders = this.normalizeHeaders(headers);
|
|
477
|
+
const signature = this.getRequiredHeader(normalizedHeaders, "x-sayna-signature");
|
|
478
|
+
const timestamp = this.getRequiredHeader(normalizedHeaders, "x-sayna-timestamp");
|
|
479
|
+
const eventId = this.getRequiredHeader(normalizedHeaders, "x-sayna-event-id");
|
|
480
|
+
if (!signature.startsWith("v1=")) {
|
|
481
|
+
throw new SaynaValidationError("Invalid signature format. Expected 'v1=<hex>' but got: " + signature.substring(0, 10) + "...");
|
|
482
|
+
}
|
|
483
|
+
const signatureHex = signature.substring(3);
|
|
484
|
+
if (!/^[0-9a-f]{64}$/i.test(signatureHex)) {
|
|
485
|
+
throw new SaynaValidationError("Invalid signature: must be 64 hex characters (HMAC-SHA256)");
|
|
486
|
+
}
|
|
487
|
+
this.validateTimestamp(timestamp);
|
|
488
|
+
const canonical = `v1:${timestamp}:${eventId}:${body}`;
|
|
489
|
+
const hmac = createHmac("sha256", this.secret);
|
|
490
|
+
hmac.update(canonical, "utf8");
|
|
491
|
+
const expectedSignature = hmac.digest("hex");
|
|
492
|
+
if (!this.constantTimeEqual(signatureHex, expectedSignature)) {
|
|
493
|
+
throw new SaynaValidationError("Signature verification failed. The webhook may have been tampered with or the secret is incorrect.");
|
|
494
|
+
}
|
|
495
|
+
return this.parseAndValidatePayload(body);
|
|
496
|
+
}
|
|
497
|
+
normalizeHeaders(headers) {
|
|
498
|
+
const normalized = {};
|
|
499
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
500
|
+
if (value !== undefined) {
|
|
501
|
+
const stringValue = Array.isArray(value) ? value[0] : value;
|
|
502
|
+
if (stringValue) {
|
|
503
|
+
normalized[key.toLowerCase()] = stringValue;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return normalized;
|
|
508
|
+
}
|
|
509
|
+
getRequiredHeader(headers, name) {
|
|
510
|
+
const value = headers[name.toLowerCase()];
|
|
511
|
+
if (!value) {
|
|
512
|
+
throw new SaynaValidationError(`Missing required header: ${name}`);
|
|
513
|
+
}
|
|
514
|
+
return value;
|
|
515
|
+
}
|
|
516
|
+
validateTimestamp(timestampStr) {
|
|
517
|
+
const timestamp = Number(timestampStr);
|
|
518
|
+
if (isNaN(timestamp)) {
|
|
519
|
+
throw new SaynaValidationError(`Invalid timestamp format: expected Unix seconds but got '${timestampStr}'`);
|
|
520
|
+
}
|
|
521
|
+
const now = Math.floor(Date.now() / 1000);
|
|
522
|
+
const diff = Math.abs(now - timestamp);
|
|
523
|
+
if (diff > TIMESTAMP_TOLERANCE_SECONDS) {
|
|
524
|
+
throw new SaynaValidationError(`Timestamp outside replay protection window. ` + `Difference: ${diff} seconds (max allowed: ${TIMESTAMP_TOLERANCE_SECONDS}). ` + `This webhook may be a replay attack or there may be significant clock skew.`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
constantTimeEqual(a, b) {
|
|
528
|
+
if (a.length !== b.length) {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
const bufA = Buffer.from(a, "utf8");
|
|
532
|
+
const bufB = Buffer.from(b, "utf8");
|
|
533
|
+
return timingSafeEqual(bufA, bufB);
|
|
534
|
+
}
|
|
535
|
+
parseAndValidatePayload(body) {
|
|
536
|
+
let payload;
|
|
537
|
+
try {
|
|
538
|
+
payload = JSON.parse(body);
|
|
539
|
+
} catch (error) {
|
|
540
|
+
throw new SaynaValidationError(`Invalid JSON payload: ${error instanceof Error ? error.message : String(error)}`);
|
|
541
|
+
}
|
|
542
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
543
|
+
throw new SaynaValidationError("Webhook payload must be a JSON object");
|
|
544
|
+
}
|
|
545
|
+
const data = payload;
|
|
546
|
+
this.validateParticipant(data.participant);
|
|
547
|
+
this.validateRoom(data.room);
|
|
548
|
+
this.validateStringField(data, "from_phone_number", "from_phone_number");
|
|
549
|
+
this.validateStringField(data, "to_phone_number", "to_phone_number");
|
|
550
|
+
this.validateStringField(data, "room_prefix", "room_prefix");
|
|
551
|
+
this.validateStringField(data, "sip_host", "sip_host");
|
|
552
|
+
return data;
|
|
553
|
+
}
|
|
554
|
+
validateParticipant(participant) {
|
|
555
|
+
if (!participant || typeof participant !== "object" || Array.isArray(participant)) {
|
|
556
|
+
throw new SaynaValidationError("Webhook payload missing required field 'participant' (must be an object)");
|
|
557
|
+
}
|
|
558
|
+
const p = participant;
|
|
559
|
+
this.validateStringField(p, "identity", "participant.identity");
|
|
560
|
+
this.validateStringField(p, "sid", "participant.sid");
|
|
561
|
+
if (p.name !== undefined && typeof p.name !== "string") {
|
|
562
|
+
throw new SaynaValidationError("Field 'participant.name' must be a string if present");
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
validateRoom(room) {
|
|
566
|
+
if (!room || typeof room !== "object" || Array.isArray(room)) {
|
|
567
|
+
throw new SaynaValidationError("Webhook payload missing required field 'room' (must be an object)");
|
|
568
|
+
}
|
|
569
|
+
const r = room;
|
|
570
|
+
this.validateStringField(r, "name", "room.name");
|
|
571
|
+
this.validateStringField(r, "sid", "room.sid");
|
|
572
|
+
}
|
|
573
|
+
validateStringField(obj, field, displayName) {
|
|
574
|
+
const value = obj[field];
|
|
575
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
576
|
+
throw new SaynaValidationError(`Webhook payload missing required field '${displayName}' (must be a non-empty string)`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
436
580
|
|
|
437
581
|
// src/index.ts
|
|
438
|
-
async function saynaConnect(url, sttConfig, ttsConfig, livekitConfig, withoutAudio = false) {
|
|
439
|
-
const client = new SaynaClient(url, sttConfig, ttsConfig, livekitConfig, withoutAudio);
|
|
582
|
+
async function saynaConnect(url, sttConfig, ttsConfig, livekitConfig, withoutAudio = false, apiKey) {
|
|
583
|
+
const client = new SaynaClient(url, sttConfig, ttsConfig, livekitConfig, withoutAudio, apiKey);
|
|
440
584
|
await client.connect();
|
|
441
585
|
return client;
|
|
442
586
|
}
|
|
443
587
|
export {
|
|
444
588
|
saynaConnect,
|
|
589
|
+
WebhookReceiver,
|
|
445
590
|
SaynaValidationError,
|
|
446
591
|
SaynaServerError,
|
|
447
592
|
SaynaNotReadyError,
|
|
@@ -451,4 +596,4 @@ export {
|
|
|
451
596
|
SaynaClient
|
|
452
597
|
};
|
|
453
598
|
|
|
454
|
-
//# debugId=
|
|
599
|
+
//# debugId=61BF4B0E0558D09664756E2164756E21
|
package/dist/index.js.map
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/errors.ts", "../src/sayna-client.ts", "../src/index.ts"],
|
|
3
|
+
"sources": ["../src/errors.ts", "../src/sayna-client.ts", "../src/webhook-receiver.ts", "../src/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * Base error class for all Sayna SDK errors.\n */\nexport class SaynaError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SaynaError\";\n Object.setPrototypeOf(this, SaynaError.prototype);\n }\n}\n\n/**\n * Error thrown when attempting to use the client before it's connected.\n */\nexport class SaynaNotConnectedError extends SaynaError {\n constructor(message: string = \"Not connected to Sayna WebSocket\") {\n super(message);\n this.name = \"SaynaNotConnectedError\";\n Object.setPrototypeOf(this, SaynaNotConnectedError.prototype);\n }\n}\n\n/**\n * Error thrown when attempting operations before the client is ready.\n */\nexport class SaynaNotReadyError extends SaynaError {\n constructor(\n message: string = \"Sayna voice providers are not ready. Wait for the connection to be established.\"\n ) {\n super(message);\n this.name = \"SaynaNotReadyError\";\n Object.setPrototypeOf(this, SaynaNotReadyError.prototype);\n }\n}\n\n/**\n * Error thrown when WebSocket connection fails.\n */\nexport class SaynaConnectionError extends SaynaError {\n public override readonly cause?: unknown;\n\n constructor(message: string, cause?: unknown) {\n super(message);\n this.name = \"SaynaConnectionError\";\n this.cause = cause;\n Object.setPrototypeOf(this, SaynaConnectionError.prototype);\n }\n}\n\n/**\n * Error thrown when invalid parameters are provided.\n */\nexport class SaynaValidationError extends SaynaError {\n constructor(message: string) {\n super(message);\n this.name = \"SaynaValidationError\";\n Object.setPrototypeOf(this, SaynaValidationError.prototype);\n }\n}\n\n/**\n * Error thrown when the server returns an error.\n */\nexport class SaynaServerError extends SaynaError {\n constructor(message: string) {\n super(message);\n this.name = \"SaynaServerError\";\n Object.setPrototypeOf(this, SaynaServerError.prototype);\n }\n}\n",
|
|
6
|
-
"import type {\n STTConfig,\n TTSConfig,\n LiveKitConfig,\n ConfigMessage,\n SpeakMessage,\n ClearMessage,\n SendMessageMessage,\n ReadyMessage,\n STTResultMessage,\n ErrorMessage,\n MessageMessage,\n ParticipantDisconnectedMessage,\n OutgoingMessage,\n SaynaMessage,\n Participant,\n TTSPlaybackCompleteMessage,\n VoicesResponse,\n HealthResponse,\n LiveKitTokenResponse,\n} from \"./types\";\nimport {\n SaynaNotConnectedError,\n SaynaNotReadyError,\n SaynaConnectionError,\n SaynaValidationError,\n SaynaServerError,\n} from \"./errors\";\n\n// Node.js 18+ has native fetch support\ndeclare const fetch: typeof globalThis.fetch;\n\n/**\n * Event handler for speech-to-text results.\n */\nexport type STTResultHandler = (\n result: STTResultMessage\n) => void | Promise<void>;\n\n/**\n * Event handler for text-to-speech audio data.\n */\nexport type TTSAudioHandler = (audio: ArrayBuffer) => void | Promise<void>;\n\n/**\n * Event handler for error messages.\n */\nexport type ErrorHandler = (error: ErrorMessage) => void | Promise<void>;\n\n/**\n * Event handler for participant messages.\n */\nexport type MessageHandler = (message: SaynaMessage) => void | Promise<void>;\n\n/**\n * Event handler for participant disconnections.\n */\nexport type ParticipantDisconnectedHandler = (\n participant: Participant\n) => void | Promise<void>;\n\n/**\n * Event handler for TTS playback completion.\n */\nexport type TTSPlaybackCompleteHandler = (\n timestamp: number\n) => void | Promise<void>;\n\n/**\n * Client for connecting to Sayna WebSocket server for real-time voice interactions.\n *\n * @example\n * ```typescript\n * const client = await saynaConnect(\n * \"https://api.sayna.ai\",\n * sttConfig,\n * ttsConfig\n * );\n *\n * client.registerOnSttResult((result) => {\n * console.log(\"Transcription:\", result.transcript);\n * });\n *\n * await client.speak(\"Hello, world!\");\n * ```\n */\nexport class SaynaClient {\n private url: string;\n private sttConfig: STTConfig;\n private ttsConfig: TTSConfig;\n private livekitConfig?: LiveKitConfig;\n private withoutAudio: boolean;\n private websocket?: WebSocket;\n private isConnected: boolean = false;\n private isReady: boolean = false;\n private _livekitRoomName?: string;\n private _livekitUrl?: string;\n private _saynaParticipantIdentity?: string;\n private _saynaParticipantName?: string;\n private sttCallback?: STTResultHandler;\n private ttsCallback?: TTSAudioHandler;\n private errorCallback?: ErrorHandler;\n private messageCallback?: MessageHandler;\n private participantDisconnectedCallback?: ParticipantDisconnectedHandler;\n private ttsPlaybackCompleteCallback?: TTSPlaybackCompleteHandler;\n private readyPromiseResolve?: () => void;\n private readyPromiseReject?: (error: Error) => void;\n\n /**\n * Creates a new SaynaClient instance.\n *\n * @param url - The Sayna server URL (e.g., \"https://api.sayna.ai\")\n * @param sttConfig - Speech-to-text configuration\n * @param ttsConfig - Text-to-speech configuration\n * @param livekitConfig - Optional LiveKit room configuration\n * @param withoutAudio - If true, disables audio streaming (default: false)\n *\n * @throws {SaynaValidationError} If URL or configurations are invalid\n */\n constructor(\n url: string,\n sttConfig: STTConfig,\n ttsConfig: TTSConfig,\n livekitConfig?: LiveKitConfig,\n withoutAudio: boolean = false\n ) {\n // Validate URL\n if (!url || typeof url !== \"string\") {\n throw new SaynaValidationError(\"URL must be a non-empty string\");\n }\n if (\n !url.startsWith(\"http://\") &&\n !url.startsWith(\"https://\") &&\n !url.startsWith(\"ws://\") &&\n !url.startsWith(\"wss://\")\n ) {\n throw new SaynaValidationError(\n \"URL must start with http://, https://, ws://, or wss://\"\n );\n }\n\n this.url = url;\n this.sttConfig = sttConfig;\n this.ttsConfig = ttsConfig;\n this.livekitConfig = livekitConfig;\n this.withoutAudio = withoutAudio;\n }\n\n /**\n * Establishes connection to the Sayna WebSocket server.\n *\n * @throws {SaynaConnectionError} If connection fails\n * @returns Promise that resolves when the connection is ready\n */\n async connect(): Promise<void> {\n if (this.isConnected) {\n return;\n }\n\n // Convert HTTP(S) URL to WebSocket URL\n const wsUrl =\n this.url.replace(/^https:\\/\\//, \"wss://\").replace(/^http:\\/\\//, \"ws://\") +\n (this.url.endsWith(\"/\") ? \"ws\" : \"/ws\");\n\n return new Promise((resolve, reject) => {\n this.readyPromiseResolve = resolve;\n this.readyPromiseReject = reject;\n\n try {\n this.websocket = new WebSocket(wsUrl);\n\n this.websocket.onopen = () => {\n this.isConnected = true;\n\n // Send initial configuration\n const configMessage: ConfigMessage = {\n type: \"config\",\n stt_config: this.sttConfig,\n tts_config: this.ttsConfig,\n livekit: this.livekitConfig,\n audio: !this.withoutAudio,\n };\n\n try {\n if (this.websocket) {\n this.websocket.send(JSON.stringify(configMessage));\n }\n } catch (error) {\n this.cleanup();\n const err = new SaynaConnectionError(\n \"Failed to send configuration\",\n error\n );\n if (this.readyPromiseReject) {\n this.readyPromiseReject(err);\n }\n }\n };\n\n this.websocket.onmessage = async (event) => {\n try {\n if (\n event.data instanceof Blob ||\n event.data instanceof ArrayBuffer\n ) {\n // Binary TTS audio data\n const buffer =\n event.data instanceof Blob\n ? await event.data.arrayBuffer()\n : event.data;\n if (this.ttsCallback) {\n await this.ttsCallback(buffer);\n }\n } else {\n // JSON control messages\n const data = JSON.parse(event.data) as OutgoingMessage;\n await this.handleJsonMessage(data);\n }\n } catch (error) {\n // Log parse errors but don't break the connection\n if (this.errorCallback) {\n await this.errorCallback({\n type: \"error\",\n message: `Failed to process message: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n }\n };\n\n this.websocket.onerror = () => {\n const error = new SaynaConnectionError(\"WebSocket connection error\");\n if (this.readyPromiseReject && !this.isReady) {\n this.readyPromiseReject(error);\n }\n };\n\n this.websocket.onclose = (event) => {\n const wasReady = this.isReady;\n this.cleanup();\n\n // If connection closed before ready, reject the promise\n if (!wasReady && this.readyPromiseReject) {\n this.readyPromiseReject(\n new SaynaConnectionError(\n `WebSocket closed before ready (code: ${event.code}, reason: ${event.reason || \"none\"})`\n )\n );\n }\n };\n } catch (error) {\n reject(new SaynaConnectionError(\"Failed to create WebSocket\", error));\n }\n });\n }\n\n /**\n * Handles incoming JSON messages from the WebSocket.\n * @internal\n */\n private async handleJsonMessage(data: OutgoingMessage): Promise<void> {\n const messageType = data.type;\n\n try {\n switch (messageType) {\n case \"ready\": {\n const readyMsg = data as ReadyMessage;\n this.isReady = true;\n this._livekitRoomName = readyMsg.livekit_room_name;\n this._livekitUrl = readyMsg.livekit_url;\n this._saynaParticipantIdentity = readyMsg.sayna_participant_identity;\n this._saynaParticipantName = readyMsg.sayna_participant_name;\n if (this.readyPromiseResolve) {\n this.readyPromiseResolve();\n }\n break;\n }\n\n case \"stt_result\": {\n const sttResult = data as STTResultMessage;\n if (this.sttCallback) {\n await this.sttCallback(sttResult);\n }\n break;\n }\n\n case \"error\": {\n const errorMsg = data as ErrorMessage;\n if (this.errorCallback) {\n await this.errorCallback(errorMsg);\n }\n if (this.readyPromiseReject && !this.isReady) {\n this.readyPromiseReject(new SaynaServerError(errorMsg.message));\n }\n break;\n }\n\n case \"message\": {\n const messageData = data as MessageMessage;\n if (this.messageCallback) {\n await this.messageCallback(messageData.message);\n }\n break;\n }\n\n case \"participant_disconnected\": {\n const participantMsg = data as ParticipantDisconnectedMessage;\n if (this.participantDisconnectedCallback) {\n await this.participantDisconnectedCallback(\n participantMsg.participant\n );\n }\n break;\n }\n\n case \"tts_playback_complete\": {\n const ttsPlaybackCompleteMsg = data as TTSPlaybackCompleteMessage;\n if (this.ttsPlaybackCompleteCallback) {\n await this.ttsPlaybackCompleteCallback(ttsPlaybackCompleteMsg.timestamp);\n }\n break;\n }\n }\n } catch (error) {\n // Notify error callback if handler fails\n if (this.errorCallback) {\n await this.errorCallback({\n type: \"error\",\n message: `Handler error: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n }\n }\n\n /**\n * Cleans up internal state.\n * @internal\n */\n private cleanup(): void {\n this.isConnected = false;\n this.isReady = false;\n this._livekitRoomName = undefined;\n this._livekitUrl = undefined;\n this._saynaParticipantIdentity = undefined;\n this._saynaParticipantName = undefined;\n }\n\n /**\n * Converts WebSocket URL to HTTP URL for REST API calls.\n * @internal\n */\n private getHttpUrl(): string {\n return this.url\n .replace(/^ws:\\/\\//, \"http://\")\n .replace(/^wss:\\/\\//, \"https://\");\n }\n\n /**\n * Generic fetch helper for making REST API calls to Sayna server.\n * Handles URL construction, headers, error responses, and type conversion.\n * @internal\n *\n * @param endpoint - API endpoint path (e.g., \"/voices\", \"/speak\")\n * @param options - Fetch options including method, body, headers, etc.\n * @param responseType - Expected response type: \"json\" or \"arrayBuffer\"\n * @returns Promise resolving to the parsed response\n * @throws {SaynaConnectionError} If the network request fails\n * @throws {SaynaServerError} If the server returns an error response\n */\n private async fetchFromSayna<T>(\n endpoint: string,\n options: RequestInit = {},\n responseType: \"json\" | \"arrayBuffer\" = \"json\"\n ): Promise<T> {\n const httpUrl = this.getHttpUrl();\n const url = `${httpUrl}${httpUrl.endsWith(\"/\") ? \"\" : \"/\"}${endpoint.startsWith(\"/\") ? endpoint.slice(1) : endpoint}`;\n\n // Merge default headers with user-provided headers\n const headers: Record<string, string> = {\n ...(options.headers as Record<string, string>),\n };\n\n // Add Content-Type for JSON requests if not already set\n if (options.method === \"POST\" && options.body && !headers[\"Content-Type\"]) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n });\n\n if (!response.ok) {\n // Try to parse error message from JSON response\n const errorData = await response.json().catch(() => ({\n error: response.statusText,\n }));\n throw new SaynaServerError(\n errorData?.error ?? `Request failed: ${response.status} ${response.statusText}`\n );\n }\n\n // Parse response based on expected type\n if (responseType === \"arrayBuffer\") {\n return (await response.arrayBuffer()) as T;\n } else {\n return (await response.json()) as T;\n }\n } catch (error) {\n // Re-throw SaynaServerError as-is\n if (error instanceof SaynaServerError) {\n throw error;\n }\n // Wrap other errors in SaynaConnectionError\n throw new SaynaConnectionError(\n `Failed to fetch from ${endpoint}`,\n error\n );\n }\n }\n\n /**\n * Disconnects from the Sayna WebSocket server and cleans up resources.\n */\n async disconnect(): Promise<void> {\n if (this.websocket) {\n // Remove event handlers to prevent memory leaks\n this.websocket.onopen = null;\n this.websocket.onmessage = null;\n this.websocket.onerror = null;\n this.websocket.onclose = null;\n\n if (this.websocket.readyState === WebSocket.OPEN) {\n this.websocket.close(1000, \"Client disconnect\");\n }\n\n this.websocket = undefined;\n }\n\n this.cleanup();\n }\n\n /**\n * Sends audio data to the server for speech recognition.\n *\n * @param audioData - Raw audio data as ArrayBuffer\n * @throws {SaynaNotConnectedError} If not connected\n * @throws {SaynaNotReadyError} If connection is not ready\n */\n async onAudioInput(audioData: ArrayBuffer): Promise<void> {\n if (!this.isConnected || !this.websocket) {\n throw new SaynaNotConnectedError();\n }\n\n if (!this.isReady) {\n throw new SaynaNotReadyError();\n }\n\n if (!(audioData instanceof ArrayBuffer)) {\n throw new SaynaValidationError(\"audioData must be an ArrayBuffer\");\n }\n\n if (audioData.byteLength === 0) {\n throw new SaynaValidationError(\"audioData cannot be empty\");\n }\n\n try {\n this.websocket.send(audioData);\n } catch (error) {\n throw new SaynaConnectionError(\"Failed to send audio data\", error);\n }\n }\n\n /**\n * Registers a callback for speech-to-text results.\n *\n * @param callback - Function to call when STT results are received\n */\n registerOnSttResult(callback: STTResultHandler): void {\n this.sttCallback = callback;\n }\n\n /**\n * Registers a callback for text-to-speech audio data.\n *\n * @param callback - Function to call when TTS audio is received\n */\n registerOnTtsAudio(callback: TTSAudioHandler): void {\n this.ttsCallback = callback;\n }\n\n /**\n * Registers a callback for error messages.\n *\n * @param callback - Function to call when errors occur\n */\n registerOnError(callback: ErrorHandler): void {\n this.errorCallback = callback;\n }\n\n /**\n * Registers a callback for participant messages.\n *\n * @param callback - Function to call when messages are received\n */\n registerOnMessage(callback: MessageHandler): void {\n this.messageCallback = callback;\n }\n\n /**\n * Registers a callback for participant disconnection events.\n *\n * @param callback - Function to call when a participant disconnects\n */\n registerOnParticipantDisconnected(\n callback: ParticipantDisconnectedHandler\n ): void {\n this.participantDisconnectedCallback = callback;\n }\n\n /**\n * Registers a callback for TTS playback completion.\n *\n * @param callback - Function to call when TTS playback is complete\n */\n registerOnTtsPlaybackComplete(callback: TTSPlaybackCompleteHandler): void {\n this.ttsPlaybackCompleteCallback = callback;\n }\n\n /**\n * Sends text to be synthesized as speech.\n *\n * @param text - Text to synthesize\n * @param flush - Whether to flush the TTS queue before speaking (default: true)\n * @param allowInterruption - Whether this speech can be interrupted (default: true)\n * @throws {SaynaNotConnectedError} If not connected\n * @throws {SaynaNotReadyError} If connection is not ready\n * @throws {SaynaValidationError} If text is not a string\n */\n async speak(\n text: string,\n flush: boolean = true,\n allowInterruption: boolean = true\n ): Promise<void> {\n if (!this.isConnected || !this.websocket) {\n throw new SaynaNotConnectedError();\n }\n\n if (!this.isReady) {\n throw new SaynaNotReadyError();\n }\n\n if (typeof text !== \"string\") {\n throw new SaynaValidationError(\"text must be a string\");\n }\n\n try {\n const speakMessage: SpeakMessage = {\n type: \"speak\",\n text,\n flush,\n allow_interruption: allowInterruption,\n };\n this.websocket.send(JSON.stringify(speakMessage));\n } catch (error) {\n throw new SaynaConnectionError(\"Failed to send speak command\", error);\n }\n }\n\n /**\n * Clears the text-to-speech queue.\n *\n * @throws {SaynaNotConnectedError} If not connected\n * @throws {SaynaNotReadyError} If connection is not ready\n */\n async clear(): Promise<void> {\n if (!this.isConnected || !this.websocket) {\n throw new SaynaNotConnectedError();\n }\n\n if (!this.isReady) {\n throw new SaynaNotReadyError();\n }\n\n try {\n const clearMessage: ClearMessage = {\n type: \"clear\",\n };\n this.websocket.send(JSON.stringify(clearMessage));\n } catch (error) {\n throw new SaynaConnectionError(\"Failed to send clear command\", error);\n }\n }\n\n /**\n * Flushes the TTS queue by sending an empty speak command.\n *\n * @param allowInterruption - Whether the flush can be interrupted (default: true)\n * @throws {SaynaNotConnectedError} If not connected\n * @throws {SaynaNotReadyError} If connection is not ready\n */\n async ttsFlush(allowInterruption: boolean = true): Promise<void> {\n await this.speak(\"\", true, allowInterruption);\n }\n\n /**\n * Sends a message to the Sayna session.\n *\n * @param message - Message content\n * @param role - Message role (e.g., \"user\", \"assistant\")\n * @param topic - Optional topic identifier\n * @param debug - Optional debug metadata\n * @throws {SaynaNotConnectedError} If not connected\n * @throws {SaynaNotReadyError} If connection is not ready\n * @throws {SaynaValidationError} If parameters are invalid\n */\n async sendMessage(\n message: string,\n role: string,\n topic?: string,\n debug?: Record<string, unknown>\n ): Promise<void> {\n if (!this.isConnected || !this.websocket) {\n throw new SaynaNotConnectedError();\n }\n\n if (!this.isReady) {\n throw new SaynaNotReadyError();\n }\n\n if (typeof message !== \"string\") {\n throw new SaynaValidationError(\"message must be a string\");\n }\n\n if (typeof role !== \"string\") {\n throw new SaynaValidationError(\"role must be a string\");\n }\n\n try {\n const sendMsg: SendMessageMessage = {\n type: \"send_message\",\n message,\n role,\n topic,\n debug,\n };\n this.websocket.send(JSON.stringify(sendMsg));\n } catch (error) {\n throw new SaynaConnectionError(\"Failed to send message\", error);\n }\n }\n\n /**\n * Performs a health check on the Sayna server.\n *\n * @returns Promise that resolves with the health status\n * @throws {SaynaConnectionError} If the request fails\n * @throws {SaynaServerError} If server returns an error\n *\n * @example\n * ```typescript\n * const health = await client.health();\n * console.log(health.status); // \"OK\"\n * ```\n */\n async health(): Promise<HealthResponse> {\n return await this.fetchFromSayna<HealthResponse>(\"\");\n }\n\n /**\n * Retrieves the catalogue of text-to-speech voices grouped by provider.\n *\n * @returns Promise that resolves with voices organized by provider\n * @throws {SaynaConnectionError} If the request fails\n * @throws {SaynaServerError} If server returns an error\n *\n * @example\n * ```typescript\n * const voices = await client.getVoices();\n * for (const [provider, voiceList] of Object.entries(voices)) {\n * console.log(`${provider}:`, voiceList.map(v => v.name));\n * }\n * ```\n */\n async getVoices(): Promise<VoicesResponse> {\n return await this.fetchFromSayna<VoicesResponse>(\"voices\");\n }\n\n /**\n * Synthesizes text into audio using the REST API endpoint.\n * This is a standalone synthesis method that doesn't require an active WebSocket connection.\n *\n * @param text - Text to synthesize\n * @param ttsConfig - Text-to-speech configuration\n * @returns Promise that resolves with the audio data as ArrayBuffer\n * @throws {SaynaValidationError} If text is empty\n * @throws {SaynaConnectionError} If the request fails\n * @throws {SaynaServerError} If server returns an error\n *\n * @example\n * ```typescript\n * const audioBuffer = await client.speakRest(\"Hello, world!\", {\n * provider: \"elevenlabs\",\n * voice_id: \"21m00Tcm4TlvDq8ikWAM\",\n * model: \"eleven_turbo_v2\",\n * speaking_rate: 1.0,\n * audio_format: \"mp3\",\n * sample_rate: 24000,\n * connection_timeout: 30,\n * request_timeout: 60,\n * pronunciations: []\n * });\n * ```\n */\n async speakRest(text: string, ttsConfig: TTSConfig): Promise<ArrayBuffer> {\n if (!text || text.trim().length === 0) {\n throw new SaynaValidationError(\"Text cannot be empty\");\n }\n\n return await this.fetchFromSayna<ArrayBuffer>(\n \"speak\",\n {\n method: \"POST\",\n body: JSON.stringify({\n text,\n tts_config: ttsConfig,\n }),\n },\n \"arrayBuffer\"\n );\n }\n\n /**\n * Issues a LiveKit access token for a participant.\n *\n * @param roomName - LiveKit room to join or create\n * @param participantName - Display name assigned to the participant\n * @param participantIdentity - Unique identifier for the participant\n * @returns Promise that resolves with the LiveKit token and connection details\n * @throws {SaynaValidationError} If any parameter is empty\n * @throws {SaynaConnectionError} If the request fails\n * @throws {SaynaServerError} If server returns an error\n *\n * @example\n * ```typescript\n * const tokenInfo = await client.getLiveKitToken(\n * \"my-room\",\n * \"John Doe\",\n * \"user-123\"\n * );\n * console.log(\"Token:\", tokenInfo.token);\n * console.log(\"LiveKit URL:\", tokenInfo.livekit_url);\n * ```\n */\n async getLiveKitToken(\n roomName: string,\n participantName: string,\n participantIdentity: string\n ): Promise<LiveKitTokenResponse> {\n if (!roomName || roomName.trim().length === 0) {\n throw new SaynaValidationError(\"room_name cannot be empty\");\n }\n\n if (!participantName || participantName.trim().length === 0) {\n throw new SaynaValidationError(\"participant_name cannot be empty\");\n }\n\n if (!participantIdentity || participantIdentity.trim().length === 0) {\n throw new SaynaValidationError(\"participant_identity cannot be empty\");\n }\n\n return await this.fetchFromSayna<LiveKitTokenResponse>(\n \"livekit/token\",\n {\n method: \"POST\",\n body: JSON.stringify({\n room_name: roomName,\n participant_name: participantName,\n participant_identity: participantIdentity,\n }),\n }\n );\n }\n\n /**\n * Whether the client is ready to send/receive data.\n */\n get ready(): boolean {\n return this.isReady;\n }\n\n /**\n * Whether the client is connected to the WebSocket.\n */\n get connected(): boolean {\n return this.isConnected;\n }\n\n /**\n * LiveKit room name acknowledged by the server, if available.\n */\n get livekitRoomName(): string | undefined {\n return this._livekitRoomName;\n }\n\n /**\n * LiveKit WebSocket URL configured on the server, if available.\n */\n get livekitUrl(): string | undefined {\n return this._livekitUrl;\n }\n\n /**\n * Identity assigned to the agent participant when LiveKit is enabled, if available.\n */\n get saynaParticipantIdentity(): string | undefined {\n return this._saynaParticipantIdentity;\n }\n\n /**\n * Display name assigned to the agent participant when LiveKit is enabled, if available.\n */\n get saynaParticipantName(): string | undefined {\n return this._saynaParticipantName;\n }\n}\n",
|
|
7
|
-
"import { SaynaClient } from \"./sayna-client\";\nimport { STTConfig, TTSConfig, LiveKitConfig } from \"./types\";\n\nexport * from \"./sayna-client\";\nexport * from \"./types\";\nexport * from \"./errors\";\n\n/**\n * Creates and connects a new SaynaClient instance.\n *\n * This is the recommended way to create a Sayna client. It handles both\n * instantiation and connection, returning a ready-to-use client.\n *\n * @param url - The Sayna server URL (e.g., \"https://api.sayna.ai\")\n * @param sttConfig - Speech-to-text configuration\n * @param ttsConfig - Text-to-speech configuration\n * @param livekitConfig - Optional LiveKit room configuration\n * @param withoutAudio - If true, disables audio streaming (default: false)\n *\n * @returns Promise that resolves to a connected SaynaClient\n *\n * @throws {SaynaValidationError} If parameters are invalid\n * @throws {SaynaConnectionError} If connection fails\n * @throws {SaynaServerError} If server returns an error during setup\n *\n * @example\n * ```typescript\n * import { saynaConnect } from \"@sayna/node-sdk\";\n *\n * const client = await saynaConnect(\n * \"https://api.sayna.ai\",\n * {\n * provider: \"deepgram\",\n * language: \"en-US\",\n * sample_rate: 16000,\n * channels: 1,\n * punctuation: true,\n * encoding: \"linear16\",\n * model: \"nova-2\"\n * },\n * {\n * provider: \"elevenlabs\",\n * voice_id: \"21m00Tcm4TlvDq8ikWAM\",\n * speaking_rate: 1.0,\n * audio_format: \"pcm\",\n * sample_rate: 16000,\n * connection_timeout: 5000,\n * request_timeout: 10000,\n * model: \"eleven_turbo_v2\",\n * pronunciations: []\n * }\n * );\n *\n * // Register event handlers\n * client.registerOnSttResult((result) => {\n * console.log(\"Transcription:\", result.transcript);\n * });\n *\n * // Send text to be spoken\n * await client.speak(\"Hello, world!\");\n *\n * // Clean up\n * await client.disconnect();\n * ```\n */\nexport async function saynaConnect(\n url: string,\n sttConfig: STTConfig,\n ttsConfig: TTSConfig,\n livekitConfig?: LiveKitConfig,\n withoutAudio: boolean = false\n): Promise<SaynaClient> {\n const client = new SaynaClient(\n url,\n sttConfig,\n ttsConfig,\n livekitConfig,\n withoutAudio\n );\n await client.connect();\n return client;\n}\n"
|
|
6
|
+
"import type {\n STTConfig,\n TTSConfig,\n LiveKitConfig,\n ConfigMessage,\n SpeakMessage,\n ClearMessage,\n SendMessageMessage,\n STTResultMessage,\n ErrorMessage,\n OutgoingMessage,\n SaynaMessage,\n Participant,\n VoicesResponse,\n HealthResponse,\n LiveKitTokenResponse,\n} from \"./types\";\nimport {\n SaynaNotConnectedError,\n SaynaNotReadyError,\n SaynaConnectionError,\n SaynaValidationError,\n SaynaServerError,\n} from \"./errors\";\n\n// Node.js 18+ has native fetch support\ndeclare const fetch: typeof globalThis.fetch;\n\n/**\n * Event handler for speech-to-text results.\n */\nexport type STTResultHandler = (\n result: STTResultMessage\n) => void | Promise<void>;\n\n/**\n * Event handler for text-to-speech audio data.\n */\nexport type TTSAudioHandler = (audio: ArrayBuffer) => void | Promise<void>;\n\n/**\n * Event handler for error messages.\n */\nexport type ErrorHandler = (error: ErrorMessage) => void | Promise<void>;\n\n/**\n * Event handler for participant messages.\n */\nexport type MessageHandler = (message: SaynaMessage) => void | Promise<void>;\n\n/**\n * Event handler for participant disconnections.\n */\nexport type ParticipantDisconnectedHandler = (\n participant: Participant\n) => void | Promise<void>;\n\n/**\n * Event handler for TTS playback completion.\n */\nexport type TTSPlaybackCompleteHandler = (\n timestamp: number\n) => void | Promise<void>;\n\n/**\n * Client for connecting to Sayna WebSocket server for real-time voice interactions.\n *\n * @example\n * ```typescript\n * const client = await saynaConnect(\n * \"https://api.sayna.ai\",\n * sttConfig,\n * ttsConfig\n * );\n *\n * client.registerOnSttResult((result) => {\n * console.log(\"Transcription:\", result.transcript);\n * });\n *\n * await client.speak(\"Hello, world!\");\n * ```\n */\nexport class SaynaClient {\n private url: string;\n private sttConfig?: STTConfig;\n private ttsConfig?: TTSConfig;\n private livekitConfig?: LiveKitConfig;\n private withoutAudio: boolean;\n private apiKey?: string;\n private websocket?: WebSocket;\n private isConnected: boolean = false;\n private isReady: boolean = false;\n private _livekitRoomName?: string;\n private _livekitUrl?: string;\n private _saynaParticipantIdentity?: string;\n private _saynaParticipantName?: string;\n private sttCallback?: STTResultHandler;\n private ttsCallback?: TTSAudioHandler;\n private errorCallback?: ErrorHandler;\n private messageCallback?: MessageHandler;\n private participantDisconnectedCallback?: ParticipantDisconnectedHandler;\n private ttsPlaybackCompleteCallback?: TTSPlaybackCompleteHandler;\n private readyPromiseResolve?: () => void;\n private readyPromiseReject?: (error: Error) => void;\n\n /**\n * Creates a new SaynaClient instance.\n *\n * @param url - The Sayna server URL (e.g., \"https://api.sayna.ai\")\n * @param sttConfig - Speech-to-text configuration (required when withoutAudio=false)\n * @param ttsConfig - Text-to-speech configuration (required when withoutAudio=false)\n * @param livekitConfig - Optional LiveKit room configuration\n * @param withoutAudio - If true, disables audio streaming (default: false)\n * @param apiKey - Optional API key used to authorize HTTP and WebSocket calls (defaults to SAYNA_API_KEY env)\n *\n * @throws {SaynaValidationError} If URL is invalid or if audio configs are missing when audio is enabled\n */\n constructor(\n url: string,\n sttConfig?: STTConfig,\n ttsConfig?: TTSConfig,\n livekitConfig?: LiveKitConfig,\n withoutAudio: boolean = false,\n apiKey?: string\n ) {\n // Validate URL\n if (!url || typeof url !== \"string\") {\n throw new SaynaValidationError(\"URL must be a non-empty string\");\n }\n if (\n !url.startsWith(\"http://\") &&\n !url.startsWith(\"https://\") &&\n !url.startsWith(\"ws://\") &&\n !url.startsWith(\"wss://\")\n ) {\n throw new SaynaValidationError(\n \"URL must start with http://, https://, ws://, or wss://\"\n );\n }\n\n // Validate audio config requirements\n if (!withoutAudio) {\n if (!sttConfig || !ttsConfig) {\n throw new SaynaValidationError(\n \"sttConfig and ttsConfig are required when withoutAudio=false (audio streaming enabled). \" +\n \"Either provide both configs or set withoutAudio=true for non-audio use cases.\"\n );\n }\n }\n\n this.url = url;\n this.sttConfig = sttConfig;\n this.ttsConfig = ttsConfig;\n this.livekitConfig = livekitConfig;\n this.withoutAudio = withoutAudio;\n this.apiKey = apiKey ?? process.env.SAYNA_API_KEY;\n }\n\n /**\n * Establishes connection to the Sayna WebSocket server.\n *\n * @throws {SaynaConnectionError} If connection fails\n * @returns Promise that resolves when the connection is ready\n */\n async connect(): Promise<void> {\n if (this.isConnected) {\n return;\n }\n\n // Convert HTTP(S) URL to WebSocket URL\n const wsUrl =\n this.url.replace(/^https:\\/\\//, \"wss://\").replace(/^http:\\/\\//, \"ws://\") +\n (this.url.endsWith(\"/\") ? \"ws\" : \"/ws\");\n\n return new Promise((resolve, reject) => {\n this.readyPromiseResolve = resolve;\n this.readyPromiseReject = reject;\n\n try {\n const headers = this.apiKey\n ? { Authorization: `Bearer ${this.apiKey}` }\n : undefined;\n\n const WebSocketConstructor = WebSocket as unknown as {\n new (\n url: string,\n protocols?: string | string[],\n options?: { headers?: Record<string, string> }\n ): WebSocket;\n };\n\n this.websocket = headers\n ? new WebSocketConstructor(wsUrl, undefined, { headers })\n : new WebSocket(wsUrl);\n\n this.websocket.onopen = () => {\n this.isConnected = true;\n\n // Send initial configuration\n const configMessage: ConfigMessage = {\n type: \"config\",\n stt_config: this.sttConfig,\n tts_config: this.ttsConfig,\n livekit: this.livekitConfig,\n audio: !this.withoutAudio,\n };\n\n try {\n if (this.websocket) {\n this.websocket.send(JSON.stringify(configMessage));\n }\n } catch (error) {\n this.cleanup();\n const err = new SaynaConnectionError(\n \"Failed to send configuration\",\n error\n );\n if (this.readyPromiseReject) {\n this.readyPromiseReject(err);\n }\n }\n };\n\n this.websocket.onmessage = async (event) => {\n try {\n if (\n event.data instanceof Blob ||\n event.data instanceof ArrayBuffer\n ) {\n // Binary TTS audio data\n const buffer =\n event.data instanceof Blob\n ? await event.data.arrayBuffer()\n : event.data;\n if (this.ttsCallback) {\n await this.ttsCallback(buffer);\n }\n } else {\n // JSON control messages\n if (typeof event.data !== \"string\") {\n throw new Error(\"Expected string data for JSON messages\");\n }\n const data = JSON.parse(event.data) as OutgoingMessage;\n await this.handleJsonMessage(data);\n }\n } catch (error) {\n // Log parse errors but don't break the connection\n if (this.errorCallback) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n await this.errorCallback({\n type: \"error\",\n message: `Failed to process message: ${errorMessage}`,\n });\n }\n }\n };\n\n this.websocket.onerror = () => {\n const error = new SaynaConnectionError(\"WebSocket connection error\");\n if (this.readyPromiseReject && !this.isReady) {\n this.readyPromiseReject(error);\n }\n };\n\n this.websocket.onclose = (event) => {\n const wasReady = this.isReady;\n this.cleanup();\n\n // If connection closed before ready, reject the promise\n if (!wasReady && this.readyPromiseReject) {\n this.readyPromiseReject(\n new SaynaConnectionError(\n `WebSocket closed before ready (code: ${event.code}, reason: ${event.reason || \"none\"})`\n )\n );\n }\n };\n } catch (error) {\n reject(new SaynaConnectionError(\"Failed to create WebSocket\", error));\n }\n });\n }\n\n /**\n * Handles incoming JSON messages from the WebSocket.\n * @internal\n */\n private async handleJsonMessage(data: OutgoingMessage): Promise<void> {\n const messageType = data.type;\n\n try {\n switch (messageType) {\n case \"ready\": {\n const readyMsg = data;\n this.isReady = true;\n this._livekitRoomName = readyMsg.livekit_room_name;\n this._livekitUrl = readyMsg.livekit_url;\n this._saynaParticipantIdentity = readyMsg.sayna_participant_identity;\n this._saynaParticipantName = readyMsg.sayna_participant_name;\n if (this.readyPromiseResolve) {\n this.readyPromiseResolve();\n }\n break;\n }\n\n case \"stt_result\": {\n const sttResult = data;\n if (this.sttCallback) {\n await this.sttCallback(sttResult);\n }\n break;\n }\n\n case \"error\": {\n const errorMsg = data;\n if (this.errorCallback) {\n await this.errorCallback(errorMsg);\n }\n if (this.readyPromiseReject && !this.isReady) {\n this.readyPromiseReject(new SaynaServerError(errorMsg.message));\n }\n break;\n }\n\n case \"message\": {\n const messageData = data;\n if (this.messageCallback) {\n await this.messageCallback(messageData.message);\n }\n break;\n }\n\n case \"participant_disconnected\": {\n const participantMsg = data;\n if (this.participantDisconnectedCallback) {\n await this.participantDisconnectedCallback(\n participantMsg.participant\n );\n }\n break;\n }\n\n case \"tts_playback_complete\": {\n const ttsPlaybackCompleteMsg = data;\n if (this.ttsPlaybackCompleteCallback) {\n await this.ttsPlaybackCompleteCallback(\n ttsPlaybackCompleteMsg.timestamp\n );\n }\n break;\n }\n }\n } catch (error) {\n // Notify error callback if handler fails\n if (this.errorCallback) {\n await this.errorCallback({\n type: \"error\",\n message: `Handler error: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n }\n }\n\n /**\n * Cleans up internal state.\n * @internal\n */\n private cleanup(): void {\n this.isConnected = false;\n this.isReady = false;\n this._livekitRoomName = undefined;\n this._livekitUrl = undefined;\n this._saynaParticipantIdentity = undefined;\n this._saynaParticipantName = undefined;\n }\n\n /**\n * Converts WebSocket URL to HTTP URL for REST API calls.\n * @internal\n */\n private getHttpUrl(): string {\n return this.url\n .replace(/^ws:\\/\\//, \"http://\")\n .replace(/^wss:\\/\\//, \"https://\");\n }\n\n /**\n * Generic fetch helper for making REST API calls to Sayna server.\n * Handles URL construction, headers, error responses, and type conversion.\n * @internal\n *\n * @param endpoint - API endpoint path (e.g., \"/voices\", \"/speak\")\n * @param options - Fetch options including method, body, headers, etc.\n * @param responseType - Expected response type: \"json\" or \"arrayBuffer\"\n * @returns Promise resolving to the parsed response\n * @throws {SaynaConnectionError} If the network request fails\n * @throws {SaynaServerError} If the server returns an error response\n */\n private async fetchFromSayna<T>(\n endpoint: string,\n options: RequestInit = {},\n responseType: \"json\" | \"arrayBuffer\" = \"json\"\n ): Promise<T> {\n const httpUrl = this.getHttpUrl();\n const url = `${httpUrl}${httpUrl.endsWith(\"/\") ? \"\" : \"/\"}${endpoint.startsWith(\"/\") ? endpoint.slice(1) : endpoint}`;\n\n // Merge default headers with user-provided headers\n const headers: Record<string, string> = {\n ...(options.headers as Record<string, string>),\n };\n\n // Add Authorization header when an API key is provided, unless user supplied one\n const hasAuthHeader = Object.keys(headers).some(\n (key) => key.toLowerCase() === \"authorization\"\n );\n if (this.apiKey && !hasAuthHeader) {\n headers[\"Authorization\"] = `Bearer ${this.apiKey}`;\n }\n\n // Add Content-Type for JSON requests if not already set\n if (options.method === \"POST\" && options.body && !headers[\"Content-Type\"]) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n });\n\n if (!response.ok) {\n // Try to parse error message from JSON response\n let errorMessage: string;\n try {\n const errorData: unknown = await response.json();\n errorMessage =\n errorData &&\n typeof errorData === \"object\" &&\n \"error\" in errorData &&\n typeof errorData.error === \"string\"\n ? errorData.error\n : `Request failed: ${response.status} ${response.statusText}`;\n } catch {\n errorMessage = `Request failed: ${response.status} ${response.statusText}`;\n }\n throw new SaynaServerError(errorMessage);\n }\n\n // Parse response based on expected type\n if (responseType === \"arrayBuffer\") {\n return (await response.arrayBuffer()) as T;\n } else {\n return (await response.json()) as T;\n }\n } catch (error) {\n // Re-throw SaynaServerError as-is\n if (error instanceof SaynaServerError) {\n throw error;\n }\n // Wrap other errors in SaynaConnectionError\n throw new SaynaConnectionError(`Failed to fetch from ${endpoint}`, error);\n }\n }\n\n /**\n * Disconnects from the Sayna WebSocket server and cleans up resources.\n */\n disconnect(): void {\n if (this.websocket) {\n // Remove event handlers to prevent memory leaks\n this.websocket.onopen = null;\n this.websocket.onmessage = null;\n this.websocket.onerror = null;\n this.websocket.onclose = null;\n\n if (this.websocket.readyState === WebSocket.OPEN) {\n this.websocket.close(1000, \"Client disconnect\");\n }\n\n this.websocket = undefined;\n }\n\n this.cleanup();\n }\n\n /**\n * Sends audio data to the server for speech recognition.\n *\n * @param audioData - Raw audio data as ArrayBuffer\n * @throws {SaynaNotConnectedError} If not connected\n * @throws {SaynaNotReadyError} If connection is not ready\n */\n onAudioInput(audioData: ArrayBuffer): void {\n if (!this.isConnected || !this.websocket) {\n throw new SaynaNotConnectedError();\n }\n\n if (!this.isReady) {\n throw new SaynaNotReadyError();\n }\n\n if (!(audioData instanceof ArrayBuffer)) {\n throw new SaynaValidationError(\"audioData must be an ArrayBuffer\");\n }\n\n if (audioData.byteLength === 0) {\n throw new SaynaValidationError(\"audioData cannot be empty\");\n }\n\n try {\n this.websocket.send(audioData);\n } catch (error) {\n throw new SaynaConnectionError(\"Failed to send audio data\", error);\n }\n }\n\n /**\n * Registers a callback for speech-to-text results.\n *\n * @param callback - Function to call when STT results are received\n */\n registerOnSttResult(callback: STTResultHandler): void {\n this.sttCallback = callback;\n }\n\n /**\n * Registers a callback for text-to-speech audio data.\n *\n * @param callback - Function to call when TTS audio is received\n */\n registerOnTtsAudio(callback: TTSAudioHandler): void {\n this.ttsCallback = callback;\n }\n\n /**\n * Registers a callback for error messages.\n *\n * @param callback - Function to call when errors occur\n */\n registerOnError(callback: ErrorHandler): void {\n this.errorCallback = callback;\n }\n\n /**\n * Registers a callback for participant messages.\n *\n * @param callback - Function to call when messages are received\n */\n registerOnMessage(callback: MessageHandler): void {\n this.messageCallback = callback;\n }\n\n /**\n * Registers a callback for participant disconnection events.\n *\n * @param callback - Function to call when a participant disconnects\n */\n registerOnParticipantDisconnected(\n callback: ParticipantDisconnectedHandler\n ): void {\n this.participantDisconnectedCallback = callback;\n }\n\n /**\n * Registers a callback for TTS playback completion.\n *\n * @param callback - Function to call when TTS playback is complete\n */\n registerOnTtsPlaybackComplete(callback: TTSPlaybackCompleteHandler): void {\n this.ttsPlaybackCompleteCallback = callback;\n }\n\n /**\n * Sends text to be synthesized as speech.\n *\n * @param text - Text to synthesize\n * @param flush - Whether to flush the TTS queue before speaking (default: true)\n * @param allowInterruption - Whether this speech can be interrupted (default: true)\n * @throws {SaynaNotConnectedError} If not connected\n * @throws {SaynaNotReadyError} If connection is not ready\n * @throws {SaynaValidationError} If text is not a string\n */\n speak(\n text: string,\n flush: boolean = true,\n allowInterruption: boolean = true\n ): void {\n if (!this.isConnected || !this.websocket) {\n throw new SaynaNotConnectedError();\n }\n\n if (!this.isReady) {\n throw new SaynaNotReadyError();\n }\n\n if (typeof text !== \"string\") {\n throw new SaynaValidationError(\"text must be a string\");\n }\n\n try {\n const speakMessage: SpeakMessage = {\n type: \"speak\",\n text,\n flush,\n allow_interruption: allowInterruption,\n };\n this.websocket.send(JSON.stringify(speakMessage));\n } catch (error) {\n throw new SaynaConnectionError(\"Failed to send speak command\", error);\n }\n }\n\n /**\n * Clears the text-to-speech queue.\n *\n * @throws {SaynaNotConnectedError} If not connected\n * @throws {SaynaNotReadyError} If connection is not ready\n */\n clear(): void {\n if (!this.isConnected || !this.websocket) {\n throw new SaynaNotConnectedError();\n }\n\n if (!this.isReady) {\n throw new SaynaNotReadyError();\n }\n\n try {\n const clearMessage: ClearMessage = {\n type: \"clear\",\n };\n this.websocket.send(JSON.stringify(clearMessage));\n } catch (error) {\n throw new SaynaConnectionError(\"Failed to send clear command\", error);\n }\n }\n\n /**\n * Flushes the TTS queue by sending an empty speak command.\n *\n * @param allowInterruption - Whether the flush can be interrupted (default: true)\n * @throws {SaynaNotConnectedError} If not connected\n * @throws {SaynaNotReadyError} If connection is not ready\n */\n ttsFlush(allowInterruption: boolean = true): void {\n this.speak(\"\", true, allowInterruption);\n }\n\n /**\n * Sends a message to the Sayna session.\n *\n * @param message - Message content\n * @param role - Message role (e.g., \"user\", \"assistant\")\n * @param topic - Optional topic identifier\n * @param debug - Optional debug metadata\n * @throws {SaynaNotConnectedError} If not connected\n * @throws {SaynaNotReadyError} If connection is not ready\n * @throws {SaynaValidationError} If parameters are invalid\n */\n sendMessage(\n message: string,\n role: string,\n topic?: string,\n debug?: Record<string, unknown>\n ): void {\n if (!this.isConnected || !this.websocket) {\n throw new SaynaNotConnectedError();\n }\n\n if (!this.isReady) {\n throw new SaynaNotReadyError();\n }\n\n if (typeof message !== \"string\") {\n throw new SaynaValidationError(\"message must be a string\");\n }\n\n if (typeof role !== \"string\") {\n throw new SaynaValidationError(\"role must be a string\");\n }\n\n try {\n const sendMsg: SendMessageMessage = {\n type: \"send_message\",\n message,\n role,\n topic,\n debug,\n };\n this.websocket.send(JSON.stringify(sendMsg));\n } catch (error) {\n throw new SaynaConnectionError(\"Failed to send message\", error);\n }\n }\n\n /**\n * Performs a health check on the Sayna server.\n *\n * @returns Promise that resolves with the health status\n * @throws {SaynaConnectionError} If the request fails\n * @throws {SaynaServerError} If server returns an error\n *\n * @example\n * ```typescript\n * const health = await client.health();\n * console.log(health.status); // \"OK\"\n * ```\n */\n async health(): Promise<HealthResponse> {\n return this.fetchFromSayna<HealthResponse>(\"\");\n }\n\n /**\n * Retrieves the catalogue of text-to-speech voices grouped by provider.\n *\n * @returns Promise that resolves with voices organized by provider\n * @throws {SaynaConnectionError} If the request fails\n * @throws {SaynaServerError} If server returns an error\n *\n * @example\n * ```typescript\n * const voices = await client.getVoices();\n * for (const [provider, voiceList] of Object.entries(voices)) {\n * console.log(`${provider}:`, voiceList.map(v => v.name));\n * }\n * ```\n */\n async getVoices(): Promise<VoicesResponse> {\n return this.fetchFromSayna<VoicesResponse>(\"voices\");\n }\n\n /**\n * Synthesizes text into audio using the REST API endpoint.\n * This is a standalone synthesis method that doesn't require an active WebSocket connection.\n *\n * @param text - Text to synthesize\n * @param ttsConfig - Text-to-speech configuration\n * @returns Promise that resolves with the audio data as ArrayBuffer\n * @throws {SaynaValidationError} If text is empty\n * @throws {SaynaConnectionError} If the request fails\n * @throws {SaynaServerError} If server returns an error\n *\n * @example\n * ```typescript\n * const audioBuffer = await client.speakRest(\"Hello, world!\", {\n * provider: \"elevenlabs\",\n * voice_id: \"21m00Tcm4TlvDq8ikWAM\",\n * model: \"eleven_turbo_v2\",\n * speaking_rate: 1.0,\n * audio_format: \"mp3\",\n * sample_rate: 24000,\n * connection_timeout: 30,\n * request_timeout: 60,\n * pronunciations: []\n * });\n * ```\n */\n async speakRest(text: string, ttsConfig: TTSConfig): Promise<ArrayBuffer> {\n if (!text || text.trim().length === 0) {\n throw new SaynaValidationError(\"Text cannot be empty\");\n }\n\n return this.fetchFromSayna<ArrayBuffer>(\n \"speak\",\n {\n method: \"POST\",\n body: JSON.stringify({\n text,\n tts_config: ttsConfig,\n }),\n },\n \"arrayBuffer\"\n );\n }\n\n /**\n * Issues a LiveKit access token for a participant.\n *\n * @param roomName - LiveKit room to join or create\n * @param participantName - Display name assigned to the participant\n * @param participantIdentity - Unique identifier for the participant\n * @returns Promise that resolves with the LiveKit token and connection details\n * @throws {SaynaValidationError} If any parameter is empty\n * @throws {SaynaConnectionError} If the request fails\n * @throws {SaynaServerError} If server returns an error\n *\n * @example\n * ```typescript\n * const tokenInfo = await client.getLiveKitToken(\n * \"my-room\",\n * \"John Doe\",\n * \"user-123\"\n * );\n * console.log(\"Token:\", tokenInfo.token);\n * console.log(\"LiveKit URL:\", tokenInfo.livekit_url);\n * ```\n */\n async getLiveKitToken(\n roomName: string,\n participantName: string,\n participantIdentity: string\n ): Promise<LiveKitTokenResponse> {\n if (!roomName || roomName.trim().length === 0) {\n throw new SaynaValidationError(\"room_name cannot be empty\");\n }\n\n if (!participantName || participantName.trim().length === 0) {\n throw new SaynaValidationError(\"participant_name cannot be empty\");\n }\n\n if (!participantIdentity || participantIdentity.trim().length === 0) {\n throw new SaynaValidationError(\"participant_identity cannot be empty\");\n }\n\n return this.fetchFromSayna<LiveKitTokenResponse>(\"livekit/token\", {\n method: \"POST\",\n body: JSON.stringify({\n room_name: roomName,\n participant_name: participantName,\n participant_identity: participantIdentity,\n }),\n });\n }\n\n /**\n * Whether the client is ready to send/receive data.\n */\n get ready(): boolean {\n return this.isReady;\n }\n\n /**\n * Whether the client is connected to the WebSocket.\n */\n get connected(): boolean {\n return this.isConnected;\n }\n\n /**\n * LiveKit room name acknowledged by the server, if available.\n */\n get livekitRoomName(): string | undefined {\n return this._livekitRoomName;\n }\n\n /**\n * LiveKit WebSocket URL configured on the server, if available.\n */\n get livekitUrl(): string | undefined {\n return this._livekitUrl;\n }\n\n /**\n * Identity assigned to the agent participant when LiveKit is enabled, if available.\n */\n get saynaParticipantIdentity(): string | undefined {\n return this._saynaParticipantIdentity;\n }\n\n /**\n * Display name assigned to the agent participant when LiveKit is enabled, if available.\n */\n get saynaParticipantName(): string | undefined {\n return this._saynaParticipantName;\n }\n}\n",
|
|
7
|
+
"import { createHmac, timingSafeEqual } from \"crypto\";\nimport { SaynaValidationError } from \"./errors\";\nimport type { WebhookSIPOutput } from \"./types\";\n\n/**\n * Minimum required secret length in characters for security.\n * @internal\n */\nconst MIN_SECRET_LENGTH = 16;\n\n/**\n * Maximum allowed time difference in seconds for replay protection.\n * Webhooks with timestamps outside this window will be rejected.\n * @internal\n */\nconst TIMESTAMP_TOLERANCE_SECONDS = 300; // 5 minutes\n\n/**\n * Receives and verifies cryptographically signed webhooks from Sayna SIP service.\n *\n * This class handles the secure verification of webhook signatures using HMAC-SHA256,\n * validates timestamp freshness to prevent replay attacks, and parses the webhook\n * payload into a strongly-typed WebhookSIPOutput object.\n *\n * ## Security Features\n *\n * - **HMAC-SHA256 Signature Verification**: Ensures webhook authenticity\n * - **Constant-Time Comparison**: Prevents timing attack vulnerabilities\n * - **Replay Protection**: 5-minute timestamp window prevents replay attacks\n * - **Strict Validation**: Comprehensive checks on all required fields\n *\n * ## Usage\n *\n * ### Basic Example\n *\n * ```typescript\n * import { WebhookReceiver } from \"@sayna-ai/node-sdk\";\n *\n * // Initialize with secret (or uses SAYNA_WEBHOOK_SECRET env variable)\n * const receiver = new WebhookReceiver(\"your-secret-key-min-16-chars\");\n *\n * // In your Express route handler\n * app.post('/webhook', express.json({ verify: (req, res, buf) => {\n * req.rawBody = buf.toString('utf8');\n * }}), (req, res) => {\n * try {\n * const webhook = receiver.receive(req.headers, req.rawBody);\n *\n * console.log('Valid webhook received:');\n * console.log(' From:', webhook.from_phone_number);\n * console.log(' To:', webhook.to_phone_number);\n * console.log(' Room:', webhook.room.name);\n * console.log(' SIP Host:', webhook.sip_host);\n * console.log(' Participant:', webhook.participant.identity);\n *\n * res.status(200).json({ received: true });\n * } catch (error) {\n * console.error('Webhook verification failed:', error.message);\n * res.status(401).json({ error: 'Invalid signature' });\n * }\n * });\n * ```\n *\n * ### With Environment Variable\n *\n * ```typescript\n * // Set environment variable\n * process.env.SAYNA_WEBHOOK_SECRET = \"your-secret-key\";\n *\n * // Receiver automatically uses env variable\n * const receiver = new WebhookReceiver();\n * ```\n *\n * ### Fastify Example\n *\n * ```typescript\n * import Fastify from 'fastify';\n * import { WebhookReceiver } from \"@sayna-ai/node-sdk\";\n *\n * const fastify = Fastify();\n * const receiver = new WebhookReceiver();\n *\n * fastify.post('/webhook', {\n * config: {\n * rawBody: true\n * }\n * }, async (request, reply) => {\n * try {\n * const webhook = receiver.receive(\n * request.headers,\n * request.rawBody\n * );\n *\n * // Process webhook...\n *\n * return { received: true };\n * } catch (error) {\n * reply.code(401);\n * return { error: error.message };\n * }\n * });\n * ```\n *\n * ## Important Notes\n *\n * - **Raw Body Required**: You MUST pass the raw request body string, not the parsed JSON object.\n * The signature is computed over the exact bytes received, so any formatting changes will\n * cause verification to fail.\n *\n * - **Case-Insensitive Headers**: Header names are case-insensitive in HTTP. This class handles\n * both `X-Sayna-Signature` and `x-sayna-signature` correctly.\n *\n * - **Secret Security**: Never commit secrets to version control. Use environment variables\n * or a secret management system.\n *\n * @see WebhookSIPOutput\n */\nexport class WebhookReceiver {\n private readonly secret: string;\n\n /**\n * Creates a new webhook receiver with the specified signing secret.\n *\n * @param secret - HMAC signing secret (min 16 chars, 32+ recommended).\n * If not provided, uses SAYNA_WEBHOOK_SECRET environment variable.\n *\n * @throws {SaynaValidationError} If secret is missing or too short\n *\n * @example\n * ```typescript\n * // Explicit secret\n * const receiver = new WebhookReceiver(\"my-secret-key-at-least-16-chars\");\n *\n * // From environment variable\n * const receiver = new WebhookReceiver();\n * ```\n */\n constructor(secret?: string) {\n const effectiveSecret = secret ?? process.env.SAYNA_WEBHOOK_SECRET;\n\n if (!effectiveSecret) {\n throw new SaynaValidationError(\n \"Webhook secret is required. Provide it as a constructor parameter or set SAYNA_WEBHOOK_SECRET environment variable.\"\n );\n }\n\n const trimmedSecret = effectiveSecret.trim();\n\n if (trimmedSecret.length < MIN_SECRET_LENGTH) {\n throw new SaynaValidationError(\n `Webhook secret must be at least ${MIN_SECRET_LENGTH} characters long. ` +\n `Received ${trimmedSecret.length} characters. ` +\n `Generate a secure secret with: openssl rand -hex 32`\n );\n }\n\n this.secret = trimmedSecret;\n }\n\n /**\n * Verifies and parses an incoming SIP webhook from Sayna.\n *\n * This method performs the following security checks:\n * 1. Validates presence of required headers\n * 2. Verifies timestamp is within acceptable window (prevents replay attacks)\n * 3. Computes HMAC-SHA256 signature over canonical string\n * 4. Performs constant-time comparison to prevent timing attacks\n * 5. Parses and validates the webhook payload structure\n *\n * @param headers - HTTP request headers (case-insensitive)\n * @param body - Raw request body as string (not parsed JSON)\n *\n * @returns Parsed and validated webhook payload\n *\n * @throws {SaynaValidationError} If signature verification fails or payload is invalid\n *\n * @example\n * ```typescript\n * const receiver = new WebhookReceiver(\"your-secret\");\n *\n * // Express example\n * app.post('/webhook', express.json({ verify: (req, res, buf) => {\n * req.rawBody = buf.toString();\n * }}), (req, res) => {\n * const webhook = receiver.receive(req.headers, req.rawBody);\n * // webhook is now a validated WebhookSIPOutput object\n * });\n * ```\n */\n receive(\n headers: Record<string, string | string[] | undefined>,\n body: string\n ): WebhookSIPOutput {\n // Normalize headers to lowercase for case-insensitive lookup\n const normalizedHeaders = this.normalizeHeaders(headers);\n\n // Extract required headers\n const signature = this.getRequiredHeader(\n normalizedHeaders,\n \"x-sayna-signature\"\n );\n const timestamp = this.getRequiredHeader(\n normalizedHeaders,\n \"x-sayna-timestamp\"\n );\n const eventId = this.getRequiredHeader(\n normalizedHeaders,\n \"x-sayna-event-id\"\n );\n\n // Parse and validate signature format\n if (!signature.startsWith(\"v1=\")) {\n throw new SaynaValidationError(\n \"Invalid signature format. Expected 'v1=<hex>' but got: \" +\n signature.substring(0, 10) +\n \"...\"\n );\n }\n const signatureHex = signature.substring(3);\n\n // Validate signature is valid hex (64 chars for SHA256)\n if (!/^[0-9a-f]{64}$/i.test(signatureHex)) {\n throw new SaynaValidationError(\n \"Invalid signature: must be 64 hex characters (HMAC-SHA256)\"\n );\n }\n\n // Validate and check timestamp\n this.validateTimestamp(timestamp);\n\n // Build canonical string for signature verification\n const canonical = `v1:${timestamp}:${eventId}:${body}`;\n\n // Compute expected signature\n const hmac = createHmac(\"sha256\", this.secret);\n hmac.update(canonical, \"utf8\");\n const expectedSignature = hmac.digest(\"hex\");\n\n // Constant-time comparison to prevent timing attacks\n if (!this.constantTimeEqual(signatureHex, expectedSignature)) {\n throw new SaynaValidationError(\n \"Signature verification failed. The webhook may have been tampered with or the secret is incorrect.\"\n );\n }\n\n // Parse and validate the webhook payload\n return this.parseAndValidatePayload(body);\n }\n\n /**\n * Normalizes HTTP headers to lowercase for case-insensitive access.\n * Handles both single string values and arrays of strings.\n *\n * @internal\n */\n private normalizeHeaders(\n headers: Record<string, string | string[] | undefined>\n ): Record<string, string> {\n const normalized: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(headers)) {\n if (value !== undefined) {\n // Handle array values (take first element)\n const stringValue = Array.isArray(value) ? value[0] : value;\n if (stringValue) {\n normalized[key.toLowerCase()] = stringValue;\n }\n }\n }\n\n return normalized;\n }\n\n /**\n * Retrieves a required header value or throws a validation error.\n *\n * @internal\n */\n private getRequiredHeader(\n headers: Record<string, string>,\n name: string\n ): string {\n const value = headers[name.toLowerCase()];\n\n if (!value) {\n throw new SaynaValidationError(`Missing required header: ${name}`);\n }\n\n return value;\n }\n\n /**\n * Validates the timestamp is within the acceptable window.\n *\n * @internal\n */\n private validateTimestamp(timestampStr: string): void {\n // Parse timestamp\n const timestamp = Number(timestampStr);\n\n if (isNaN(timestamp)) {\n throw new SaynaValidationError(\n `Invalid timestamp format: expected Unix seconds but got '${timestampStr}'`\n );\n }\n\n // Check if timestamp is within acceptable range\n const now = Math.floor(Date.now() / 1000);\n const diff = Math.abs(now - timestamp);\n\n if (diff > TIMESTAMP_TOLERANCE_SECONDS) {\n throw new SaynaValidationError(\n `Timestamp outside replay protection window. ` +\n `Difference: ${diff} seconds (max allowed: ${TIMESTAMP_TOLERANCE_SECONDS}). ` +\n `This webhook may be a replay attack or there may be significant clock skew.`\n );\n }\n }\n\n /**\n * Performs constant-time string comparison to prevent timing attacks.\n *\n * @internal\n */\n private constantTimeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false;\n }\n\n const bufA = Buffer.from(a, \"utf8\");\n const bufB = Buffer.from(b, \"utf8\");\n\n return timingSafeEqual(bufA, bufB);\n }\n\n /**\n * Parses and validates the webhook payload structure.\n *\n * @internal\n */\n private parseAndValidatePayload(body: string): WebhookSIPOutput {\n let payload: unknown;\n\n // Parse JSON\n try {\n payload = JSON.parse(body);\n } catch (error) {\n throw new SaynaValidationError(\n `Invalid JSON payload: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n\n // Validate payload is an object\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n throw new SaynaValidationError(\"Webhook payload must be a JSON object\");\n }\n\n const data = payload as Record<string, unknown>;\n\n // Validate required fields\n this.validateParticipant(data.participant);\n this.validateRoom(data.room);\n this.validateStringField(data, \"from_phone_number\", \"from_phone_number\");\n this.validateStringField(data, \"to_phone_number\", \"to_phone_number\");\n this.validateStringField(data, \"room_prefix\", \"room_prefix\");\n this.validateStringField(data, \"sip_host\", \"sip_host\");\n\n // TypeScript type assertion is safe here because we've validated all fields\n // We use double assertion through unknown to satisfy strict type checking\n return data as unknown as WebhookSIPOutput;\n }\n\n /**\n * Validates the participant object structure.\n *\n * @internal\n */\n private validateParticipant(participant: unknown): void {\n if (\n !participant ||\n typeof participant !== \"object\" ||\n Array.isArray(participant)\n ) {\n throw new SaynaValidationError(\n \"Webhook payload missing required field 'participant' (must be an object)\"\n );\n }\n\n const p = participant as Record<string, unknown>;\n\n this.validateStringField(p, \"identity\", \"participant.identity\");\n this.validateStringField(p, \"sid\", \"participant.sid\");\n\n // name is optional, but if present must be a string\n if (p.name !== undefined && typeof p.name !== \"string\") {\n throw new SaynaValidationError(\n \"Field 'participant.name' must be a string if present\"\n );\n }\n }\n\n /**\n * Validates the room object structure.\n *\n * @internal\n */\n private validateRoom(room: unknown): void {\n if (!room || typeof room !== \"object\" || Array.isArray(room)) {\n throw new SaynaValidationError(\n \"Webhook payload missing required field 'room' (must be an object)\"\n );\n }\n\n const r = room as Record<string, unknown>;\n\n this.validateStringField(r, \"name\", \"room.name\");\n this.validateStringField(r, \"sid\", \"room.sid\");\n }\n\n /**\n * Validates a required string field.\n *\n * @internal\n */\n private validateStringField(\n obj: Record<string, unknown>,\n field: string,\n displayName: string\n ): void {\n const value = obj[field];\n\n if (typeof value !== \"string\" || value.length === 0) {\n throw new SaynaValidationError(\n `Webhook payload missing required field '${displayName}' (must be a non-empty string)`\n );\n }\n }\n}\n",
|
|
8
|
+
"import { SaynaClient } from \"./sayna-client\";\nimport type { STTConfig, TTSConfig, LiveKitConfig } from \"./types\";\n\nexport * from \"./sayna-client\";\nexport * from \"./types\";\nexport * from \"./errors\";\nexport * from \"./webhook-receiver\";\n\n/**\n * Creates and connects a new SaynaClient instance.\n *\n * This is the recommended way to create a Sayna client. It handles both\n * instantiation and connection, returning a ready-to-use client.\n *\n * @param url - The Sayna server URL (e.g., \"https://api.sayna.ai\")\n * @param sttConfig - Speech-to-text configuration\n * @param ttsConfig - Text-to-speech configuration\n * @param livekitConfig - Optional LiveKit room configuration\n * @param withoutAudio - If true, disables audio streaming (default: false)\n * @param apiKey - Optional API key used to authorize HTTP and WebSocket calls (defaults to SAYNA_API_KEY env)\n *\n * @returns Promise that resolves to a connected SaynaClient\n *\n * @throws {SaynaValidationError} If parameters are invalid\n * @throws {SaynaConnectionError} If connection fails\n * @throws {SaynaServerError} If server returns an error during setup\n *\n * @example\n * ```typescript\n * import { saynaConnect } from \"@sayna/node-sdk\";\n *\n * const client = await saynaConnect(\n * \"https://api.sayna.ai\",\n * {\n * provider: \"deepgram\",\n * language: \"en-US\",\n * sample_rate: 16000,\n * channels: 1,\n * punctuation: true,\n * encoding: \"linear16\",\n * model: \"nova-2\"\n * },\n * {\n * provider: \"elevenlabs\",\n * voice_id: \"21m00Tcm4TlvDq8ikWAM\",\n * speaking_rate: 1.0,\n * audio_format: \"pcm\",\n * sample_rate: 16000,\n * connection_timeout: 5000,\n * request_timeout: 10000,\n * model: \"eleven_turbo_v2\",\n * pronunciations: []\n * }\n * );\n *\n * // Register event handlers\n * client.registerOnSttResult((result) => {\n * console.log(\"Transcription:\", result.transcript);\n * });\n *\n * // Send text to be spoken\n * await client.speak(\"Hello, world!\");\n *\n * // Clean up\n * await client.disconnect();\n * ```\n */\nexport async function saynaConnect(\n url: string,\n sttConfig: STTConfig,\n ttsConfig: TTSConfig,\n livekitConfig?: LiveKitConfig,\n withoutAudio: boolean = false,\n apiKey?: string\n): Promise<SaynaClient> {\n const client = new SaynaClient(\n url,\n sttConfig,\n ttsConfig,\n livekitConfig,\n withoutAudio,\n apiKey\n );\n await client.connect();\n return client;\n}\n"
|
|
8
9
|
],
|
|
9
|
-
"mappings": "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",
|
|
10
|
-
"debugId": "
|
|
10
|
+
"mappings": "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",
|
|
11
|
+
"debugId": "61BF4B0E0558D09664756E2164756E21",
|
|
11
12
|
"names": []
|
|
12
13
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM;CAK5B;AAED;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,UAAU;gBACxC,OAAO,GAAE,MAA2C;CAKjE;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,UAAU;gBAE9C,OAAO,GAAE,MAA0F;CAMtG;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,UAAU;IAClD,SAAyB,KAAK,CAAC,EAAE,OAAO,CAAC;gBAE7B,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAM7C;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,UAAU;gBACtC,OAAO,EAAE,MAAM;CAK5B;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,UAAU;gBAClC,OAAO,EAAE,MAAM;CAK5B"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { SaynaClient } from "./sayna-client";
|
|
2
|
-
import { STTConfig, TTSConfig, LiveKitConfig } from "./types";
|
|
2
|
+
import type { STTConfig, TTSConfig, LiveKitConfig } from "./types";
|
|
3
3
|
export * from "./sayna-client";
|
|
4
4
|
export * from "./types";
|
|
5
5
|
export * from "./errors";
|
|
6
|
+
export * from "./webhook-receiver";
|
|
6
7
|
/**
|
|
7
8
|
* Creates and connects a new SaynaClient instance.
|
|
8
9
|
*
|
|
@@ -14,6 +15,7 @@ export * from "./errors";
|
|
|
14
15
|
* @param ttsConfig - Text-to-speech configuration
|
|
15
16
|
* @param livekitConfig - Optional LiveKit room configuration
|
|
16
17
|
* @param withoutAudio - If true, disables audio streaming (default: false)
|
|
18
|
+
* @param apiKey - Optional API key used to authorize HTTP and WebSocket calls (defaults to SAYNA_API_KEY env)
|
|
17
19
|
*
|
|
18
20
|
* @returns Promise that resolves to a connected SaynaClient
|
|
19
21
|
*
|
|
@@ -61,5 +63,5 @@ export * from "./errors";
|
|
|
61
63
|
* await client.disconnect();
|
|
62
64
|
* ```
|
|
63
65
|
*/
|
|
64
|
-
export declare function saynaConnect(url: string, sttConfig: STTConfig, ttsConfig: TTSConfig, livekitConfig?: LiveKitConfig, withoutAudio?: boolean): Promise<SaynaClient>;
|
|
66
|
+
export declare function saynaConnect(url: string, sttConfig: STTConfig, ttsConfig: TTSConfig, livekitConfig?: LiveKitConfig, withoutAudio?: boolean, apiKey?: string): Promise<SaynaClient>;
|
|
65
67
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAEnE,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,oBAAoB,CAAC;AAEnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,EACpB,aAAa,CAAC,EAAE,aAAa,EAC7B,YAAY,GAAE,OAAe,EAC7B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,WAAW,CAAC,CAWtB"}
|
|
@@ -43,10 +43,11 @@ export type TTSPlaybackCompleteHandler = (timestamp: number) => void | Promise<v
|
|
|
43
43
|
*/
|
|
44
44
|
export declare class SaynaClient {
|
|
45
45
|
private url;
|
|
46
|
-
private sttConfig
|
|
47
|
-
private ttsConfig
|
|
46
|
+
private sttConfig?;
|
|
47
|
+
private ttsConfig?;
|
|
48
48
|
private livekitConfig?;
|
|
49
49
|
private withoutAudio;
|
|
50
|
+
private apiKey?;
|
|
50
51
|
private websocket?;
|
|
51
52
|
private isConnected;
|
|
52
53
|
private isReady;
|
|
@@ -66,14 +67,15 @@ export declare class SaynaClient {
|
|
|
66
67
|
* Creates a new SaynaClient instance.
|
|
67
68
|
*
|
|
68
69
|
* @param url - The Sayna server URL (e.g., "https://api.sayna.ai")
|
|
69
|
-
* @param sttConfig - Speech-to-text configuration
|
|
70
|
-
* @param ttsConfig - Text-to-speech configuration
|
|
70
|
+
* @param sttConfig - Speech-to-text configuration (required when withoutAudio=false)
|
|
71
|
+
* @param ttsConfig - Text-to-speech configuration (required when withoutAudio=false)
|
|
71
72
|
* @param livekitConfig - Optional LiveKit room configuration
|
|
72
73
|
* @param withoutAudio - If true, disables audio streaming (default: false)
|
|
74
|
+
* @param apiKey - Optional API key used to authorize HTTP and WebSocket calls (defaults to SAYNA_API_KEY env)
|
|
73
75
|
*
|
|
74
|
-
* @throws {SaynaValidationError} If URL or
|
|
76
|
+
* @throws {SaynaValidationError} If URL is invalid or if audio configs are missing when audio is enabled
|
|
75
77
|
*/
|
|
76
|
-
constructor(url: string, sttConfig
|
|
78
|
+
constructor(url: string, sttConfig?: STTConfig, ttsConfig?: TTSConfig, livekitConfig?: LiveKitConfig, withoutAudio?: boolean, apiKey?: string);
|
|
77
79
|
/**
|
|
78
80
|
* Establishes connection to the Sayna WebSocket server.
|
|
79
81
|
*
|
|
@@ -112,7 +114,7 @@ export declare class SaynaClient {
|
|
|
112
114
|
/**
|
|
113
115
|
* Disconnects from the Sayna WebSocket server and cleans up resources.
|
|
114
116
|
*/
|
|
115
|
-
disconnect():
|
|
117
|
+
disconnect(): void;
|
|
116
118
|
/**
|
|
117
119
|
* Sends audio data to the server for speech recognition.
|
|
118
120
|
*
|
|
@@ -120,7 +122,7 @@ export declare class SaynaClient {
|
|
|
120
122
|
* @throws {SaynaNotConnectedError} If not connected
|
|
121
123
|
* @throws {SaynaNotReadyError} If connection is not ready
|
|
122
124
|
*/
|
|
123
|
-
onAudioInput(audioData: ArrayBuffer):
|
|
125
|
+
onAudioInput(audioData: ArrayBuffer): void;
|
|
124
126
|
/**
|
|
125
127
|
* Registers a callback for speech-to-text results.
|
|
126
128
|
*
|
|
@@ -167,14 +169,14 @@ export declare class SaynaClient {
|
|
|
167
169
|
* @throws {SaynaNotReadyError} If connection is not ready
|
|
168
170
|
* @throws {SaynaValidationError} If text is not a string
|
|
169
171
|
*/
|
|
170
|
-
speak(text: string, flush?: boolean, allowInterruption?: boolean):
|
|
172
|
+
speak(text: string, flush?: boolean, allowInterruption?: boolean): void;
|
|
171
173
|
/**
|
|
172
174
|
* Clears the text-to-speech queue.
|
|
173
175
|
*
|
|
174
176
|
* @throws {SaynaNotConnectedError} If not connected
|
|
175
177
|
* @throws {SaynaNotReadyError} If connection is not ready
|
|
176
178
|
*/
|
|
177
|
-
clear():
|
|
179
|
+
clear(): void;
|
|
178
180
|
/**
|
|
179
181
|
* Flushes the TTS queue by sending an empty speak command.
|
|
180
182
|
*
|
|
@@ -182,7 +184,7 @@ export declare class SaynaClient {
|
|
|
182
184
|
* @throws {SaynaNotConnectedError} If not connected
|
|
183
185
|
* @throws {SaynaNotReadyError} If connection is not ready
|
|
184
186
|
*/
|
|
185
|
-
ttsFlush(allowInterruption?: boolean):
|
|
187
|
+
ttsFlush(allowInterruption?: boolean): void;
|
|
186
188
|
/**
|
|
187
189
|
* Sends a message to the Sayna session.
|
|
188
190
|
*
|
|
@@ -194,7 +196,7 @@ export declare class SaynaClient {
|
|
|
194
196
|
* @throws {SaynaNotReadyError} If connection is not ready
|
|
195
197
|
* @throws {SaynaValidationError} If parameters are invalid
|
|
196
198
|
*/
|
|
197
|
-
sendMessage(message: string, role: string, topic?: string, debug?: Record<string, unknown>):
|
|
199
|
+
sendMessage(message: string, role: string, topic?: string, debug?: Record<string, unknown>): void;
|
|
198
200
|
/**
|
|
199
201
|
* Performs a health check on the Sayna server.
|
|
200
202
|
*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sayna-client.d.ts","sourceRoot":"","sources":["../../src/sayna-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -74,10 +74,10 @@ export interface ConfigMessage {
|
|
|
74
74
|
type: "config";
|
|
75
75
|
/** Whether audio streaming is enabled */
|
|
76
76
|
audio?: boolean;
|
|
77
|
-
/** Speech-to-text configuration */
|
|
78
|
-
stt_config
|
|
79
|
-
/** Text-to-speech configuration */
|
|
80
|
-
tts_config
|
|
77
|
+
/** Speech-to-text configuration (required when audio=true) */
|
|
78
|
+
stt_config?: STTConfig;
|
|
79
|
+
/** Text-to-speech configuration (required when audio=true) */
|
|
80
|
+
tts_config?: TTSConfig;
|
|
81
81
|
/** Optional LiveKit room configuration */
|
|
82
82
|
livekit?: LiveKitConfig;
|
|
83
83
|
}
|
|
@@ -252,4 +252,47 @@ export interface LiveKitTokenResponse {
|
|
|
252
252
|
/** WebSocket endpoint for the LiveKit server */
|
|
253
253
|
livekit_url: string;
|
|
254
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Participant information from a SIP webhook event.
|
|
257
|
+
*/
|
|
258
|
+
export interface WebhookSIPParticipant {
|
|
259
|
+
/** Display name of the SIP participant (may be undefined if not provided) */
|
|
260
|
+
name?: string;
|
|
261
|
+
/** Unique identity assigned to the participant */
|
|
262
|
+
identity: string;
|
|
263
|
+
/** Participant session ID from LiveKit */
|
|
264
|
+
sid: string;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Room information from a SIP webhook event.
|
|
268
|
+
*/
|
|
269
|
+
export interface WebhookSIPRoom {
|
|
270
|
+
/** Name of the LiveKit room */
|
|
271
|
+
name: string;
|
|
272
|
+
/** Room session ID from LiveKit */
|
|
273
|
+
sid: string;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* SIP webhook payload sent from Sayna service.
|
|
277
|
+
*
|
|
278
|
+
* This represents a cryptographically signed webhook event forwarded by Sayna
|
|
279
|
+
* when a SIP participant joins a LiveKit room. Use the WebhookReceiver class
|
|
280
|
+
* to verify the signature and parse this payload securely.
|
|
281
|
+
*
|
|
282
|
+
* @see WebhookReceiver
|
|
283
|
+
*/
|
|
284
|
+
export interface WebhookSIPOutput {
|
|
285
|
+
/** SIP participant information */
|
|
286
|
+
participant: WebhookSIPParticipant;
|
|
287
|
+
/** LiveKit room information */
|
|
288
|
+
room: WebhookSIPRoom;
|
|
289
|
+
/** Caller's phone number (E.164 format, e.g., "+15559876543") */
|
|
290
|
+
from_phone_number: string;
|
|
291
|
+
/** Called phone number (E.164 format, e.g., "+15551234567") */
|
|
292
|
+
to_phone_number: string;
|
|
293
|
+
/** Room name prefix configured in Sayna (e.g., "sip-") */
|
|
294
|
+
room_prefix: string;
|
|
295
|
+
/** SIP domain extracted from the To header (e.g., "example.com") */
|
|
296
|
+
sip_host: string;
|
|
297
|
+
}
|
|
255
298
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { WebhookSIPOutput } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Receives and verifies cryptographically signed webhooks from Sayna SIP service.
|
|
4
|
+
*
|
|
5
|
+
* This class handles the secure verification of webhook signatures using HMAC-SHA256,
|
|
6
|
+
* validates timestamp freshness to prevent replay attacks, and parses the webhook
|
|
7
|
+
* payload into a strongly-typed WebhookSIPOutput object.
|
|
8
|
+
*
|
|
9
|
+
* ## Security Features
|
|
10
|
+
*
|
|
11
|
+
* - **HMAC-SHA256 Signature Verification**: Ensures webhook authenticity
|
|
12
|
+
* - **Constant-Time Comparison**: Prevents timing attack vulnerabilities
|
|
13
|
+
* - **Replay Protection**: 5-minute timestamp window prevents replay attacks
|
|
14
|
+
* - **Strict Validation**: Comprehensive checks on all required fields
|
|
15
|
+
*
|
|
16
|
+
* ## Usage
|
|
17
|
+
*
|
|
18
|
+
* ### Basic Example
|
|
19
|
+
*
|
|
20
|
+
* ```typescript
|
|
21
|
+
* import { WebhookReceiver } from "@sayna-ai/node-sdk";
|
|
22
|
+
*
|
|
23
|
+
* // Initialize with secret (or uses SAYNA_WEBHOOK_SECRET env variable)
|
|
24
|
+
* const receiver = new WebhookReceiver("your-secret-key-min-16-chars");
|
|
25
|
+
*
|
|
26
|
+
* // In your Express route handler
|
|
27
|
+
* app.post('/webhook', express.json({ verify: (req, res, buf) => {
|
|
28
|
+
* req.rawBody = buf.toString('utf8');
|
|
29
|
+
* }}), (req, res) => {
|
|
30
|
+
* try {
|
|
31
|
+
* const webhook = receiver.receive(req.headers, req.rawBody);
|
|
32
|
+
*
|
|
33
|
+
* console.log('Valid webhook received:');
|
|
34
|
+
* console.log(' From:', webhook.from_phone_number);
|
|
35
|
+
* console.log(' To:', webhook.to_phone_number);
|
|
36
|
+
* console.log(' Room:', webhook.room.name);
|
|
37
|
+
* console.log(' SIP Host:', webhook.sip_host);
|
|
38
|
+
* console.log(' Participant:', webhook.participant.identity);
|
|
39
|
+
*
|
|
40
|
+
* res.status(200).json({ received: true });
|
|
41
|
+
* } catch (error) {
|
|
42
|
+
* console.error('Webhook verification failed:', error.message);
|
|
43
|
+
* res.status(401).json({ error: 'Invalid signature' });
|
|
44
|
+
* }
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* ### With Environment Variable
|
|
49
|
+
*
|
|
50
|
+
* ```typescript
|
|
51
|
+
* // Set environment variable
|
|
52
|
+
* process.env.SAYNA_WEBHOOK_SECRET = "your-secret-key";
|
|
53
|
+
*
|
|
54
|
+
* // Receiver automatically uses env variable
|
|
55
|
+
* const receiver = new WebhookReceiver();
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* ### Fastify Example
|
|
59
|
+
*
|
|
60
|
+
* ```typescript
|
|
61
|
+
* import Fastify from 'fastify';
|
|
62
|
+
* import { WebhookReceiver } from "@sayna-ai/node-sdk";
|
|
63
|
+
*
|
|
64
|
+
* const fastify = Fastify();
|
|
65
|
+
* const receiver = new WebhookReceiver();
|
|
66
|
+
*
|
|
67
|
+
* fastify.post('/webhook', {
|
|
68
|
+
* config: {
|
|
69
|
+
* rawBody: true
|
|
70
|
+
* }
|
|
71
|
+
* }, async (request, reply) => {
|
|
72
|
+
* try {
|
|
73
|
+
* const webhook = receiver.receive(
|
|
74
|
+
* request.headers,
|
|
75
|
+
* request.rawBody
|
|
76
|
+
* );
|
|
77
|
+
*
|
|
78
|
+
* // Process webhook...
|
|
79
|
+
*
|
|
80
|
+
* return { received: true };
|
|
81
|
+
* } catch (error) {
|
|
82
|
+
* reply.code(401);
|
|
83
|
+
* return { error: error.message };
|
|
84
|
+
* }
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* ## Important Notes
|
|
89
|
+
*
|
|
90
|
+
* - **Raw Body Required**: You MUST pass the raw request body string, not the parsed JSON object.
|
|
91
|
+
* The signature is computed over the exact bytes received, so any formatting changes will
|
|
92
|
+
* cause verification to fail.
|
|
93
|
+
*
|
|
94
|
+
* - **Case-Insensitive Headers**: Header names are case-insensitive in HTTP. This class handles
|
|
95
|
+
* both `X-Sayna-Signature` and `x-sayna-signature` correctly.
|
|
96
|
+
*
|
|
97
|
+
* - **Secret Security**: Never commit secrets to version control. Use environment variables
|
|
98
|
+
* or a secret management system.
|
|
99
|
+
*
|
|
100
|
+
* @see WebhookSIPOutput
|
|
101
|
+
*/
|
|
102
|
+
export declare class WebhookReceiver {
|
|
103
|
+
private readonly secret;
|
|
104
|
+
/**
|
|
105
|
+
* Creates a new webhook receiver with the specified signing secret.
|
|
106
|
+
*
|
|
107
|
+
* @param secret - HMAC signing secret (min 16 chars, 32+ recommended).
|
|
108
|
+
* If not provided, uses SAYNA_WEBHOOK_SECRET environment variable.
|
|
109
|
+
*
|
|
110
|
+
* @throws {SaynaValidationError} If secret is missing or too short
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```typescript
|
|
114
|
+
* // Explicit secret
|
|
115
|
+
* const receiver = new WebhookReceiver("my-secret-key-at-least-16-chars");
|
|
116
|
+
*
|
|
117
|
+
* // From environment variable
|
|
118
|
+
* const receiver = new WebhookReceiver();
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
constructor(secret?: string);
|
|
122
|
+
/**
|
|
123
|
+
* Verifies and parses an incoming SIP webhook from Sayna.
|
|
124
|
+
*
|
|
125
|
+
* This method performs the following security checks:
|
|
126
|
+
* 1. Validates presence of required headers
|
|
127
|
+
* 2. Verifies timestamp is within acceptable window (prevents replay attacks)
|
|
128
|
+
* 3. Computes HMAC-SHA256 signature over canonical string
|
|
129
|
+
* 4. Performs constant-time comparison to prevent timing attacks
|
|
130
|
+
* 5. Parses and validates the webhook payload structure
|
|
131
|
+
*
|
|
132
|
+
* @param headers - HTTP request headers (case-insensitive)
|
|
133
|
+
* @param body - Raw request body as string (not parsed JSON)
|
|
134
|
+
*
|
|
135
|
+
* @returns Parsed and validated webhook payload
|
|
136
|
+
*
|
|
137
|
+
* @throws {SaynaValidationError} If signature verification fails or payload is invalid
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```typescript
|
|
141
|
+
* const receiver = new WebhookReceiver("your-secret");
|
|
142
|
+
*
|
|
143
|
+
* // Express example
|
|
144
|
+
* app.post('/webhook', express.json({ verify: (req, res, buf) => {
|
|
145
|
+
* req.rawBody = buf.toString();
|
|
146
|
+
* }}), (req, res) => {
|
|
147
|
+
* const webhook = receiver.receive(req.headers, req.rawBody);
|
|
148
|
+
* // webhook is now a validated WebhookSIPOutput object
|
|
149
|
+
* });
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
receive(headers: Record<string, string | string[] | undefined>, body: string): WebhookSIPOutput;
|
|
153
|
+
/**
|
|
154
|
+
* Normalizes HTTP headers to lowercase for case-insensitive access.
|
|
155
|
+
* Handles both single string values and arrays of strings.
|
|
156
|
+
*
|
|
157
|
+
* @internal
|
|
158
|
+
*/
|
|
159
|
+
private normalizeHeaders;
|
|
160
|
+
/**
|
|
161
|
+
* Retrieves a required header value or throws a validation error.
|
|
162
|
+
*
|
|
163
|
+
* @internal
|
|
164
|
+
*/
|
|
165
|
+
private getRequiredHeader;
|
|
166
|
+
/**
|
|
167
|
+
* Validates the timestamp is within the acceptable window.
|
|
168
|
+
*
|
|
169
|
+
* @internal
|
|
170
|
+
*/
|
|
171
|
+
private validateTimestamp;
|
|
172
|
+
/**
|
|
173
|
+
* Performs constant-time string comparison to prevent timing attacks.
|
|
174
|
+
*
|
|
175
|
+
* @internal
|
|
176
|
+
*/
|
|
177
|
+
private constantTimeEqual;
|
|
178
|
+
/**
|
|
179
|
+
* Parses and validates the webhook payload structure.
|
|
180
|
+
*
|
|
181
|
+
* @internal
|
|
182
|
+
*/
|
|
183
|
+
private parseAndValidatePayload;
|
|
184
|
+
/**
|
|
185
|
+
* Validates the participant object structure.
|
|
186
|
+
*
|
|
187
|
+
* @internal
|
|
188
|
+
*/
|
|
189
|
+
private validateParticipant;
|
|
190
|
+
/**
|
|
191
|
+
* Validates the room object structure.
|
|
192
|
+
*
|
|
193
|
+
* @internal
|
|
194
|
+
*/
|
|
195
|
+
private validateRoom;
|
|
196
|
+
/**
|
|
197
|
+
* Validates a required string field.
|
|
198
|
+
*
|
|
199
|
+
* @internal
|
|
200
|
+
*/
|
|
201
|
+
private validateStringField;
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=webhook-receiver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-receiver.d.ts","sourceRoot":"","sources":["../../src/webhook-receiver.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAehD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmGG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC;;;;;;;;;;;;;;;;OAgBG;gBACS,MAAM,CAAC,EAAE,MAAM;IAsB3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,OAAO,CACL,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,EACtD,IAAI,EAAE,MAAM,GACX,gBAAgB;IAyDnB;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAkBxB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAazB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAuBzB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;;;OAIG;IACH,OAAO,CAAC,uBAAuB;IAgC/B;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAwB3B;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAapB;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;CAa5B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.test.d.ts","sourceRoot":"","sources":["../../tests/errors.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sayna-client.test.d.ts","sourceRoot":"","sources":["../../tests/sayna-client.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.test.d.ts","sourceRoot":"","sources":["../../tests/types.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-receiver.test.d.ts","sourceRoot":"","sources":["../../tests/webhook-receiver.test.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sayna-ai/node-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Node.js SDK for Sayna.ai server-side WebSocket connections",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "bun run build.ts",
|
|
22
|
+
"test": "bun test",
|
|
23
|
+
"test:watch": "bun test --watch",
|
|
22
24
|
"typecheck": "tsc --noEmit",
|
|
23
25
|
"lint": "eslint .",
|
|
24
26
|
"lint:fix": "eslint . --fix",
|
|
@@ -33,16 +35,16 @@
|
|
|
33
35
|
"sdk",
|
|
34
36
|
"server"
|
|
35
37
|
],
|
|
36
|
-
"author": "",
|
|
38
|
+
"author": "@sayna-ai",
|
|
37
39
|
"license": "MIT",
|
|
38
40
|
"repository": {
|
|
39
41
|
"type": "git",
|
|
40
|
-
"url": ""
|
|
42
|
+
"url": "https://github.com/sayna/saysdk"
|
|
41
43
|
},
|
|
42
44
|
"bugs": {
|
|
43
|
-
"url": ""
|
|
45
|
+
"url": "https://github.com/sayna/saysdk/issues"
|
|
44
46
|
},
|
|
45
|
-
"homepage": "",
|
|
47
|
+
"homepage": "https://sayna.ai",
|
|
46
48
|
"engines": {
|
|
47
49
|
"node": ">=18.0.0"
|
|
48
50
|
},
|
package/dist/errors.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM;CAK5B;AAED;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,UAAU;gBACxC,OAAO,GAAE,MAA2C;CAKjE;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,UAAU;gBAE9C,OAAO,GAAE,MAA0F;CAMtG;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,UAAU;IAClD,SAAyB,KAAK,CAAC,EAAE,OAAO,CAAC;gBAE7B,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAM7C;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,UAAU;gBACtC,OAAO,EAAE,MAAM;CAK5B;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,UAAU;gBAClC,OAAO,EAAE,MAAM;CAK5B"}
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE9D,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AAEzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,EACpB,aAAa,CAAC,EAAE,aAAa,EAC7B,YAAY,GAAE,OAAe,GAC5B,OAAO,CAAC,WAAW,CAAC,CAUtB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sayna-client.d.ts","sourceRoot":"","sources":["../src/sayna-client.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
File without changes
|