@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.
- package/dist/index.js +677 -4683
- package/dist/index.js.map +4 -4
- package/dist/recog-client-sdk.browser.js +506 -800
- package/dist/recog-client-sdk.browser.js.map +4 -4
- package/dist/recognition-client.d.ts +1 -0
- package/dist/recognition-client.d.ts.map +1 -1
- package/dist/recognition-client.types.d.ts +18 -0
- package/dist/recognition-client.types.d.ts.map +1 -1
- package/dist/simplified-vgf-recognition-client.d.ts +19 -0
- package/dist/simplified-vgf-recognition-client.d.ts.map +1 -1
- package/dist/vgf-recognition-state.d.ts +1 -0
- package/dist/vgf-recognition-state.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/recognition-client.spec.ts +116 -1
- package/src/recognition-client.ts +23 -0
- package/src/recognition-client.types.ts +19 -0
- package/src/simplified-vgf-recognition-client.spec.ts +270 -0
- package/src/simplified-vgf-recognition-client.ts +56 -1
- package/src/vgf-recognition-state.ts +2 -1
|
@@ -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;
|
|
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,
|
|
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
|
|
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.
|
|
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
|
|
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
|
});
|