@creativeorange/azure-text-to-speech 3.0.0 → 3.0.2

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 CHANGED
@@ -7,16 +7,9 @@ Current major version: **3.0.0**
7
7
 
8
8
  ## Why version 3 exists
9
9
 
10
- In version 2, constructors accepted a permanent Azure subscription key and used:
10
+ In version 2, constructors accepted a permanent Azure subscription key and used `fromSubscription`. That forced the real API key into browser bundles, DevTools, and memory.
11
11
 
12
- ```ts
13
- SpeechConfig.fromSubscription(key, region);
14
- SpeechTranslationConfig.fromSubscription(key, region);
15
- ```
16
-
17
- That forced the real API key into browser bundles, DevTools, and memory. Anyone who can open the page can extract the key and consume your Azure Speech quota.
18
-
19
- Version 3 removes that pattern completely. The permanent key stays on your server. The browser only receives short-lived Azure authorization tokens.
12
+ Version 3 removes that pattern completely. The permanent key stays on your server. The browser only receives short-lived Azure authorization tokens via your own backend.
20
13
 
21
14
  ## Architecture
22
15
 
@@ -24,50 +17,63 @@ Version 3 removes that pattern completely. The permanent key stays on your serve
24
17
  Browser
25
18
  → your backend token endpoint
26
19
  → Azure issueToken endpoint
27
- ← temporary Azure token
20
+ ← temporary Azure token + region
28
21
  → Azure Speech SDK (fromAuthorizationToken)
29
22
  ```
30
23
 
31
24
  Supported authentication options:
32
25
 
33
26
  1. `tokenEndpoint` — simple GET endpoint (Craft, classic sites)
34
- 2. `getAuthorizationToken` — custom async provider (Laravel APIs, auth headers, CSRF, etc.)
27
+ 2. `getAuthorizationToken` — custom async provider
35
28
 
36
29
  There is **no** option to pass a permanent subscription key.
37
30
 
31
+ The Azure Speech SDK is **bundled** into the published `dist` files, so consumers do not need to install `microsoft-cognitiveservices-speech-sdk` separately. That dependency remains a `devDependency` of this package.
32
+
38
33
  ## Install
39
34
 
40
35
  ```bash
41
36
  npm install @creativeorange/azure-text-to-speech
42
37
  ```
43
38
 
39
+ TypeScript declarations are published at `dist/main.d.ts`.
40
+
41
+ ```ts
42
+ import {
43
+ TextToSpeech,
44
+ SpeechToText,
45
+ type TextToSpeechOptions,
46
+ type SpeechToTextOptions,
47
+ type SpeechAuthorization,
48
+ } from '@creativeorange/azure-text-to-speech';
49
+ ```
50
+
51
+ Always import from the package root. Do not import from `/dist/...` paths.
52
+
44
53
  ## Public API
45
54
 
46
55
  ### Text to Speech with `tokenEndpoint`
47
56
 
48
57
  ```ts
49
- import {TextToSpeech} from '@creativeorange/azure-text-to-speech';
50
-
51
58
  const textToSpeech = new TextToSpeech({
52
59
  tokenEndpoint: '/actions/azure-speech/token',
53
60
  voice: 'nl-NL-FennaNeural',
54
- region: 'westeurope',
55
61
  });
56
62
 
57
63
  await textToSpeech.start();
58
64
  ```
59
65
 
66
+ The Speech region comes from the token endpoint response, not from frontend configuration. That prevents mismatches between frontend, backend and Azure resource region.
67
+
60
68
  ### Text to Speech with a custom provider
61
69
 
62
70
  ```ts
63
- import {TextToSpeech} from '@creativeorange/azure-text-to-speech';
64
-
65
71
  const textToSpeech = new TextToSpeech({
66
72
  voice: 'nl-NL-FennaNeural',
67
- region: 'westeurope',
68
73
  getAuthorizationToken: async () => {
69
74
  const response = await fetch('/api/azure-speech/token', {
70
75
  credentials: 'same-origin',
76
+ cache: 'no-store',
71
77
  headers: {
72
78
  Accept: 'application/json',
73
79
  },
@@ -87,11 +93,8 @@ await textToSpeech.start();
87
93
  ### Speech to Text
88
94
 
89
95
  ```ts
90
- import {SpeechToText} from '@creativeorange/azure-text-to-speech';
91
-
92
96
  const speechToText = new SpeechToText({
93
97
  tokenEndpoint: '/actions/azure-speech/token',
94
- region: 'westeurope',
95
98
  sourceLanguage: 'nl-NL',
96
99
  targetLanguage: 'nl',
97
100
  });
@@ -99,6 +102,8 @@ const speechToText = new SpeechToText({
99
102
  await speechToText.start();
100
103
  ```
101
104
 
105
+ Continuous recognition can outlive a single Azure token. After recognition starts successfully, Speech-to-Text refreshes the active recognizer token about every eight minutes (`tokenLifetimeMs`) by setting `recognizer.authorizationToken`.
106
+
102
107
  ### Token endpoint response
103
108
 
104
109
  ```json
@@ -122,12 +127,12 @@ type SpeechAuthenticationOptions = {
122
127
  tokenRequestOptions?: {
123
128
  headers?: Record<string, string>;
124
129
  credentials?: RequestCredentials;
130
+ cache?: RequestCache; // default: 'no-store'
125
131
  };
126
132
  tokenLifetimeMs?: number; // default: 8 minutes
127
133
  };
128
134
 
129
135
  type TextToSpeechOptions = SpeechAuthenticationOptions & {
130
- region?: string;
131
136
  voice: string;
132
137
  rate?: number;
133
138
  pitch?: number;
@@ -135,27 +140,20 @@ type TextToSpeechOptions = SpeechAuthenticationOptions & {
135
140
  };
136
141
 
137
142
  type SpeechToTextOptions = SpeechAuthenticationOptions & {
138
- region?: string;
139
143
  sourceLanguage: string;
140
144
  targetLanguage?: string;
141
145
  };
142
146
  ```
143
147
 
144
- Provide **exactly one** of `tokenEndpoint` or `getAuthorizationToken`:
145
-
146
- - both → `Provide either tokenEndpoint or getAuthorizationToken, not both.`
147
- - neither → `A tokenEndpoint or getAuthorizationToken provider is required.`
148
+ Provide **exactly one** of `tokenEndpoint` or `getAuthorizationToken`.
148
149
 
149
- ### Token caching
150
+ ### Token caching and transport
150
151
 
151
- Tokens are cached in memory:
152
-
153
- - fetched on first use
154
- - reused while still valid
155
- - refreshed after about **8 minutes** by default (`tokenLifetimeMs`)
156
- - parallel callers share one in-flight request
157
- - invalid/empty token or region values throw clear errors
158
- - tokens are never logged and never included in error events
152
+ - Tokens are cached in memory and reused while valid.
153
+ - Tokens refresh after about **8 minutes** by default.
154
+ - Parallel callers share one in-flight request.
155
+ - `tokenEndpoint` requests use `credentials: 'same-origin'` and `cache: 'no-store'` by default.
156
+ - Tokens are never logged and never included in error events.
159
157
 
160
158
  ### Browser events
161
159
 
@@ -174,12 +172,8 @@ STT:
174
172
  - `COAzureSTTStoppedRecording`
175
173
  - `COAzureSTTError` → `{ detail: { error: { message, code? } } }`
176
174
 
177
- Error payloads never contain tokens or authorization headers.
178
-
179
175
  ## Migrating from v2 to v3
180
176
 
181
- This is a **breaking** change. The old positional constructor is removed on purpose.
182
-
183
177
  ### Old unsafe usage
184
178
 
185
179
  ```ts
@@ -192,22 +186,13 @@ const textToSpeech = new TextToSpeech(
192
186
  );
193
187
  ```
194
188
 
195
- Or Twig leaking the key into the browser:
196
-
197
- ```twig
198
- <script>
199
- window.azureSpeechKey = '{{ craft.app.config.general.azureSpeechKey }}';
200
- </script>
201
- ```
202
-
203
- Remove these patterns.
189
+ Remove Twig/Vite/window exports of the subscription key.
204
190
 
205
191
  ### New safe usage
206
192
 
207
193
  ```ts
208
194
  const textToSpeech = new TextToSpeech({
209
195
  tokenEndpoint: '/actions/azure-speech/token',
210
- region: 'westeurope',
211
196
  voice: 'nl-NL-FennaNeural',
212
197
  rate: 0,
213
198
  pitch: 0,
@@ -218,13 +203,13 @@ const textToSpeech = new TextToSpeech({
218
203
 
219
204
  1. Update the package to `3.x`.
220
205
  2. Store `AZURE_SPEECH_KEY` and `AZURE_SPEECH_REGION` server-side only.
221
- 3. Add a backend token endpoint (Craft / Laravel / other).
206
+ 3. Add a backend token endpoint.
222
207
  4. Replace the old constructor with the options object.
223
- 5. Remove every Twig, JavaScript, Vite, Nuxt, or window variable that contained the subscription key.
208
+ 5. Remove every frontend variable that contained the subscription key.
224
209
  6. Rebuild the frontend.
225
- 7. Confirm in DevTools that the permanent key no longer appears in network responses meant for the page, JS bundles, or globals.
226
- 8. Test TTS, STT, highlighting, pause/resume, chained playback, and prefetching.
227
- 9. **Rotate the previously exposed Azure subscription key.** Treat it as compromised.
210
+ 7. Confirm in DevTools that the permanent key no longer appears.
211
+ 8. Test TTS, STT, highlighting, pause/resume, chained playback and prefetching.
212
+ 9. **Rotate the previously exposed Azure subscription key.**
228
213
 
229
214
  ## Craft CMS integration
230
215
 
@@ -254,13 +239,29 @@ return [
254
239
  ];
255
240
  ```
256
241
 
257
- With controller ID `token`, the action URL becomes:
242
+ Craft action routing uses:
243
+
244
+ - module ID: `azure-speech`
245
+ - controller ID: `token` (`TokenController`)
246
+ - action: `actionIndex`
247
+
248
+ Resulting URL:
258
249
 
259
250
  ```text
260
251
  /actions/azure-speech/token
261
252
  ```
262
253
 
263
- ### Token controller
254
+ Ensure the module registers its controller namespace, for example in `modules/azurespeech/Module.php`:
255
+
256
+ ```php
257
+ public function init(): void
258
+ {
259
+ parent::init();
260
+ // Controllers live in modules\azurespeech\controllers
261
+ }
262
+ ```
263
+
264
+ ### Token controller for Craft 4 / Craft 5
264
265
 
265
266
  ```php
266
267
  <?php
@@ -273,7 +274,8 @@ use yii\web\Response;
273
274
 
274
275
  class TokenController extends Controller
275
276
  {
276
- protected array|bool $allowAnonymous = true;
277
+ // Craft 4 and Craft 5
278
+ protected array|bool|int $allowAnonymous = true;
277
279
 
278
280
  public function actionIndex(): Response
279
281
  {
@@ -289,7 +291,7 @@ class TokenController extends Controller
289
291
  $client = Craft::createGuzzleClient();
290
292
 
291
293
  try {
292
- $response = $client->post(
294
+ $azureResponse = $client->post(
293
295
  "https://{$region}.api.cognitive.microsoft.com/sts/v1.0/issueToken",
294
296
  [
295
297
  'headers' => [
@@ -309,44 +311,50 @@ class TokenController extends Controller
309
311
  );
310
312
  }
311
313
 
312
- return $this->asJson([
313
- 'token' => (string) $response->getBody(),
314
+ $craftResponse = $this->asJson([
315
+ 'token' => (string) $azureResponse->getBody(),
314
316
  'region' => $region,
315
317
  ]);
318
+
319
+ $craftResponse->getHeaders()->set(
320
+ 'Cache-Control',
321
+ 'private, no-store, max-age=0'
322
+ );
323
+ $craftResponse->getHeaders()->set(
324
+ 'Pragma',
325
+ 'no-cache'
326
+ );
327
+
328
+ return $craftResponse;
316
329
  }
317
330
  }
318
331
  ```
319
332
 
320
- For older Craft/PHP versions where a typed property is undesirable:
333
+ ### Token controller property for Craft 3 / older PHP
321
334
 
322
335
  ```php
336
+ // Craft 3 or older PHP versions without union property types
323
337
  protected $allowAnonymous = true;
324
338
  ```
325
339
 
326
- Use the property form that matches your Craft and PHP version.
340
+ Use the property form that matches your Craft and PHP version. Do not log tokens or exception details to the browser.
327
341
 
328
342
  ### Frontend initialization
329
343
 
330
344
  ```ts
331
- import {TextToSpeech} from '@creativeorange/azure-text-to-speech';
345
+ import {TextToSpeech, SpeechToText} from '@creativeorange/azure-text-to-speech';
332
346
 
333
347
  const textToSpeech = new TextToSpeech({
334
348
  tokenEndpoint: '/actions/azure-speech/token',
335
- region: 'westeurope',
336
349
  voice: 'nl-NL-FennaNeural',
337
350
  });
338
351
 
339
352
  textToSpeech.start().catch((error) => {
340
353
  console.error('Could not initialize text to speech.', error);
341
354
  });
342
- ```
343
-
344
- ```ts
345
- import {SpeechToText} from '@creativeorange/azure-text-to-speech';
346
355
 
347
356
  const speechToText = new SpeechToText({
348
357
  tokenEndpoint: '/actions/azure-speech/token',
349
- region: 'westeurope',
350
358
  sourceLanguage: 'nl-NL',
351
359
  targetLanguage: 'nl',
352
360
  });
@@ -366,12 +374,13 @@ Recommended controls:
366
374
  - Cloudflare rate limiting when available
367
375
  - Authentication when TTS/STT is not meant for anonymous visitors
368
376
  - HTTPS only
369
- - Short server-side timeouts (for example 10 seconds)
377
+ - Short server-side timeouts
370
378
  - Azure budget and usage alerts
371
379
  - Azure resource networking restrictions when infrastructure allows it
372
380
  - Never put the permanent key in responses or logs
381
+ - Return `Cache-Control: private, no-store, max-age=0`
373
382
 
374
- **CORS alone is not security.** Endpoints can be called outside a browser with `curl` or scripts. Rate limiting, auth, and Azure monitoring remain necessary.
383
+ **CORS alone is not security.** Endpoints can be called outside a browser.
375
384
 
376
385
  ## Laravel token endpoint
377
386
 
@@ -382,7 +391,7 @@ use Illuminate\Support\Facades\Route;
382
391
  Route::get('/api/azure-speech/token', function () {
383
392
  $region = config('services.azure_speech.region');
384
393
 
385
- $response = Http::timeout(10)
394
+ $azureResponse = Http::timeout(10)
386
395
  ->withHeaders([
387
396
  'Ocp-Apim-Subscription-Key' =>
388
397
  config('services.azure_speech.key'),
@@ -391,53 +400,23 @@ Route::get('/api/azure-speech/token', function () {
391
400
  "https://{$region}.api.cognitive.microsoft.com/sts/v1.0/issueToken"
392
401
  );
393
402
 
394
- abort_unless($response->successful(), 502);
403
+ abort_unless($azureResponse->successful(), 502);
395
404
 
396
- return response()->json([
397
- 'token' => $response->body(),
398
- 'region' => $region,
399
- ]);
405
+ return response()
406
+ ->json([
407
+ 'token' => $azureResponse->body(),
408
+ 'region' => $region,
409
+ ])
410
+ ->header(
411
+ 'Cache-Control',
412
+ 'private, no-store, max-age=0'
413
+ )
414
+ ->header('Pragma', 'no-cache');
400
415
  })->middleware('throttle:20,1');
401
416
  ```
402
417
 
403
418
  Put this route behind authentication whenever the feature is not public.
404
419
 
405
- Example `config/services.php` entries:
406
-
407
- ```php
408
- 'azure_speech' => [
409
- 'key' => env('AZURE_SPEECH_KEY'),
410
- 'region' => env('AZURE_SPEECH_REGION', 'westeurope'),
411
- ],
412
- ```
413
-
414
- Frontend:
415
-
416
- ```ts
417
- const textToSpeech = new TextToSpeech({
418
- tokenEndpoint: '/api/azure-speech/token',
419
- region: 'westeurope',
420
- voice: 'nl-NL-FennaNeural',
421
- });
422
- ```
423
-
424
- Or with CSRF / custom headers:
425
-
426
- ```ts
427
- const textToSpeech = new TextToSpeech({
428
- voice: 'nl-NL-FennaNeural',
429
- region: 'westeurope',
430
- tokenEndpoint: '/api/azure-speech/token',
431
- tokenRequestOptions: {
432
- headers: {
433
- 'X-CSRF-TOKEN': document
434
- .querySelector('meta[name="csrf-token"]')
435
- ?.getAttribute('content') ?? '',
436
- },
437
- },
438
- });
439
- ```
440
-
441
420
  ## Existing browser features
442
421
 
443
422
  Version 3 keeps the existing client behaviour from the development branch:
@@ -450,36 +429,14 @@ Version 3 keeps the existing client behaviour from the development branch:
450
429
  - voice / rate / pitch setters
451
430
  - existing custom browser events
452
431
 
453
- ## HTML attributes (unchanged)
454
-
455
- TTS examples:
456
-
457
- ```html
458
- <button co-tts="Hello world">Play</button>
459
- <button co-tts.pause>Pause</button>
460
- <button co-tts.resume>Resume</button>
461
- <button co-tts.stop>Stop</button>
462
-
463
- <div id="article" co-tts.highlight co-tts.text="Read this text" co-tts.next="next-block">
464
- Read this text
465
- </div>
466
- ```
467
-
468
- STT examples:
469
-
470
- ```html
471
- <button co-stt.start="transcript">Start</button>
472
- <button co-stt.stop>Stop</button>
473
- <div id="transcript"></div>
474
- ```
475
-
476
432
  ## Development
477
433
 
478
434
  ```bash
479
- npm install
480
- npm run build
481
- npm test
435
+ npm ci
482
436
  npm run lint
437
+ npm test
438
+ npm run build
439
+ npm run typecheck
483
440
  ```
484
441
 
485
442
  Security sanity checks:
@@ -487,9 +444,11 @@ Security sanity checks:
487
444
  ```bash
488
445
  grep -R "fromSubscription" src
489
446
  grep -R "subscriptionKey" src
447
+ grep -R "Ocp-Apim-Subscription-Key" src
448
+ grep -R "@creativeorange/azure-text-to-speech/dist" .
490
449
  ```
491
450
 
492
- Both should return no matches in package source.
451
+ These should return no matches in package source or active examples.
493
452
 
494
453
  ## License
495
454
 
@@ -0,0 +1,27 @@
1
+ import { TranslationRecognizer } from 'microsoft-cognitiveservices-speech-sdk';
2
+ import { SpeechAuthenticationOptions } from './authentication';
3
+ export type SpeechToTextOptions = SpeechAuthenticationOptions & {
4
+ sourceLanguage: string;
5
+ targetLanguage?: string;
6
+ };
7
+ export declare class SpeechToText {
8
+ region: string;
9
+ sourceLanguage: string;
10
+ targetLanguage: string;
11
+ recognizer: TranslationRecognizer | undefined;
12
+ private readonly authorizationManager;
13
+ private readonly authorizationRefreshIntervalMs;
14
+ private authorizationRefreshTimer?;
15
+ private stopPromise?;
16
+ constructor(options: SpeechToTextOptions);
17
+ start(): Promise<void>;
18
+ registerBindings(node: any): Promise<void>;
19
+ private createSpeechTranslationConfig;
20
+ handleStartModifier(node: any, attr: Attr): Promise<void>;
21
+ handleStopModifier(node: any, _attr: Attr): Promise<void>;
22
+ stop(): Promise<void>;
23
+ private scheduleAuthorizationRefresh;
24
+ private clearAuthorizationRefreshTimer;
25
+ private stopRecognizer;
26
+ private dispatchError;
27
+ }
@@ -0,0 +1,87 @@
1
+ import { SpeechAuthenticationOptions } from './authentication';
2
+ export type TextToSpeechOptions = SpeechAuthenticationOptions & {
3
+ voice: string;
4
+ rate?: number;
5
+ pitch?: number;
6
+ url?: string;
7
+ };
8
+ export declare class TextToSpeech {
9
+ region: string;
10
+ voice: string;
11
+ rate: number;
12
+ pitch: number;
13
+ textToRead: string;
14
+ wordBoundryList: any[];
15
+ clickedNode: any;
16
+ highlightDiv: any;
17
+ speechConfig: any;
18
+ audioConfig: any;
19
+ player: any;
20
+ synthesizer: any;
21
+ previousWordBoundary: any;
22
+ originalHighlightDivInnerHTML: string;
23
+ currentWord: string;
24
+ currentOffset: number;
25
+ wordBoundaryOffset: number;
26
+ playbackTextOffsetBase: number | undefined;
27
+ prevTextOffset: number;
28
+ url: string;
29
+ prefetchedAudio: Map<string, any>;
30
+ prefetchPromises: Map<string, Promise<any>>;
31
+ activePrefetchedAudioUrl: string;
32
+ playbackSegments: any[];
33
+ private readonly authorizationManager;
34
+ private stopPromise?;
35
+ private playbackSessionId;
36
+ private highlightInterval?;
37
+ constructor(options: TextToSpeechOptions);
38
+ start(): Promise<void>;
39
+ setVoice(voice: string): this;
40
+ setRate(rate: number): this;
41
+ setPitch(pitch: number): this;
42
+ registerBindings(node: any): Promise<void>;
43
+ handleIdModifier(node: any, attr: Attr): Promise<void>;
44
+ handleAjaxModifier(node: any, attr: Attr): Promise<void>;
45
+ handleDefault(node: any, attr: Attr): Promise<void>;
46
+ handleWithoutClick(node: any, attr: Attr): Promise<void>;
47
+ handleStopModifier(node: any, _attr: Attr): Promise<void>;
48
+ handlePauseModifier(node: any, _attr: Attr): Promise<void>;
49
+ handleResumeModifier(node: any, _attr: Attr): Promise<void>;
50
+ stopPlayer(): Promise<void>;
51
+ private performStopPlayer;
52
+ private createSpeechConfig;
53
+ startSynthesizer(_node: any, _attr: Attr): Promise<void>;
54
+ collectPlaybackChain(node: any): {
55
+ node: any;
56
+ text: any;
57
+ start: number;
58
+ end: any;
59
+ highlightDiv: HTMLElement | undefined;
60
+ originalHighlightDivInnerHTML: string;
61
+ }[];
62
+ preparePlaybackChain(chain: any[]): void;
63
+ getHighlightDivForNode(node: any): HTMLElement | undefined;
64
+ resetPlaybackSegments(): void;
65
+ updateChainedHighlight(wordBoundary: any): void;
66
+ getNodeText(node: any, attr?: Attr | null): any;
67
+ getPrefetchKey(node: any, text: string): string;
68
+ clearPrefetchedAudio(): void;
69
+ prefetchNextNode(node: any): Promise<void>;
70
+ playPrefetchedNode(node: any): Promise<boolean>;
71
+ private createPlaybackSession;
72
+ private isActivePlaybackSession;
73
+ private invalidatePlaybackSession;
74
+ private stopHighlightingInterval;
75
+ private startHighlightingInterval;
76
+ private updateHighlightForCurrentTime;
77
+ private highlightTextRange;
78
+ private setHighlightTargetFromNode;
79
+ private shouldHighlight;
80
+ private resetHighlightState;
81
+ private ensurePlaybackTextOffsetBase;
82
+ private resetCurrentHighlight;
83
+ buildSSML(text: string): string;
84
+ convertHtmlEntities(input: string): string;
85
+ private closeResource;
86
+ private dispatchError;
87
+ }
@@ -0,0 +1,44 @@
1
+ export type SpeechAuthorization = {
2
+ token: string;
3
+ region: string;
4
+ };
5
+ export type SpeechAuthorizationProvider = () => Promise<SpeechAuthorization>;
6
+ export type SpeechTokenRequestOptions = {
7
+ headers?: Record<string, string>;
8
+ credentials?: RequestCredentials;
9
+ cache?: RequestCache;
10
+ };
11
+ export type SpeechAuthenticationOptions = {
12
+ tokenEndpoint?: string;
13
+ getAuthorizationToken?: SpeechAuthorizationProvider;
14
+ tokenRequestOptions?: SpeechTokenRequestOptions;
15
+ tokenLifetimeMs?: number;
16
+ };
17
+ export type SpeechSafeError = {
18
+ message: string;
19
+ code?: string;
20
+ };
21
+ export declare const DEFAULT_TOKEN_LIFETIME_MS: number;
22
+ export declare function createSpeechAuthorizationProvider(options: SpeechAuthenticationOptions): SpeechAuthorizationProvider;
23
+ export declare function validateSpeechAuthorization(value: unknown): SpeechAuthorization;
24
+ export declare function createSafeError(message: string, code?: string): Error & SpeechSafeError;
25
+ export declare function toSafeErrorDetail(error: unknown): SpeechSafeError;
26
+ export declare function sanitizeErrorText(message: string): string;
27
+ export declare function isLikelyAuthorizationError(error: unknown): boolean;
28
+ /**
29
+ * Fetches, validates, caches and refreshes temporary Azure Speech tokens.
30
+ * Never logs authorization tokens.
31
+ */
32
+ export declare class SpeechAuthorizationManager {
33
+ private authorization?;
34
+ private authorizationExpiresAt;
35
+ private authorizationRequest?;
36
+ private readonly provider;
37
+ private readonly tokenLifetimeMs;
38
+ constructor(options: SpeechAuthenticationOptions);
39
+ getTokenLifetimeMs(): number;
40
+ getAuthorization(): Promise<SpeechAuthorization>;
41
+ refreshAuthorization(): Promise<SpeechAuthorization>;
42
+ clearAuthorization(): void;
43
+ private fetchAuthorization;
44
+ }