ttp-agent-sdk 2.34.14 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.html CHANGED
@@ -23,7 +23,7 @@
23
23
  <img src="https://talktopc.com/logo192.png" alt="TTP Logo" style="width: 40px; height: 40px; border-radius: 8px;">
24
24
  <div>
25
25
  <h1 style="margin: 0; font-size: 1.4rem;">TTP Agent SDK</h1>
26
- <p class="version" style="margin: 0;">v2.3.18</p>
26
+ <p class="version" style="margin: 0;">v2.34.9</p>
27
27
  </div>
28
28
  </div>
29
29
  </div>
@@ -157,8 +157,8 @@
157
157
  <div class="feature-compact-item">
158
158
  <span class="feature-compact-icon">🔒</span>
159
159
  <div>
160
- <strong>Secure Authentication</strong>
161
- <p>Backend-to-backend signed link authentication with JWT and TTL-based expiration</p>
160
+ <strong>Simple Authentication</strong>
161
+ <p>Direct connection using agentId and appId with domain whitelist access control</p>
162
162
  </div>
163
163
  </div>
164
164
  <div class="feature-compact-item">
@@ -228,38 +228,14 @@ const sdk = new window.TTPAgentSDK.VoiceSDK(config);</code></pre>
228
228
 
229
229
  <div class="step-card">
230
230
  <div class="step-number">1</div>
231
- <div class="step-content">
232
- <h3>Get a Signed URL</h3>
233
- <div class="warning-box">
234
- <strong>🔒 Backend-to-Backend:</strong> Your frontend requests from YOUR backend, which then communicates with TTP backend. Never expose your API key to the frontend!
235
- </div>
236
- <p><strong>Frontend → Your Backend:</strong></p>
237
- <pre><code>const response = await fetch('/api/get-voice-session', {
238
- method: 'POST',
239
- headers: {
240
- 'Content-Type': 'application/json'
241
- },
242
- body: JSON.stringify({
243
- agentId: 'agent_123'
244
- })
245
- });
246
-
247
- const { signedUrl } = await response.json();</code></pre>
248
- <p><strong>Your Backend → TTP Backend:</strong> (See Authentication section for details)</p>
249
- </div>
250
- </div>
251
-
252
- <div class="step-card">
253
- <div class="step-number">2</div>
254
231
  <div class="step-content">
255
232
  <h3>Initialize the SDK</h3>
256
- <p>Create a VoiceSDK instance with the signed URL:</p>
233
+ <p>Create a VoiceSDK instance with your agent ID and app ID:</p>
257
234
  <pre><code>import { VoiceSDK } from 'ttp-agent-sdk';
258
235
 
259
236
  const voiceSDK = new VoiceSDK({
260
- signedUrl: signedUrl, // signedUrl from step 1
261
- appId: 'your_app_id', // Your application ID
262
237
  agentId: 'agent_123', // The AI agent to connect to
238
+ appId: 'your_app_id', // Your application ID
263
239
 
264
240
  // Optional: Configure audio formats (v2 protocol)
265
241
  outputContainer: 'raw', // 'raw' or 'wav'
@@ -287,7 +263,7 @@ voiceSDK.on('message', (msg) => {
287
263
  </div>
288
264
 
289
265
  <div class="step-card">
290
- <div class="step-number">3</div>
266
+ <div class="step-number">2</div>
291
267
  <div class="step-content">
292
268
  <h3>Connect & Start Recording</h3>
293
269
  <p>Connect to the agent and start capturing audio:</p>
@@ -306,203 +282,26 @@ await voiceSDK.stopRecording();</code></pre>
306
282
  <!-- Authentication -->
307
283
  <section id="authentication" class="doc-section">
308
284
  <h1>Authentication</h1>
309
- <p>TTP Agent SDK uses signed WebSocket URLs for secure authentication.</p>
285
+ <p>The SDK connects directly using <code>agentId</code> and <code>appId</code>. No server-side authentication step is needed. Access control is managed via domain whitelist in your agent's admin panel.</p>
310
286
 
287
+ <h2>How It Works</h2>
311
288
  <div class="info-box">
312
- <strong>🔒 Security First:</strong> All connections require signed URLs obtained from your backend, which authenticates with the TTP backend using an API key.
313
- </div>
314
-
315
- <h2>Authentication Flow</h2>
316
- <div class="warning-box">
317
- <strong>🔐 Critical Security Note:</strong> The signed URL generation happens <strong>Backend-to-Backend</strong>. Your frontend NEVER directly contacts TTP backend. This protects your API key!
318
- </div>
319
- <div class="flow-diagram">
320
- <div class="flow-step">
321
- <div class="flow-box" style="background: linear-gradient(135deg, #e0f2fe 0%, #bfdbfe 100%);">
322
- <strong>1. 🖥️ Your Frontend</strong>
323
- <p>User clicks "Start Voice Chat"</p>
324
- </div>
325
- <div class="flow-arrow">↓</div>
326
- <div class="flow-box" style="background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);">
327
- <strong>2. 🔧 Your Backend</strong>
328
- <p>Receives: POST /api/get-voice-session</p>
329
- </div>
330
- <div class="flow-arrow">↓ 🔒 Backend-to-Backend (API Key)</div>
331
- <div class="flow-box" style="background: linear-gradient(135deg, #e9d5ff 0%, #d8b4fe 100%);">
332
- <strong>3. 🏢 TTP Backend</strong>
333
- <p>Validates API key & generates signed JWT</p>
334
- </div>
335
- <div class="flow-arrow">↑ Returns signed URL</div>
336
- <div class="flow-box" style="background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);">
337
- <strong>4. 🔧 Your Backend</strong>
338
- <p>Forwards signed URL to frontend</p>
339
- </div>
340
- <div class="flow-arrow">↓</div>
341
- <div class="flow-box" style="background: linear-gradient(135deg, #e0f2fe 0%, #bfdbfe 100%);">
342
- <strong>5. 🖥️ Your Frontend</strong>
343
- <p>Uses SDK with signed URL (WebSocket)</p>
344
- </div>
345
- </div>
346
- </div>
347
-
348
- <h2>Backend Implementation (Backend-to-Backend)</h2>
349
- <p><strong>Your backend acts as a secure proxy between your frontend and TTP backend:</strong></p>
350
-
351
- <div class="info-box">
352
- <strong>💡 Architecture:</strong> Frontend → Your Backend → TTP Backend → Your Backend → Frontend
289
+ <strong>💡 Simple Setup:</strong> Just provide your <code>agentId</code> and <code>appId</code> in the SDK configuration. The SDK connects directly to the TTP backend via WebSocket.
353
290
  </div>
354
291
 
355
- <div class="success-box">
356
- <strong>⏱️ Configurable TTL:</strong> You can specify <code>expirationMs</code> (in milliseconds) to control how long the signed URL is valid. Default is 1 hour (3600000ms) if not specified.
357
- <br><br>
358
- <strong>Examples:</strong>
359
- <ul style="margin-top: 10px; margin-left: 20px;">
360
- <li>5 minutes: <code>300000</code></li>
361
- <li>1 hour (default): <code>3600000</code></li>
362
- <li>24 hours: <code>86400000</code></li>
363
- </ul>
364
- </div>
365
-
366
- <pre><code>// Example: Node.js/Express
367
- // This endpoint is called by YOUR FRONTEND
368
- app.post('/api/get-voice-session', async (req, res) => {
369
- const { agentId } = req.body;
370
-
371
- // 1️⃣ Authenticate YOUR user (your auth logic)
372
- if (!req.user) {
373
- return res.status(401).json({ error: 'Unauthorized' });
374
- }
375
-
376
- // 2️⃣ Backend-to-Backend: Call TTP Backend with YOUR API Key
377
- // ⚠️ This happens SERVER-SIDE only - API key never exposed to frontend
378
- const response = await fetch('https://backend.talktopc.com/api/public/agents/signed-url', {
379
- method: 'POST',
380
- headers: {
381
- 'Content-Type': 'application/json',
382
- 'Authorization': `Bearer ${process.env.TTP_API_KEY}` // 🔒 Secret - never in frontend!
383
- },
384
- body: JSON.stringify({
385
- agentId: agentId,
386
- appId: process.env.TTP_APP_ID,
387
- expirationMs: 3600000, // Optional: Token TTL in milliseconds (default: 1 hour)
388
- allowOverride: true // Optional: Enable agent settings override (default: false)
389
- })
390
- });
391
-
392
- const { signedLink } = await response.json();
393
-
394
- // 3️⃣ Return signed URL to YOUR frontend
395
- res.json({ signedUrl: signedLink });
292
+ <pre><code>const voiceSDK = new VoiceSDK({
293
+ agentId: 'agent_123', // Your AI agent ID
294
+ appId: 'your_app_id' // Your application ID
396
295
  });</code></pre>
397
296
 
398
- <div class="warning-box">
399
- <strong>⚠️ Never expose your API key:</strong> The API key should only be used on your backend server, never in frontend code.
297
+ <h2>Domain Whitelist</h2>
298
+ <p>To control which websites can use your agent, configure a domain whitelist in your agent's admin panel. Only requests originating from whitelisted domains will be accepted.</p>
299
+
300
+ <div class="info-box">
301
+ <strong>💡 Tip:</strong> During development, you can add <code>localhost</code> to the domain whitelist. Remove it before deploying to production.
400
302
  </div>
401
303
 
402
- <h2>Response Format</h2>
403
- <p>The signed URL endpoint returns the following response:</p>
404
- <pre><code>{
405
- "signedLink": "wss://speech.talktopc.com/ws/conv?signed_token=eyJ...",
406
- "agentId": "agent_123",
407
- "userId": "user_789",
408
- "appId": "your_app_id",
409
- "expiresAt": "2025-11-11T21:00:00.000+00:00",
410
- "expiresIn": 3600000,
411
- "generatedAt": "2025-11-11T20:00:00.000+00:00",
412
- "availableCredits": 150.5,
413
- "authenticationStatus": "SUCCESS"
414
- }</code></pre>
415
-
416
- <table class="properties-table">
417
- <thead>
418
- <tr>
419
- <th>Property</th>
420
- <th>Type</th>
421
- <th>Description</th>
422
- </tr>
423
- </thead>
424
- <tbody>
425
- <tr>
426
- <td><code>signedLink</code></td>
427
- <td>string</td>
428
- <td>The WebSocket URL with signed JWT token</td>
429
- </tr>
430
- <tr>
431
- <td><code>agentId</code></td>
432
- <td>string</td>
433
- <td>The AI agent identifier</td>
434
- </tr>
435
- <tr>
436
- <td><code>userId</code></td>
437
- <td>string</td>
438
- <td>Your user's identifier (ttpId)</td>
439
- </tr>
440
- <tr>
441
- <td><code>appId</code></td>
442
- <td>string</td>
443
- <td>Your application identifier</td>
444
- </tr>
445
- <tr>
446
- <td><code>expiresAt</code></td>
447
- <td>Date</td>
448
- <td>When the signed URL expires (ISO 8601 format)</td>
449
- </tr>
450
- <tr>
451
- <td><code>expiresIn</code></td>
452
- <td>number</td>
453
- <td>Token validity duration in milliseconds</td>
454
- </tr>
455
- <tr>
456
- <td><code>generatedAt</code></td>
457
- <td>Date</td>
458
- <td>When the signed URL was generated (ISO 8601 format)</td>
459
- </tr>
460
- <tr>
461
- <td><code>availableCredits</code></td>
462
- <td>number</td>
463
- <td>User's remaining credit balance</td>
464
- </tr>
465
- <tr>
466
- <td><code>authenticationStatus</code></td>
467
- <td>string</td>
468
- <td>Always "SUCCESS" for successful requests</td>
469
- </tr>
470
- </tbody>
471
- </table>
472
-
473
- <h2>JWT Token Properties</h2>
474
- <table class="properties-table">
475
- <thead>
476
- <tr>
477
- <th>Property</th>
478
- <th>Description</th>
479
- </tr>
480
- </thead>
481
- <tbody>
482
- <tr>
483
- <td><code>agentId</code></td>
484
- <td>The AI agent identifier</td>
485
- </tr>
486
- <tr>
487
- <td><code>userId</code></td>
488
- <td>Your user's identifier</td>
489
- </tr>
490
- <tr>
491
- <td><code>appId</code></td>
492
- <td>Your application identifier</td>
493
- </tr>
494
- <tr>
495
- <td><code>allowOverride</code></td>
496
- <td>Permission flag for agent settings override (optional)</td>
497
- </tr>
498
- <tr>
499
- <td><code>exp</code></td>
500
- <td>Token expiration time (TTL - configurable via <code>expirationMs</code>, defaults to 1 hour)</td>
501
- </tr>
502
- </tbody>
503
- </table>
504
-
505
- <h2>Request Parameters</h2>
304
+ <h2>Configuration Parameters</h2>
506
305
  <table class="properties-table">
507
306
  <thead>
508
307
  <tr>
@@ -525,18 +324,6 @@ app.post('/api/get-voice-session', async (req, res) => {
525
324
  <td>Yes</td>
526
325
  <td>Your application identifier</td>
527
326
  </tr>
528
- <tr>
529
- <td><code>expirationMs</code></td>
530
- <td>number</td>
531
- <td>No</td>
532
- <td>Token TTL in milliseconds (default: 3600000 = 1 hour)</td>
533
- </tr>
534
- <tr>
535
- <td><code>allowOverride</code></td>
536
- <td>boolean</td>
537
- <td>No</td>
538
- <td>Enable agent settings override permission (default: false)</td>
539
- </tr>
540
327
  </tbody>
541
328
  </table>
542
329
  </section>
@@ -548,25 +335,21 @@ app.post('/api/get-voice-session', async (req, res) => {
548
335
  <p>Dynamically customize agent behavior, voice, and personality on a per-session basis.</p>
549
336
 
550
337
  <div class="info-box">
551
- <strong>🔒 Security:</strong> Agent overrides are only available with signed link authentication where <code>allowOverride: true</code> is granted by your backend.
338
+ <strong>💡 Access Control:</strong> Agent settings override is available when the agent has domain whitelist configured in the admin panel. No additional authentication step is required.
552
339
  </div>
553
340
 
554
341
  <h2>How It Works</h2>
555
- <div class="info-box">
556
- <strong>🔐 Backend-to-Backend First:</strong> The signed URL with override permission is obtained via backend-to-backend communication before your frontend can use it.
557
- </div>
558
342
  <ol class="numbered-list">
559
- <li><strong>Backend-to-Backend:</strong> Your backend requests a signed URL from TTP backend with <code>allowOverride: true</code></li>
560
- <li><strong>Backend → Frontend:</strong> Your backend returns the signed URL to your frontend</li>
561
- <li><strong>Frontend:</strong> Your frontend uses the SDK with the signed URL + custom agent settings</li>
562
- <li><strong>TTP Backend:</strong> Validates the JWT signature and applies your overrides if permission granted</li>
343
+ <li><strong>Configure:</strong> Set up a domain whitelist for your agent in the admin panel</li>
344
+ <li><strong>Initialize:</strong> Pass <code>agentSettingsOverride</code> in the SDK configuration</li>
345
+ <li><strong>Connect:</strong> The SDK sends overrides in the hello message</li>
346
+ <li><strong>TTP Backend:</strong> Validates the domain and applies your overrides</li>
563
347
  </ol>
564
348
 
565
349
  <h2>Example</h2>
566
350
  <pre><code>const voiceSDK = new VoiceSDK({
567
- signedUrl: signedUrl, // signedUrl from your backend
568
- appId: 'your_app_id', // Your application ID
569
351
  agentId: 'agent_123', // The AI agent to connect to
352
+ appId: 'your_app_id', // Your application ID
570
353
 
571
354
  // Override agent settings
572
355
  agentSettingsOverride: {
@@ -649,16 +432,7 @@ app.post('/api/get-voice-session', async (req, res) => {
649
432
  <p>When using VoiceSDK v2, variables are passed in the SDK constructor:</p>
650
433
  <pre><code>import { VoiceSDK_v2 } from 'ttp-agent-sdk';
651
434
 
652
- // Get signed URL from your backend first
653
- const response = await fetch('/api/get-voice-session', {
654
- method: 'POST',
655
- headers: { 'Content-Type': 'application/json' },
656
- body: JSON.stringify({ agentId: 'agent_5a2b984c1', appId: 'app_Bc01EqMQt2Euehl4qqZSi6l3FJP42Q9vJ0pC' })
657
- });
658
- const { signedUrl } = await response.json();
659
-
660
435
  const voiceSDK = new VoiceSDK_v2({
661
- signedUrl: signedUrl, // Use signedUrl from backend
662
436
  agentId: 'agent_5a2b984c1',
663
437
  appId: 'app_Bc01EqMQt2Euehl4qqZSi6l3FJP42Q9vJ0pC',
664
438
 
@@ -759,16 +533,7 @@ await voiceSDK.connect();</code></pre>
759
533
  <h3>Example 1: JavaScript/TypeScript with SDK v2</h3>
760
534
  <pre><code>import { VoiceSDK_v2 } from 'ttp-agent-sdk';
761
535
 
762
- // Get signed URL from your backend first
763
- const response = await fetch('/api/get-voice-session', {
764
- method: 'POST',
765
- headers: { 'Content-Type': 'application/json' },
766
- body: JSON.stringify({ agentId: 'agent_5a2b984c1', appId: 'app_Bc01EqMQt2Euehl4qqZSi6l3FJP42Q9vJ0pC' })
767
- });
768
- const { signedUrl } = await response.json();
769
-
770
536
  const voiceSDK = new VoiceSDK_v2({
771
- signedUrl: signedUrl, // Use signedUrl from backend
772
537
  agentId: 'agent_5a2b984c1',
773
538
  appId: 'app_Bc01EqMQt2Euehl4qqZSi6l3FJP42Q9vJ0pC',
774
539
 
@@ -1189,9 +954,8 @@ voiceSDK.on('domainError', (error) => {
1189
954
 
1190
955
  <h2>Example: High-Quality Audio</h2>
1191
956
  <pre><code>const voiceSDK = new VoiceSDK({
1192
- signedUrl: signedUrl, // signedUrl from backend
1193
- appId: 'your_app_id',
1194
957
  agentId: 'agent_123',
958
+ appId: 'your_app_id',
1195
959
 
1196
960
  // Request high-quality audio
1197
961
  outputContainer: 'raw', // Raw PCM for lower latency
@@ -1211,9 +975,8 @@ voiceSDK.on('formatNegotiated', (format) => {
1211
975
 
1212
976
  <h2>Example: Bandwidth-Optimized Audio</h2>
1213
977
  <pre><code>const voiceSDK = new VoiceSDK({
1214
- signedUrl: signedUrl, // signedUrl from backend
1215
- appId: 'your_app_id',
1216
978
  agentId: 'agent_123',
979
+ appId: 'your_app_id',
1217
980
 
1218
981
  // Request compressed, low-bandwidth audio
1219
982
  outputContainer: 'raw',
@@ -1293,10 +1056,7 @@ voiceSDK.on('formatNegotiated', (format) => {
1293
1056
  agentId: 'agent_123', // Your AI agent ID
1294
1057
  appId: 'your_app_id', // Your application ID
1295
1058
 
1296
- // Optional - Signed URL (recommended for production)
1297
- signedUrl: 'wss://speech.talktopc.com/ws/conv?signed_token=...', // Signed URL from your backend
1298
-
1299
- // Optional - Agent Settings Override (requires signed URL with allowOverride=true)
1059
+ // Optional - Agent Settings Override (available when domain whitelist is configured)
1300
1060
  agentSettingsOverride: {
1301
1061
  prompt: "You are a helpful customer service assistant.",
1302
1062
  temperature: 0.8,
@@ -1321,105 +1081,30 @@ voiceSDK.on('formatNegotiated', (format) => {
1321
1081
  }
1322
1082
  });</code></pre>
1323
1083
 
1324
- <h2>Signed Link Authentication (Production)</h2>
1325
- <div class="warning-box">
1326
- <strong>🔒 Security Best Practice:</strong> For production applications, use signed links instead of exposing agent IDs directly. Signed links provide secure, time-limited authentication.
1084
+ <h2>Access Control</h2>
1085
+ <div class="info-box">
1086
+ <strong>💡 Domain Whitelist:</strong> For production applications, configure a domain whitelist in your agent's admin panel to control which websites can connect to your agent.
1327
1087
  </div>
1328
1088
 
1329
- <p>To use signed links with the widget, provide a <code>signedUrl</code> parameter. You should fetch this signed URL from your backend before initializing the widget:</p>
1089
+ <p>The widget connects directly using <code>agentId</code> and <code>appId</code>. No backend authentication step is needed:</p>
1330
1090
 
1331
- <pre><code>// Step 1: Get signed URL from your backend
1332
- async function initializeWidget() {
1333
- // Request signed URL from your backend
1334
- const response = await fetch('/api/get-voice-session', {
1335
- method: 'POST',
1336
- headers: {
1337
- 'Content-Type': 'application/json',
1338
- 'Authorization': `Bearer ${getAuthToken()}` // Your auth token
1339
- },
1340
- body: JSON.stringify({
1341
- agentId: 'agent_123',
1342
- appId: 'your_app_id',
1343
- variables: {
1344
- userName: 'John Doe',
1345
- page: 'homepage'
1346
- }
1347
- })
1348
- });
1349
-
1350
- if (!response.ok) {
1351
- throw new Error(`Backend API error: ${response.status}`);
1352
- }
1353
-
1354
- const data = await response.json();
1355
-
1356
- // Backend must return signedUrl field
1357
- if (!data.signedUrl) {
1358
- throw new Error('Backend response must contain "signedUrl" field');
1091
+ <pre><code>const widget = new TTPAgentSDK.TTPChatWidget({
1092
+ agentId: 'agent_123',
1093
+ appId: 'your_app_id',
1094
+ variables: {
1095
+ userName: 'John Doe',
1096
+ page: 'homepage'
1359
1097
  }
1360
-
1361
- // Step 2: Initialize widget with signedUrl
1362
- const widget = new TTPAgentSDK.TTPChatWidget({
1363
- agentId: 'agent_123',
1364
- appId: 'your_app_id',
1365
- signedUrl: data.signedUrl, // Use signed URL from backend
1366
- variables: {
1367
- userName: 'John Doe',
1368
- page: 'homepage'
1369
- }
1370
- });
1371
- }
1372
-
1373
- // Initialize when ready
1374
- initializeWidget();</code></pre>
1098
+ });</code></pre>
1375
1099
 
1376
1100
  <div class="info-box">
1377
1101
  <strong>📋 Important:</strong>
1378
1102
  <ul style="margin-top: 10px; margin-left: 20px;">
1379
- <li>The widget accepts <code>signedUrl</code> as a direct parameter (not <code>getSessionUrl</code>)</li>
1380
- <li>You must fetch the signed URL from your backend before initializing the widget</li>
1381
- <li>Your backend must return a response with a <code>signedUrl</code> field (not <code>websocketUrl</code>, <code>url</code>, or any other field)</li>
1382
- <li>If <code>signedUrl</code> is not provided, the widget will construct a URL from <code>agentId</code> and <code>appId</code> (less secure)</li>
1383
- <li>Your backend should authenticate with TTP backend using your API key (see <a href="#authentication">Authentication</a> section)</li>
1103
+ <li>The widget only needs <code>agentId</code> and <code>appId</code> to connect</li>
1104
+ <li>Access control is managed via domain whitelist in the admin panel</li>
1105
+ <li>Optionally pass <code>websocketUrl</code> to override the default WebSocket endpoint</li>
1384
1106
  </ul>
1385
1107
  </div>
1386
-
1387
- <h3>Backend Implementation Example</h3>
1388
- <p>Your backend endpoint should request signed URLs from TTP:</p>
1389
- <pre><code>// Your Backend (Node.js/Express example)
1390
- app.post('/api/get-voice-session', async (req, res) => {
1391
- const { agentId, appId, variables } = req.body;
1392
-
1393
- // Authenticate your user
1394
- if (!req.user) {
1395
- return res.status(401).json({ error: 'Unauthorized' });
1396
- }
1397
-
1398
- // Backend-to-Backend: Request signed URL from TTP
1399
- const response = await fetch('https://backend.talktopc.com/api/public/agents/signed-url', {
1400
- method: 'POST',
1401
- headers: {
1402
- 'Content-Type': 'application/json',
1403
- 'Authorization': `Bearer ${process.env.TTP_API_KEY}` // Your TTP API key
1404
- },
1405
- body: JSON.stringify({
1406
- agentId,
1407
- appId,
1408
- variables
1409
- })
1410
- });
1411
-
1412
- if (!response.ok) {
1413
- return res.status(500).json({ error: 'Failed to get signed URL' });
1414
- }
1415
-
1416
- const data = await response.json();
1417
-
1418
- // Return signedUrl to frontend
1419
- res.json({
1420
- signedUrl: data.signedUrl
1421
- });
1422
- });</code></pre>
1423
1108
 
1424
1109
  <h2>Advanced Customization</h2>
1425
1110
 
@@ -1466,13 +1151,12 @@ app.post('/api/get-voice-session', async (req, res) => {
1466
1151
 
1467
1152
  <h3>Agent Settings Override</h3>
1468
1153
  <div class="info-box">
1469
- <strong>🔒 Security:</strong> Agent settings override requires a signed URL with <code>allowOverride: true</code> granted by your backend.
1154
+ <strong>💡 Access Control:</strong> Agent settings override is available when the agent has domain whitelist configured in the admin panel.
1470
1155
  </div>
1471
1156
  <p>Dynamically customize agent behavior, voice, and personality on a per-session basis:</p>
1472
1157
  <pre><code>const widget = new TTPAgentSDK.TTPChatWidget({
1473
1158
  agentId: 'agent_123',
1474
1159
  appId: 'your_app_id',
1475
- signedUrl: 'wss://speech.talktopc.com/ws/conv?signed_token=...', // Required: signed URL with allowOverride=true
1476
1160
 
1477
1161
  // Override agent settings dynamically
1478
1162
  agentSettingsOverride: {
@@ -1696,16 +1380,16 @@ document.getElementById('myButton').onclick = () => {
1696
1380
  <td>Custom variables to pass to agent</td>
1697
1381
  </tr>
1698
1382
  <tr>
1699
- <td><code>signedUrl</code></td>
1700
- <td>string | function</td>
1383
+ <td><code>websocketUrl</code></td>
1384
+ <td>string</td>
1701
1385
  <td>null</td>
1702
- <td>Signed URL for secure authentication (string or async function that returns URL)</td>
1386
+ <td>Optional custom WebSocket base URL (defaults to wss://speech.talktopc.com/ws/conv)</td>
1703
1387
  </tr>
1704
1388
  <tr>
1705
1389
  <td><code>agentSettingsOverride</code></td>
1706
1390
  <td>object</td>
1707
1391
  <td>null</td>
1708
- <td>Override agent settings dynamically (requires signed URL with allowOverride=true). See <a href="#agent-override">Agent Settings Override</a> for details.</td>
1392
+ <td>Override agent settings dynamically. See <a href="#agent-override">Agent Settings Override</a> for details.</td>
1709
1393
  </tr>
1710
1394
  <tr>
1711
1395
  <td><code>customStyles</code></td>
@@ -1713,10 +1397,98 @@ document.getElementById('myButton').onclick = () => {
1713
1397
  <td>''</td>
1714
1398
  <td>Custom CSS to inject</td>
1715
1399
  </tr>
1400
+ <tr>
1401
+ <td><code>useShadowDOM</code></td>
1402
+ <td>boolean</td>
1403
+ <td>true</td>
1404
+ <td>Enable Shadow DOM for CSS isolation. Set to <code>false</code> for Shopify compatibility. See <a href="#shadow-dom-config">Shadow DOM Configuration</a> below.</td>
1405
+ </tr>
1716
1406
  </tbody>
1717
1407
  </table>
1718
1408
  </details>
1719
1409
 
1410
+ <div id="shadow-dom-config" class="info-box" style="margin: 20px 0; padding: 20px; background: #f8f9fa; border-left: 4px solid #7C3AED;">
1411
+ <h3 style="margin-top: 0;">Shadow DOM Configuration (<code>useShadowDOM</code>)</h3>
1412
+
1413
+ <p><strong>What is Shadow DOM?</strong></p>
1414
+ <p>Shadow DOM is a web standard that provides CSS isolation by creating a separate DOM tree that doesn't inherit styles from the parent page. This prevents theme CSS from interfering with the widget's appearance.</p>
1415
+
1416
+ <p><strong>When to Use Shadow DOM:</strong></p>
1417
+ <ul style="margin: 10px 0 10px 20px;">
1418
+ <li><strong>✅ WordPress:</strong> Use Shadow DOM (<code>useShadowDOM: true</code> or omit - defaults to <code>true</code>)</li>
1419
+ <li><strong>✅ Most platforms:</strong> Shadow DOM works well on most websites and platforms</li>
1420
+ <li><strong>❌ Shopify:</strong> Disable Shadow DOM (<code>useShadowDOM: false</code>) due to rendering issues</li>
1421
+ </ul>
1422
+
1423
+ <p><strong>Why We Need This Option:</strong></p>
1424
+ <p>While Shadow DOM provides excellent CSS isolation, some platforms (notably Shopify) have rendering issues where Shadow DOM elements render with 0x0 dimensions, making the widget invisible. By setting <code>useShadowDOM: false</code>, the widget uses regular DOM with targeted CSS resets instead, ensuring visibility while still protecting against most theme conflicts.</p>
1425
+
1426
+ <p><strong>Platform-Specific Recommendations:</strong></p>
1427
+ <table class="properties-table" style="margin: 15px 0;">
1428
+ <thead>
1429
+ <tr>
1430
+ <th>Platform</th>
1431
+ <th>Recommended Setting</th>
1432
+ <th>Reason</th>
1433
+ </tr>
1434
+ </thead>
1435
+ <tbody>
1436
+ <tr>
1437
+ <td><strong>WordPress</strong></td>
1438
+ <td><code>useShadowDOM: true</code> (default)</td>
1439
+ <td>Shadow DOM works perfectly and provides better CSS isolation</td>
1440
+ </tr>
1441
+ <tr>
1442
+ <td><strong>Shopify</strong></td>
1443
+ <td><code>useShadowDOM: false</code></td>
1444
+ <td>Shadow DOM elements render with 0x0 dimensions, making widget invisible</td>
1445
+ </tr>
1446
+ <tr>
1447
+ <td><strong>Wix</strong></td>
1448
+ <td><code>useShadowDOM: true</code> (default)</td>
1449
+ <td>Shadow DOM works well on Wix</td>
1450
+ </tr>
1451
+ <tr>
1452
+ <td><strong>Custom Websites</strong></td>
1453
+ <td><code>useShadowDOM: true</code> (default)</td>
1454
+ <td>Use Shadow DOM unless you experience rendering issues</td>
1455
+ </tr>
1456
+ </tbody>
1457
+ </table>
1458
+
1459
+ <p><strong>Example Usage:</strong></p>
1460
+ <pre><code class="language-javascript">// WordPress (default - Shadow DOM enabled)
1461
+ const widget = new TTPChatWidget({
1462
+ agentId: 'your-agent-id',
1463
+ // useShadowDOM defaults to true, so widget is isolated from theme CSS
1464
+ });
1465
+
1466
+ // Shopify (disable Shadow DOM)
1467
+ const widget = new TTPChatWidget({
1468
+ agentId: 'your-agent-id',
1469
+ useShadowDOM: false // Required for Shopify compatibility
1470
+ });
1471
+
1472
+ // If widget is invisible, try disabling Shadow DOM
1473
+ const widget = new TTPChatWidget({
1474
+ agentId: 'your-agent-id',
1475
+ useShadowDOM: false // Fallback if Shadow DOM causes rendering issues
1476
+ });</code></pre>
1477
+
1478
+ <p><strong>How It Works:</strong></p>
1479
+ <ul style="margin: 10px 0 10px 20px;">
1480
+ <li><strong>With Shadow DOM (<code>useShadowDOM: true</code>):</strong> Widget is rendered inside a Shadow DOM tree, completely isolated from page CSS. Styles are injected into the shadow root.</li>
1481
+ <li><strong>Without Shadow DOM (<code>useShadowDOM: false</code>):</strong> Widget is rendered in regular DOM. Styles are injected into the document <code>&lt;head&gt;</code> with high-specificity selectors to prevent theme CSS conflicts. Targeted CSS resets protect against common theme issues while preserving widget functionality.</li>
1482
+ </ul>
1483
+
1484
+ <p><strong>⚠️ Important Notes:</strong></p>
1485
+ <ul style="margin: 10px 0 10px 20px;">
1486
+ <li>When <code>useShadowDOM: false</code>, the widget uses targeted CSS resets instead of aggressive resets to preserve internal widget styles</li>
1487
+ <li>If you experience layout issues with <code>useShadowDOM: false</code>, check if your theme's CSS is overriding widget styles - you may need to add more specific CSS rules</li>
1488
+ <li>The widget automatically handles CSS injection differently based on this setting - no additional configuration needed</li>
1489
+ </ul>
1490
+ </div>
1491
+
1720
1492
  <details>
1721
1493
  <summary><strong>Positioning</strong></summary>
1722
1494
  <table class="properties-table">
@@ -2099,6 +1871,24 @@ document.getElementById('myButton').onclick = () => {
2099
1871
  <td>'#64748b'</td>
2100
1872
  <td>Status subtitle text color</td>
2101
1873
  </tr>
1874
+ <tr>
1875
+ <td><code>voice.startCallTitle</code></td>
1876
+ <td>string</td>
1877
+ <td>null</td>
1878
+ <td>Custom text for "Click to Start Call" title (bypasses translations)</td>
1879
+ </tr>
1880
+ <tr>
1881
+ <td><code>voice.startCallSubtitle</code></td>
1882
+ <td>string</td>
1883
+ <td>null</td>
1884
+ <td>Custom text for "Real-time voice conversation" subtitle (bypasses translations)</td>
1885
+ </tr>
1886
+ <tr>
1887
+ <td><code>voice.startCallButtonText</code></td>
1888
+ <td>string</td>
1889
+ <td>null</td>
1890
+ <td>Custom text for "Start Call" button (bypasses translations)</td>
1891
+ </tr>
2102
1892
  <tr>
2103
1893
  <td><code>voice.startCallButtonColor</code></td>
2104
1894
  <td>string</td>
@@ -2392,10 +2182,10 @@ document.getElementById('myButton').onclick = () => {
2392
2182
  </thead>
2393
2183
  <tbody>
2394
2184
  <tr>
2395
- <td><code>signedUrl</code></td>
2185
+ <td><code>websocketUrl</code></td>
2396
2186
  <td>string</td>
2397
2187
  <td>Optional</td>
2398
- <td>Signed WebSocket URL for secure authentication (for voice). If not provided, URL is constructed from agentId/appId.</td>
2188
+ <td>Custom WebSocket base URL (defaults to wss://speech.talktopc.com/ws/conv). If not provided, URL is constructed from agentId/appId.</td>
2399
2189
  </tr>
2400
2190
  <tr>
2401
2191
  <td><code>demo</code></td>
@@ -2519,14 +2309,9 @@ class VoiceAssistant {
2519
2309
  }
2520
2310
 
2521
2311
  async initialize(agentId, overrides = {}) {
2522
- // Step 1: Get signed URL
2523
- const signedUrl = await this.getSignedUrl(agentId);
2524
-
2525
- // Step 2: Create SDK
2526
2312
  this.sdk = new VoiceSDK({
2527
- signedUrl: signedUrl, // signedUrl from backend
2528
- appId: 'your_app_id',
2529
2313
  agentId: agentId,
2314
+ appId: 'your_app_id',
2530
2315
  agentSettingsOverride: overrides
2531
2316
  });
2532
2317
 
@@ -2570,21 +2355,6 @@ class VoiceAssistant {
2570
2355
  });
2571
2356
  }
2572
2357
 
2573
- async getSignedUrl(agentId) {
2574
- const response = await fetch('/api/get-voice-session', {
2575
- method: 'POST',
2576
- headers: {
2577
- 'Content-Type': 'application/json'
2578
- },
2579
- body: JSON.stringify({
2580
- agentId: agentId
2581
- })
2582
- });
2583
-
2584
- const { signedUrl } = await response.json();
2585
- return signedUrl;
2586
- }
2587
-
2588
2358
  async toggleRecording() {
2589
2359
  if (!this.isConnected) return;
2590
2360
 
@@ -2642,24 +2412,9 @@ function VoiceChat() {
2642
2412
  // Initialize SDK
2643
2413
  useEffect(() => {
2644
2414
  async function initSDK() {
2645
- // Get signed URL
2646
- const response = await fetch('/api/get-voice-session', {
2647
- method: 'POST',
2648
- headers: {
2649
- 'Content-Type': 'application/json'
2650
- },
2651
- body: JSON.stringify({
2652
- agentId: 'agent_123'
2653
- })
2654
- });
2655
-
2656
- const { signedUrl } = await response.json();
2657
-
2658
- // Create SDK
2659
2415
  const sdk = new VoiceSDK({
2660
- signedUrl: signedUrl, // signedUrl from backend
2661
- appId: 'your_app_id',
2662
2416
  agentId: 'agent_123',
2417
+ appId: 'your_app_id',
2663
2418
  agentSettingsOverride: {
2664
2419
  language: 'es',
2665
2420
  temperature: 0.9
@@ -2730,38 +2485,14 @@ export default VoiceChat;</code></pre>
2730
2485
  <pre><code>import { VoiceButton } from 'ttp-agent-sdk/react';</code></pre>
2731
2486
 
2732
2487
  <h2>Basic Usage</h2>
2733
- <pre><code>import React, { useState, useEffect } from 'react';
2488
+ <pre><code>import React from 'react';
2734
2489
  import { VoiceButton } from 'ttp-agent-sdk/react';
2735
2490
 
2736
2491
  function App() {
2737
- const [signedUrl, setSignedUrl] = useState(null);
2738
-
2739
- useEffect(() => {
2740
- async function fetchSignedUrl() {
2741
- const response = await fetch('/api/get-voice-session', {
2742
- method: 'POST',
2743
- headers: {
2744
- 'Content-Type': 'application/json'
2745
- },
2746
- body: JSON.stringify({
2747
- agentId: 'agent_123'
2748
- })
2749
- });
2750
-
2751
- const { signedUrl } = await response.json();
2752
- setSignedUrl(signedUrl);
2753
- }
2754
-
2755
- fetchSignedUrl();
2756
- }, []);
2757
-
2758
- if (!signedUrl) return &lt;div&gt;Loading...&lt;/div&gt;;
2759
-
2760
2492
  return (
2761
2493
  &lt;VoiceButton
2762
- signedUrl={signedUrl} {/* signedUrl from backend */}
2763
- appId="your_app_id"
2764
2494
  agentId="agent_123"
2495
+ appId="your_app_id"
2765
2496
  agentSettingsOverride={{
2766
2497
  language: 'es',
2767
2498
  temperature: 0.9
@@ -2785,10 +2516,10 @@ function App() {
2785
2516
  </thead>
2786
2517
  <tbody>
2787
2518
  <tr>
2788
- <td><code>signedUrl</code></td>
2519
+ <td><code>agentId</code></td>
2789
2520
  <td>string</td>
2790
2521
  <td>Yes</td>
2791
- <td>Signed WebSocket URL</td>
2522
+ <td>The AI agent identifier</td>
2792
2523
  </tr>
2793
2524
  <tr>
2794
2525
  <td><code>appId</code></td>
@@ -2797,10 +2528,10 @@ function App() {
2797
2528
  <td>Your application ID</td>
2798
2529
  </tr>
2799
2530
  <tr>
2800
- <td><code>agentId</code></td>
2531
+ <td><code>websocketUrl</code></td>
2801
2532
  <td>string</td>
2802
- <td>Yes</td>
2803
- <td>The AI agent identifier</td>
2533
+ <td>No</td>
2534
+ <td>Optional custom WebSocket base URL (defaults to wss://speech.talktopc.com/ws/conv)</td>
2804
2535
  </tr>
2805
2536
  <tr>
2806
2537
  <td><code>agentSettingsOverride</code></td>
@@ -2931,10 +2662,10 @@ function App() {
2931
2662
  </thead>
2932
2663
  <tbody>
2933
2664
  <tr>
2934
- <td><code>signedUrl</code></td>
2665
+ <td><code>agentId</code></td>
2935
2666
  <td>string</td>
2936
2667
  <td>Yes</td>
2937
- <td>Signed WebSocket URL from your backend</td>
2668
+ <td>The AI agent identifier to connect to</td>
2938
2669
  </tr>
2939
2670
  <tr>
2940
2671
  <td><code>appId</code></td>
@@ -2943,10 +2674,10 @@ function App() {
2943
2674
  <td>Your application identifier</td>
2944
2675
  </tr>
2945
2676
  <tr>
2946
- <td><code>agentId</code></td>
2677
+ <td><code>websocketUrl</code></td>
2947
2678
  <td>string</td>
2948
- <td>Yes</td>
2949
- <td>The AI agent identifier to connect to</td>
2679
+ <td>No</td>
2680
+ <td>Optional custom WebSocket base URL (defaults to wss://speech.talktopc.com/ws/conv)</td>
2950
2681
  </tr>
2951
2682
  <tr>
2952
2683
  <td><code>agentSettingsOverride</code></td>
@@ -3053,9 +2784,8 @@ function App() {
3053
2784
 
3054
2785
  <h4>Example: Custom Audio Format</h4>
3055
2786
  <pre><code>const voiceSDK = new VoiceSDK({
3056
- signedUrl: signedUrl, // signedUrl from backend
3057
- appId: 'your_app_id',
3058
2787
  agentId: 'agent_123',
2788
+ appId: 'your_app_id',
3059
2789
 
3060
2790
  // Input format (what we send to server)
3061
2791
  sampleRate: 16000,