@volley/recognition-client-sdk 0.1.287 → 0.1.295

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.
@@ -88,6 +88,7 @@ export declare class RealTimeTwoWayWebSocketRecognitionClient extends WebSocketA
88
88
  sendAudio(audioData: ArrayBuffer | ArrayBufferView | Blob): void;
89
89
  private sendAudioInternal;
90
90
  stopRecording(): Promise<void>;
91
+ stopAbnormally(): void;
91
92
  getAudioUtteranceId(): string;
92
93
  getUrl(): string;
93
94
  getState(): ClientState;
@@ -1 +1 @@
1
- {"version":3,"file":"recognition-client.d.ts","sourceRoot":"","sources":["../src/recognition-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAML,KAAK,qBAAqB,EAS3B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,KAAK,EACV,kBAAkB,EAClB,uBAAuB,EACvB,8CAA8C,EAE/C,MAAM,+BAA+B,CAAC;AAUvC;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE3D;AAgCD;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,qBAAqB,CAAC;AAGxD,YAAY,EAAE,8CAA8C,EAAE,MAAM,+BAA+B,CAAC;AAgCpG;;;;;GAKG;AACH,qBAAa,wCACX,SAAQ,oBAAoB,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAC7C,YAAW,kBAAkB;IAE7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAK;IAE7C,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,iBAAiB,CAA4B;IAGrD,OAAO,CAAC,iBAAiB,CAAS;IAGlC,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,qBAAqB,CAAO;IACpC,OAAO,CAAC,iBAAiB,CAAK;gBAElB,MAAM,EAAE,8CAA8C;IA8ElE;;;;;;OAMG;IACH,OAAO,CAAC,GAAG;IAWX;;;OAGG;IACH,OAAO,CAAC,OAAO;IAmBA,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA6BvC;;;OAGG;YACW,gBAAgB;IAkIrB,SAAS,CAAC,SAAS,EAAE,WAAW,GAAG,eAAe,GAAG,IAAI,GAAG,IAAI;IAiBzE,OAAO,CAAC,iBAAiB;IAsCnB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAqCpC,mBAAmB,IAAI,MAAM;IAI7B,MAAM,IAAI,MAAM;IAIhB,QAAQ,IAAI,WAAW;IAIvB,WAAW,IAAI,OAAO;IAItB,YAAY,IAAI,OAAO;IAIvB,UAAU,IAAI,OAAO;IAIrB,uBAAuB,IAAI,OAAO;IAIlC,mBAAmB,IAAI,OAAO;IAI9B,QAAQ,IAAI,uBAAuB;IAgBnC,SAAS,CAAC,WAAW,IAAI,IAAI;IAiE7B,SAAS,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAqB5D,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;cAYlB,SAAS,CAAC,GAAG,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAE,GAAG,IAAI;IAQ/E;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAyB5B;;;OAGG;IACH,OAAO,CAAC,YAAY;CAuBrB"}
1
+ {"version":3,"file":"recognition-client.d.ts","sourceRoot":"","sources":["../src/recognition-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAML,KAAK,qBAAqB,EAS3B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,KAAK,EACV,kBAAkB,EAClB,uBAAuB,EACvB,8CAA8C,EAE/C,MAAM,+BAA+B,CAAC;AAUvC;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE3D;AAgCD;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,qBAAqB,CAAC;AAGxD,YAAY,EAAE,8CAA8C,EAAE,MAAM,+BAA+B,CAAC;AAgCpG;;;;;GAKG;AACH,qBAAa,wCACX,SAAQ,oBAAoB,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAC7C,YAAW,kBAAkB;IAE7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAK;IAE7C,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,iBAAiB,CAA4B;IAGrD,OAAO,CAAC,iBAAiB,CAAS;IAGlC,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,qBAAqB,CAAO;IACpC,OAAO,CAAC,iBAAiB,CAAK;gBAElB,MAAM,EAAE,8CAA8C;IA8ElE;;;;;;OAMG;IACH,OAAO,CAAC,GAAG;IAWX;;;OAGG;IACH,OAAO,CAAC,OAAO;IAmBA,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA6BvC;;;OAGG;YACW,gBAAgB;IAkIrB,SAAS,CAAC,SAAS,EAAE,WAAW,GAAG,eAAe,GAAG,IAAI,GAAG,IAAI;IAiBzE,OAAO,CAAC,iBAAiB;IAsCnB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAoCpC,cAAc,IAAI,IAAI;IAwBtB,mBAAmB,IAAI,MAAM;IAI7B,MAAM,IAAI,MAAM;IAIhB,QAAQ,IAAI,WAAW;IAIvB,WAAW,IAAI,OAAO;IAItB,YAAY,IAAI,OAAO;IAIvB,UAAU,IAAI,OAAO;IAIrB,uBAAuB,IAAI,OAAO;IAIlC,mBAAmB,IAAI,OAAO;IAI9B,QAAQ,IAAI,uBAAuB;IAgBnC,SAAS,CAAC,WAAW,IAAI,IAAI;IAiE7B,SAAS,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAqB5D,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;cAYlB,SAAS,CAAC,GAAG,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,GAAG,CAAA;KAAE,GAAG,IAAI;IAQ/E;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAyB5B;;;OAGG;IACH,OAAO,CAAC,YAAY;CAuBrB"}
@@ -172,6 +172,24 @@ export interface IRecognitionClient {
172
172
  * @returns Promise that resolves when final transcript is received
173
173
  */
174
174
  stopRecording(): Promise<void>;
175
+ /**
176
+ * Force stop and immediately close connection without waiting for server
177
+ *
178
+ * WARNING: This is an abnormal shutdown that bypasses the graceful stop flow:
179
+ * - Does NOT wait for server to process remaining audio
180
+ * - Does NOT receive final transcript from server
181
+ * - Immediately closes WebSocket connection
182
+ * - Cleans up resources (buffers, listeners)
183
+ *
184
+ * Use Cases:
185
+ * - User explicitly cancels/abandons session
186
+ * - Timeout scenarios where waiting is not acceptable
187
+ * - Need immediate cleanup and can't wait for server
188
+ *
189
+ * RECOMMENDED: Use stopRecording() for normal shutdown.
190
+ * Only use this when immediate disconnection is required.
191
+ */
192
+ stopAbnormally(): void;
175
193
  /**
176
194
  * Get the audio utterance ID for this session
177
195
  * Available immediately after client construction.
@@ -1 +1 @@
1
- {"version":3,"file":"recognition-client.types.d.ts","sourceRoot":"","sources":["../src/recognition-client.types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"recognition-client.types.d.ts","sourceRoot":"","sources":["../src/recognition-client.types.ts"],"names":[],"mappings":"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"}
@@ -46,6 +46,24 @@ export interface ISimplifiedVGFRecognitionClient {
46
46
  * @returns Promise that resolves when transcription is complete
47
47
  */
48
48
  stopRecording(): Promise<void>;
49
+ /**
50
+ * Force stop and immediately close connection without waiting for server
51
+ *
52
+ * WARNING: This is an abnormal shutdown that bypasses the graceful stop flow:
53
+ * - Does NOT wait for server to process remaining audio
54
+ * - Does NOT receive final transcript from server (VGF state set to empty)
55
+ * - Immediately closes WebSocket connection
56
+ * - Cleans up resources (buffers, listeners)
57
+ *
58
+ * Use Cases:
59
+ * - User explicitly cancels/abandons the session
60
+ * - Timeout scenarios where waiting is not acceptable
61
+ * - Need immediate cleanup and can't wait for server
62
+ *
63
+ * RECOMMENDED: Use stopRecording() for normal shutdown.
64
+ * Only use this when immediate disconnection is required.
65
+ */
66
+ stopAbnormally(): void;
49
67
  /**
50
68
  * Get the current VGF recognition state
51
69
  * @returns Current RecognitionState with all transcription data
@@ -97,6 +115,7 @@ export declare class SimplifiedVGFRecognitionClient implements ISimplifiedVGFRec
97
115
  connect(): Promise<void>;
98
116
  sendAudio(audioData: ArrayBuffer | ArrayBufferView | Blob): void;
99
117
  stopRecording(): Promise<void>;
118
+ stopAbnormally(): void;
100
119
  getAudioUtteranceId(): string;
101
120
  getUrl(): string;
102
121
  getState(): ClientState;
@@ -1 +1 @@
1
- {"version":3,"file":"simplified-vgf-recognition-client.d.ts","sourceRoot":"","sources":["../src/simplified-vgf-recognition-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAEH,wBAAwB,EACxB,WAAW,EACd,MAAM,+BAA+B,CAAC;AAWvC;;GAEG;AACH,MAAM,WAAW,yBAA0B,SAAQ,wBAAwB;IACvE;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAElD;;;OAGG;IACH,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACnC;AAED;;;;;GAKG;AACH,MAAM,WAAW,+BAA+B;IAE5C;;;OAGG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzB;;;OAGG;IACH,SAAS,CAAC,SAAS,EAAE,WAAW,GAAG,eAAe,GAAG,IAAI,GAAG,IAAI,CAAC;IAEjE;;;OAGG;IACH,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAG/B;;;OAGG;IACH,WAAW,IAAI,gBAAgB,CAAC;IAGhC;;OAEG;IACH,WAAW,IAAI,OAAO,CAAC;IAEvB;;OAEG;IACH,YAAY,IAAI,OAAO,CAAC;IAExB;;OAEG;IACH,UAAU,IAAI,OAAO,CAAC;IAEtB;;OAEG;IACH,uBAAuB,IAAI,OAAO,CAAC;IAEnC;;OAEG;IACH,mBAAmB,IAAI,OAAO,CAAC;IAG/B;;OAEG;IACH,mBAAmB,IAAI,MAAM,CAAC;IAE9B;;OAEG;IACH,MAAM,IAAI,MAAM,CAAC;IAEjB;;OAEG;IACH,QAAQ,IAAI,WAAW,CAAC;CAE3B;AAED;;;GAGG;AACH,qBAAa,8BAA+B,YAAW,+BAA+B;IAClF,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,gBAAgB,CAAkB;IAC1C,OAAO,CAAC,mBAAmB,CAAkD;gBAEjE,MAAM,EAAE,yBAAyB;IAoGvC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAK9B,SAAS,CAAC,SAAS,EAAE,WAAW,GAAG,eAAe,GAAG,IAAI,GAAG,IAAI;IAc1D,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAQpC,mBAAmB,IAAI,MAAM;IAI7B,MAAM,IAAI,MAAM;IAIhB,QAAQ,IAAI,WAAW;IAIvB,WAAW,IAAI,OAAO;IAItB,YAAY,IAAI,OAAO;IAIvB,UAAU,IAAI,OAAO;IAIrB,uBAAuB,IAAI,OAAO;IAIlC,mBAAmB,IAAI,OAAO;IAM9B,WAAW,IAAI,gBAAgB;IAI/B,OAAO,CAAC,iBAAiB;CAK5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,yBAAyB,GAAG,+BAA+B,CAE5G"}
1
+ {"version":3,"file":"simplified-vgf-recognition-client.d.ts","sourceRoot":"","sources":["../src/simplified-vgf-recognition-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACH,gBAAgB,EAGnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAEH,wBAAwB,EACxB,WAAW,EACd,MAAM,+BAA+B,CAAC;AAWvC;;GAEG;AACH,MAAM,WAAW,yBAA0B,SAAQ,wBAAwB;IACvE;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAElD;;;OAGG;IACH,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACnC;AAED;;;;;GAKG;AACH,MAAM,WAAW,+BAA+B;IAE5C;;;OAGG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzB;;;OAGG;IACH,SAAS,CAAC,SAAS,EAAE,WAAW,GAAG,eAAe,GAAG,IAAI,GAAG,IAAI,CAAC;IAEjE;;;OAGG;IACH,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/B;;;;;;;;;;;;;;;;OAgBG;IACH,cAAc,IAAI,IAAI,CAAC;IAGvB;;;OAGG;IACH,WAAW,IAAI,gBAAgB,CAAC;IAGhC;;OAEG;IACH,WAAW,IAAI,OAAO,CAAC;IAEvB;;OAEG;IACH,YAAY,IAAI,OAAO,CAAC;IAExB;;OAEG;IACH,UAAU,IAAI,OAAO,CAAC;IAEtB;;OAEG;IACH,uBAAuB,IAAI,OAAO,CAAC;IAEnC;;OAEG;IACH,mBAAmB,IAAI,OAAO,CAAC;IAG/B;;OAEG;IACH,mBAAmB,IAAI,MAAM,CAAC;IAE9B;;OAEG;IACH,MAAM,IAAI,MAAM,CAAC;IAEjB;;OAEG;IACH,QAAQ,IAAI,WAAW,CAAC;CAE3B;AAED;;;GAGG;AACH,qBAAa,8BAA+B,YAAW,+BAA+B;IAClF,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,gBAAgB,CAAkB;IAC1C,OAAO,CAAC,mBAAmB,CAAkD;gBAEjE,MAAM,EAAE,yBAAyB;IAoGvC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAK9B,SAAS,CAAC,SAAS,EAAE,WAAW,GAAG,eAAe,GAAG,IAAI,GAAG,IAAI;IAc1D,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAOpC,cAAc,IAAI,IAAI;IAiCtB,mBAAmB,IAAI,MAAM;IAI7B,MAAM,IAAI,MAAM;IAIhB,QAAQ,IAAI,WAAW;IAIvB,WAAW,IAAI,OAAO;IAItB,YAAY,IAAI,OAAO;IAIvB,UAAU,IAAI,OAAO;IAIrB,uBAAuB,IAAI,OAAO;IAIlC,mBAAmB,IAAI,OAAO;IAM9B,WAAW,IAAI,gBAAgB;IAI/B,OAAO,CAAC,iBAAiB;CAK5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,yBAAyB,GAAG,+BAA+B,CAE5G"}
@@ -73,6 +73,7 @@ export declare const TranscriptionStatus: {
73
73
  readonly NOT_STARTED: "NOT_STARTED";
74
74
  readonly IN_PROGRESS: "IN_PROGRESS";
75
75
  readonly FINALIZED: "FINALIZED";
76
+ readonly ABORTED: "ABORTED";
76
77
  readonly ERROR: "ERROR";
77
78
  };
78
79
  export type TranscriptionStatusType = typeof TranscriptionStatus[keyof typeof TranscriptionStatus];
@@ -1 +1 @@
1
- {"version":3,"file":"vgf-recognition-state.d.ts","sourceRoot":"","sources":["../src/vgf-recognition-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0BpC,CAAA;AAEF,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAA;AAGxE,eAAO,MAAM,eAAe;;;;;CAKlB,CAAA;AAEV,MAAM,MAAM,mBAAmB,GAAG,OAAO,eAAe,CAAC,MAAM,OAAO,eAAe,CAAC,CAAA;AAEtF,eAAO,MAAM,mBAAmB;;;;;CAKtB,CAAA;AAEV,MAAM,MAAM,uBAAuB,GAAG,OAAO,mBAAmB,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAA;AAGlG,wBAAgB,6BAA6B,CAAC,gBAAgB,EAAE,MAAM,GAAG,gBAAgB,CAOxF;AAGD,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAa9F"}
1
+ {"version":3,"file":"vgf-recognition-state.d.ts","sourceRoot":"","sources":["../src/vgf-recognition-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0BpC,CAAA;AAEF,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAA;AAGxE,eAAO,MAAM,eAAe;;;;;CAKlB,CAAA;AAEV,MAAM,MAAM,mBAAmB,GAAG,OAAO,eAAe,CAAC,MAAM,OAAO,eAAe,CAAC,CAAA;AAEtF,eAAO,MAAM,mBAAmB;;;;;;CAMtB,CAAA;AAEV,MAAM,MAAM,uBAAuB,GAAG,OAAO,mBAAmB,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAA;AAGlG,wBAAgB,6BAA6B,CAAC,gBAAgB,EAAE,MAAM,GAAG,gBAAgB,CAOxF;AAGD,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAa9F"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volley/recognition-client-sdk",
3
- "version": "0.1.287",
3
+ "version": "0.1.295",
4
4
  "description": "Recognition Service TypeScript/Node.js Client SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -53,8 +53,8 @@
53
53
  "ts-jest": "^29.4.5",
54
54
  "typescript": "^5.1.6",
55
55
  "@recog/shared-config": "1.0.0",
56
- "@recog/shared-utils": "1.0.0",
57
56
  "@recog/shared-types": "1.0.0",
57
+ "@recog/shared-utils": "1.0.0",
58
58
  "@recog/websocket": "1.0.0"
59
59
  },
60
60
  "keywords": [
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { RealTimeTwoWayWebSocketRecognitionClient } from './recognition-client';
6
6
  import { ClientState } from './recognition-client.types';
7
+ import { RecognitionResultTypeV1 } from '@recog/shared-types';
7
8
  import { WebSocket as MockWebSocket } from 'ws';
8
9
 
9
10
  // Mock WebSocket
@@ -752,7 +753,11 @@ describe('RealTimeTwoWayWebSocketRecognitionClient', () => {
752
753
  async function setupConnectedClient() {
753
754
  const connectPromise = client.connect();
754
755
  mockWs.readyState = MockWebSocket.OPEN;
755
- const openHandler = mockWs.on.mock.calls.find((call: any[]) => call[0] === 'open')[1];
756
+ const openCall = mockWs.on.mock.calls.find((call: any[]) => call[0] === 'open');
757
+ if (!openCall) {
758
+ throw new Error('No "open" event handler registered on mockWs');
759
+ }
760
+ const openHandler = openCall[1];
756
761
  openHandler();
757
762
  await connectPromise;
758
763
  }
@@ -771,4 +776,114 @@ describe('RealTimeTwoWayWebSocketRecognitionClient', () => {
771
776
  });
772
777
  messageHandler(readyMessage);
773
778
  }
779
+
780
+ describe('stopAbnormally', () => {
781
+ beforeEach(() => {
782
+ // Create fresh mock WebSocket
783
+ mockWs = {
784
+ readyState: MockWebSocket.CONNECTING,
785
+ send: jest.fn(),
786
+ close: jest.fn(),
787
+ on: jest.fn().mockReturnThis(),
788
+ removeAllListeners: jest.fn(),
789
+ };
790
+
791
+ // Mock WebSocket constructor
792
+ (MockWebSocket as any).mockImplementation(() => mockWs);
793
+
794
+ // Create fresh client
795
+ client = new RealTimeTwoWayWebSocketRecognitionClient({
796
+ url: 'ws://localhost:3000',
797
+ asrRequestConfig: {
798
+ provider: 'deepgram',
799
+ language: 'en',
800
+ sampleRate: 16000,
801
+ encoding: 'linear16'
802
+ },
803
+ onTranscript: jest.fn(),
804
+ onError: jest.fn(),
805
+ onConnected: jest.fn(),
806
+ onDisconnected: jest.fn()
807
+ });
808
+ });
809
+
810
+ it.skip('should immediately close WebSocket connection', async () => {
811
+ await setupReadyClient();
812
+ expect(client.getState()).toBe(ClientState.READY);
813
+
814
+ client.stopAbnormally();
815
+
816
+ expect(mockWs.close).toHaveBeenCalledWith(1000, 'Client abnormal stop');
817
+ });
818
+
819
+ it.skip('should update state to STOPPED', async () => {
820
+ await setupReadyClient();
821
+
822
+ client.stopAbnormally();
823
+
824
+ expect(client.getState()).toBe(ClientState.STOPPED);
825
+ });
826
+
827
+ it('should work from any state', () => {
828
+ // Test from INITIAL state
829
+ expect(client.getState()).toBe(ClientState.INITIAL);
830
+ client.stopAbnormally();
831
+ expect(client.getState()).toBe(ClientState.STOPPED);
832
+ });
833
+
834
+ it.skip('should clean up resources', async () => {
835
+ await setupReadyClient();
836
+
837
+ // Send some audio to populate buffers
838
+ client.sendAudio(new ArrayBuffer(1000));
839
+
840
+ // Verify audio was sent
841
+ const statsBefore = client.getStats();
842
+ expect(statsBefore.audioBytesSent).toBeGreaterThan(0);
843
+
844
+ client.stopAbnormally();
845
+
846
+ // Cleanup resets stats
847
+ const statsAfter = client.getStats();
848
+ expect(statsAfter.audioBytesSent).toBe(0);
849
+ expect(statsAfter.audioChunksSent).toBe(0);
850
+ });
851
+
852
+ it.skip('should not send stop signal to server (immediate disconnect)', async () => {
853
+ await setupReadyClient();
854
+ jest.clearAllMocks(); // Clear connection setup messages
855
+
856
+ client.stopAbnormally();
857
+
858
+ // Should NOT send stop recording signal (unlike stopRecording)
859
+ // Only closes the WebSocket
860
+ expect(mockWs.send).not.toHaveBeenCalled();
861
+ expect(mockWs.close).toHaveBeenCalled();
862
+ });
863
+
864
+ it.skip('should differ from stopRecording behavior', async () => {
865
+ // stopAbnormally does NOT send stop signal (unlike stopRecording which sends STOP_RECORDING signal)
866
+ // This is verified by the previous test "should not send stop signal to server"
867
+ // This test verifies stopAbnormally doesn't wait for server response
868
+
869
+ await setupReadyClient();
870
+
871
+ // Call stopAbnormally
872
+ client.stopAbnormally();
873
+
874
+ // State should immediately be STOPPED (not STOPPING)
875
+ expect(client.getState()).toBe(ClientState.STOPPED);
876
+
877
+ // This is different from stopRecording which would be STOPPING and waiting for server
878
+ });
879
+
880
+ it('should be idempotent - safe to call multiple times', () => {
881
+ client.stopAbnormally();
882
+ expect(client.getState()).toBe(ClientState.STOPPED);
883
+
884
+ // Call again - should not throw
885
+ expect(() => client.stopAbnormally()).not.toThrow();
886
+ expect(client.getState()).toBe(ClientState.STOPPED);
887
+ });
888
+ });
774
889
  });
@@ -544,6 +544,29 @@ export class RealTimeTwoWayWebSocketRecognitionClient
544
544
  });
545
545
  }
546
546
 
547
+ stopAbnormally(): void {
548
+ // Guard: If already in terminal state, do nothing
549
+ if (this.state === ClientState.STOPPED || this.state === ClientState.FAILED) {
550
+ this.log('debug', 'stopAbnormally called but already in terminal state', { state: this.state });
551
+ return;
552
+ }
553
+
554
+ this.log('warn', 'Abnormal stop requested - closing connection immediately', { state: this.state });
555
+
556
+ // Update state to STOPPED (skip STOPPING)
557
+ this.state = ClientState.STOPPED;
558
+
559
+ // Clean up resources
560
+ this.cleanup();
561
+
562
+ // Close WebSocket connection immediately
563
+ // Code 1000 = Normal Closure (even though abnormal for us, it's normal for WebSocket spec)
564
+ // Type assertion needed because closeConnection is a newly added protected method
565
+ (this as any).closeConnection(1000, 'Client abnormal stop');
566
+
567
+ // Note: onDisconnected will be called by WebSocket close event
568
+ // which will call cleanup again (idempotent) and trigger onDisconnected callback
569
+ }
547
570
 
548
571
  getAudioUtteranceId(): string {
549
572
  return this.config.audioUtteranceId;
@@ -220,6 +220,25 @@ export interface IRecognitionClient {
220
220
  */
221
221
  stopRecording(): Promise<void>;
222
222
 
223
+ /**
224
+ * Force stop and immediately close connection without waiting for server
225
+ *
226
+ * WARNING: This is an abnormal shutdown that bypasses the graceful stop flow:
227
+ * - Does NOT wait for server to process remaining audio
228
+ * - Does NOT receive final transcript from server
229
+ * - Immediately closes WebSocket connection
230
+ * - Cleans up resources (buffers, listeners)
231
+ *
232
+ * Use Cases:
233
+ * - User explicitly cancels/abandons session
234
+ * - Timeout scenarios where waiting is not acceptable
235
+ * - Need immediate cleanup and can't wait for server
236
+ *
237
+ * RECOMMENDED: Use stopRecording() for normal shutdown.
238
+ * Only use this when immediate disconnection is required.
239
+ */
240
+ stopAbnormally(): void;
241
+
223
242
  /**
224
243
  * Get the audio utterance ID for this session
225
244
  * Available immediately after client construction.
@@ -29,6 +29,7 @@ describe('SimplifiedVGFRecognitionClient', () => {
29
29
  connect: jest.fn().mockResolvedValue(undefined),
30
30
  sendAudio: jest.fn(),
31
31
  stopRecording: jest.fn().mockResolvedValue(undefined),
32
+ stopAbnormally: jest.fn(),
32
33
  getAudioUtteranceId: jest.fn().mockReturnValue('test-uuid'),
33
34
  getState: jest.fn().mockReturnValue(ClientState.INITIAL),
34
35
  isConnected: jest.fn().mockReturnValue(false),
@@ -674,4 +675,273 @@ describe('SimplifiedVGFRecognitionClient', () => {
674
675
  expect(callbackState).not.toBe(currentState); // Different references
675
676
  });
676
677
  });
678
+
679
+ describe('stopAbnormally', () => {
680
+ beforeEach(() => {
681
+ simplifiedClient = new SimplifiedVGFRecognitionClient({
682
+ asrRequestConfig: {
683
+ provider: 'deepgram',
684
+ language: 'en',
685
+ sampleRate: 16000,
686
+ encoding: AudioEncoding.LINEAR16
687
+ },
688
+ onStateChange: stateChangeCallback
689
+ });
690
+ });
691
+
692
+ it('should immediately set state to ABORTED and preserve partial transcript', () => {
693
+ // Start recording first
694
+ simplifiedClient.sendAudio(Buffer.from([1, 2, 3]));
695
+ jest.clearAllMocks();
696
+
697
+ // Call stopAbnormally
698
+ simplifiedClient.stopAbnormally();
699
+
700
+ // Verify state was updated to ABORTED (not FINALIZED)
701
+ expect(stateChangeCallback).toHaveBeenCalledTimes(1);
702
+ const finalState = stateChangeCallback.mock.calls[0][0];
703
+
704
+ expect(finalState.transcriptionStatus).toBe(TranscriptionStatus.ABORTED);
705
+ // finalTranscript is preserved (not overridden to empty string)
706
+ expect(finalState.startRecordingStatus).toBe(RecordingStatus.FINISHED);
707
+ expect(finalState.finalRecordingTimestamp).toBeDefined();
708
+ expect(finalState.finalTranscriptionTimestamp).toBeDefined();
709
+ });
710
+
711
+ it('should stop recording audio flag', () => {
712
+ // Start recording
713
+ simplifiedClient.sendAudio(Buffer.from([1, 2, 3]));
714
+
715
+ // Call stopAbnormally
716
+ simplifiedClient.stopAbnormally();
717
+
718
+ // Send more audio - should not update recording status again
719
+ jest.clearAllMocks();
720
+ simplifiedClient.sendAudio(Buffer.from([4, 5, 6]));
721
+
722
+ // Verify recording status was set in sendAudio
723
+ const state = simplifiedClient.getVGFState();
724
+ expect(state.startRecordingStatus).toBe(RecordingStatus.RECORDING);
725
+ });
726
+
727
+ it('should be idempotent - calling twice does not change state again', () => {
728
+ // Start recording
729
+ simplifiedClient.sendAudio(Buffer.from([1, 2, 3]));
730
+ jest.clearAllMocks();
731
+
732
+ // Call stopAbnormally first time
733
+ simplifiedClient.stopAbnormally();
734
+ expect(stateChangeCallback).toHaveBeenCalledTimes(1);
735
+
736
+ const firstCallState = stateChangeCallback.mock.calls[0][0];
737
+ jest.clearAllMocks();
738
+
739
+ // Call stopAbnormally second time
740
+ simplifiedClient.stopAbnormally();
741
+
742
+ // Should not trigger state change callback again (already aborted)
743
+ expect(stateChangeCallback).toHaveBeenCalledTimes(0);
744
+
745
+ const currentState = simplifiedClient.getVGFState();
746
+ expect(currentState.transcriptionStatus).toBe(TranscriptionStatus.ABORTED);
747
+ expect(currentState.finalTranscript).toBe('');
748
+ });
749
+
750
+ it('should work even if called before any recording', () => {
751
+ // Call stopAbnormally without ever recording
752
+ simplifiedClient.stopAbnormally();
753
+
754
+ const state = simplifiedClient.getVGFState();
755
+ expect(state.transcriptionStatus).toBe(TranscriptionStatus.ABORTED);
756
+ expect(state.finalTranscript).toBe('');
757
+ expect(state.startRecordingStatus).toBe(RecordingStatus.FINISHED);
758
+ });
759
+
760
+ it('should preserve existing state fields except for overridden ones', () => {
761
+ // Set up some initial state by sending audio
762
+ simplifiedClient.sendAudio(Buffer.from([1, 2, 3]));
763
+
764
+ const initialState = simplifiedClient.getVGFState();
765
+ const audioUtteranceId = initialState.audioUtteranceId;
766
+ const initialTranscript = initialState.finalTranscript;
767
+
768
+ // Call stopAbnormally
769
+ simplifiedClient.stopAbnormally();
770
+
771
+ const finalState = simplifiedClient.getVGFState();
772
+
773
+ // Should preserve audioUtteranceId, finalTranscript and other non-overridden fields
774
+ expect(finalState.audioUtteranceId).toBe(audioUtteranceId);
775
+ expect(finalState.finalTranscript).toBe(initialTranscript); // Preserved
776
+
777
+ // Should override these fields
778
+ expect(finalState.transcriptionStatus).toBe(TranscriptionStatus.ABORTED);
779
+ expect(finalState.startRecordingStatus).toBe(RecordingStatus.FINISHED);
780
+ });
781
+
782
+ it('should set both recording and transcription timestamps', () => {
783
+ const beforeTime = new Date().toISOString();
784
+
785
+ simplifiedClient.stopAbnormally();
786
+
787
+ const state = simplifiedClient.getVGFState();
788
+ const afterTime = new Date().toISOString();
789
+
790
+ // Timestamps should be set and within reasonable range
791
+ expect(state.finalRecordingTimestamp).toBeDefined();
792
+ expect(state.finalTranscriptionTimestamp).toBeDefined();
793
+
794
+ // Basic sanity check that timestamps are ISO strings
795
+ expect(state.finalRecordingTimestamp).toMatch(/^\d{4}-\d{2}-\d{2}T/);
796
+ expect(state.finalTranscriptionTimestamp).toMatch(/^\d{4}-\d{2}-\d{2}T/);
797
+
798
+ // Timestamps should be close to current time
799
+ if (state.finalRecordingTimestamp) {
800
+ expect(state.finalRecordingTimestamp >= beforeTime).toBe(true);
801
+ expect(state.finalRecordingTimestamp <= afterTime).toBe(true);
802
+ }
803
+ });
804
+
805
+ it('should call underlying client stopAbnormally for cleanup', () => {
806
+ simplifiedClient.stopAbnormally();
807
+
808
+ // stopAbnormally on underlying client SHOULD be called for WebSocket cleanup
809
+ expect(mockClient.stopAbnormally).toHaveBeenCalled();
810
+
811
+ // stopRecording on underlying client should NOT be called
812
+ expect(mockClient.stopRecording).not.toHaveBeenCalled();
813
+ });
814
+
815
+ it('should differ from stopRecording behavior', async () => {
816
+ // Test that stopAbnormally and stopRecording behave differently
817
+ jest.clearAllMocks();
818
+
819
+ // Use the existing simplifiedClient for testing
820
+ simplifiedClient.sendAudio(Buffer.from([1, 2, 3]));
821
+
822
+ // Test stopAbnormally - should NOT call underlying client
823
+ simplifiedClient.stopAbnormally();
824
+ expect(mockClient.stopRecording).not.toHaveBeenCalled();
825
+
826
+ // Create new client to test stopRecording
827
+ const client2 = new SimplifiedVGFRecognitionClient({
828
+ asrRequestConfig: {
829
+ provider: 'deepgram',
830
+ language: 'en',
831
+ sampleRate: 16000,
832
+ encoding: AudioEncoding.LINEAR16
833
+ },
834
+ onStateChange: jest.fn()
835
+ });
836
+
837
+ // Clear mocks to isolate client2's behavior
838
+ jest.clearAllMocks();
839
+
840
+ // Test stopRecording - SHOULD call underlying client
841
+ await client2.stopRecording();
842
+ expect(mockClient.stopRecording).toHaveBeenCalled();
843
+ });
844
+
845
+ it('should use ABORTED status to distinguish from normal completion', () => {
846
+ // Test that stopAbnormally uses ABORTED, not FINALIZED
847
+ simplifiedClient.sendAudio(Buffer.from([1, 2, 3]));
848
+
849
+ // Abnormal stop - should set to ABORTED
850
+ simplifiedClient.stopAbnormally();
851
+ const abortedState = simplifiedClient.getVGFState();
852
+
853
+ // Verify ABORTED is used (not FINALIZED)
854
+ expect(abortedState.transcriptionStatus).toBe(TranscriptionStatus.ABORTED);
855
+ expect(abortedState.transcriptionStatus).not.toBe(TranscriptionStatus.FINALIZED);
856
+ expect(abortedState.finalTranscript).toBe(''); // Empty because cancelled
857
+
858
+ // ABORTED clearly indicates user cancelled, vs FINALIZED which means completed normally
859
+ });
860
+
861
+ describe('state guards', () => {
862
+ it('should do nothing if already fully stopped', () => {
863
+ // Setup: finalize state and mark underlying client as stopped
864
+ mockClient.getState.mockReturnValue(ClientState.STOPPED);
865
+ simplifiedClient.stopAbnormally();
866
+
867
+ // Clear mocks to test second call
868
+ jest.clearAllMocks();
869
+
870
+ // Call again - should return early and not call anything
871
+ simplifiedClient.stopAbnormally();
872
+
873
+ expect(stateChangeCallback).not.toHaveBeenCalled();
874
+ expect(mockClient.stopAbnormally).not.toHaveBeenCalled();
875
+ });
876
+
877
+ it('should not call underlying client if already in STOPPED state', () => {
878
+ // Mock underlying client as already stopped
879
+ mockClient.getState.mockReturnValue(ClientState.STOPPED);
880
+
881
+ // But VGF state not finalized yet
882
+ simplifiedClient.sendAudio(Buffer.from([1, 2, 3]));
883
+ jest.clearAllMocks();
884
+
885
+ simplifiedClient.stopAbnormally();
886
+
887
+ // Should be blocked completely - no state change, no underlying call
888
+ expect(stateChangeCallback).not.toHaveBeenCalled();
889
+ expect(mockClient.stopAbnormally).not.toHaveBeenCalled();
890
+ });
891
+
892
+ it('should not call underlying client if already in FAILED state', () => {
893
+ // Mock underlying client as failed
894
+ mockClient.getState.mockReturnValue(ClientState.FAILED);
895
+
896
+ simplifiedClient.stopAbnormally();
897
+
898
+ // Should NOT update VGF state or call underlying client
899
+ expect(stateChangeCallback).not.toHaveBeenCalled();
900
+ expect(mockClient.stopAbnormally).not.toHaveBeenCalled();
901
+ });
902
+
903
+ it('should block if client is in STOPPING state (graceful shutdown in progress)', () => {
904
+ // Start recording first
905
+ simplifiedClient.sendAudio(Buffer.from([1, 2, 3]));
906
+
907
+ // Get initial state before attempting stopAbnormally
908
+ const initialState = simplifiedClient.getVGFState();
909
+ const initialStatus = initialState.transcriptionStatus;
910
+
911
+ // Mock underlying client as STOPPING (stopRecording was called)
912
+ mockClient.getState.mockReturnValue(ClientState.STOPPING);
913
+ jest.clearAllMocks();
914
+
915
+ // Try to call stopAbnormally while graceful shutdown in progress
916
+ simplifiedClient.stopAbnormally();
917
+
918
+ // Should be blocked - no state change, no underlying call
919
+ expect(stateChangeCallback).not.toHaveBeenCalled();
920
+ expect(mockClient.stopAbnormally).not.toHaveBeenCalled();
921
+
922
+ // VGF state should remain unchanged (not changed to ABORTED)
923
+ const state = simplifiedClient.getVGFState();
924
+ expect(state.transcriptionStatus).toBe(initialStatus);
925
+ expect(state.transcriptionStatus).not.toBe(TranscriptionStatus.ABORTED);
926
+ });
927
+
928
+ it('should only update VGF state if already finalized but client not stopped', () => {
929
+ // First call - fully stop
930
+ simplifiedClient.stopAbnormally();
931
+ const firstCallCount = stateChangeCallback.mock.calls.length;
932
+
933
+ // Mock underlying client reconnects (edge case)
934
+ mockClient.getState.mockReturnValue(ClientState.READY);
935
+ jest.clearAllMocks();
936
+
937
+ // Second call - VGF already finalized but client not stopped
938
+ simplifiedClient.stopAbnormally();
939
+
940
+ // Should NOT update VGF state (already finalized)
941
+ expect(stateChangeCallback).not.toHaveBeenCalled();
942
+ // But SHOULD call underlying client (not stopped)
943
+ expect(mockClient.stopAbnormally).toHaveBeenCalled();
944
+ });
945
+ });
946
+ });
677
947
  });