@fonoster/voice 0.6.6 → 0.7.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.
@@ -1,5 +1,5 @@
1
- import { DialOptions, GatherOptions, GatherResponse, MuteOptions, PlayOptions, PlayResponse, PlaybackControlAction, RecordOptions, RecordResponse, SayOptions, SayResponse, StreamOptions, VerbResponse, VoiceRequest, VoiceSessionStreamServer } from "@fonoster/common";
2
- import { DialStatusStream, Stream } from "./verbs";
1
+ import { DialOptions, GatherOptions, GatherResponse, MuteOptions, PlayOptions, PlayResponse, PlaybackControlAction, RecordOptions, RecordResponse, SayOptions, SayResponse, StreamGatherOptions, StreamOptions, VerbResponse, VoiceRequest, VoiceSessionStreamServer } from "@fonoster/common";
2
+ import { DialStatusStream, Stream, StreamGatherStream } from "./verbs";
3
3
  /**
4
4
  * @classdesc Use the VoiceResponse object, to construct advance Interactive
5
5
  * Voice Response (IVR) applications.
@@ -11,11 +11,10 @@ import { DialStatusStream, Stream } from "./verbs";
11
11
  *
12
12
  * async function handler (request, response) {
13
13
  * await response.answer();
14
- * await response.play("sound:hello-world");
14
+ * await response.play("https://soundsserver:9000/sounds/hello-world.wav");
15
15
  * }
16
16
  *
17
- * const voiceServer = new VoiceServer({base: '/voiceapp'})
18
- * voiceServer.listen(handler, { port: 3000 })
17
+ * new VoiceServer().listen(handler, { port: 3000 })
19
18
  */
20
19
  declare class VoiceResponse {
21
20
  voice: VoiceSessionStreamServer;
@@ -164,7 +163,6 @@ declare class VoiceResponse {
164
163
  * @param {StreamOptions} options - Options to control the stream operation
165
164
  * @param {StreamDirection} options.direction - The direction to stream the audio (IN, OUT, BOTH). Default is BOTH
166
165
  * @param {StreamAudioFormat} options.format - The audio format to stream (WAV). Default is WAV
167
- * @param {boolean} options.enableVad - Enable voice activity detection. Default is false
168
166
  * @return {Promise<Stream>} The stream object
169
167
  * @example
170
168
  *
@@ -172,9 +170,7 @@ declare class VoiceResponse {
172
170
  * await response.answer();
173
171
  *
174
172
  * const stream = await response.stream({
175
- * direction: StreamDirection.BOTH,
176
- * format: StreamAudioFormat.WAV,
177
- * enableVad: true
173
+ * direction: StreamDirection.BOTH
178
174
  * });
179
175
  *
180
176
  * stream.onPayload((payload) => {
@@ -185,7 +181,25 @@ declare class VoiceResponse {
185
181
  * // stream.write({ type: StreamMessageType.AUDIO_OUT, payload: "\x00\x01\x02" });
186
182
  * }
187
183
  */
188
- stream(options: StreamOptions): Promise<Stream>;
184
+ stream(options?: StreamOptions): Promise<Stream>;
185
+ /**
186
+ * Starts a server-side stream gather operation which sends transcription data to the voice server.
187
+ *
188
+ * @param {StreamGatherOptions} options - Options to control the stream gather operation
189
+ * @param {StreamGatherSource} options.source - The source to gather data from (DTMF, SPEECH, SPEECH_AND_DTMF). Default is SPEECH
190
+ * @return {Promise<StreamGatherStream>} The stream gather object
191
+ * @see Gather
192
+ * @example
193
+ *
194
+ * async function handler (request, response) {
195
+ * await response.answer();
196
+ * const sGather = await response.streamGather({ source: StreamGatherSource.SPEECH });
197
+ * sGather.onPayload((payload) => {
198
+ * console.log("Payload: %s", payload);
199
+ * });
200
+ * }
201
+ */
202
+ sgather(options: StreamGatherOptions): Promise<StreamGatherStream>;
189
203
  /**
190
204
  * Mutes a call.
191
205
  *
@@ -42,11 +42,10 @@ const verbs_1 = require("./verbs");
42
42
  *
43
43
  * async function handler (request, response) {
44
44
  * await response.answer();
45
- * await response.play("sound:hello-world");
45
+ * await response.play("https://soundsserver:9000/sounds/hello-world.wav");
46
46
  * }
47
47
  *
48
- * const voiceServer = new VoiceServer({base: '/voiceapp'})
49
- * voiceServer.listen(handler, { port: 3000 })
48
+ * new VoiceServer().listen(handler, { port: 3000 })
50
49
  */
51
50
  class VoiceResponse {
52
51
  /**
@@ -258,7 +257,6 @@ class VoiceResponse {
258
257
  * @param {StreamOptions} options - Options to control the stream operation
259
258
  * @param {StreamDirection} options.direction - The direction to stream the audio (IN, OUT, BOTH). Default is BOTH
260
259
  * @param {StreamAudioFormat} options.format - The audio format to stream (WAV). Default is WAV
261
- * @param {boolean} options.enableVad - Enable voice activity detection. Default is false
262
260
  * @return {Promise<Stream>} The stream object
263
261
  * @example
264
262
  *
@@ -266,9 +264,7 @@ class VoiceResponse {
266
264
  * await response.answer();
267
265
  *
268
266
  * const stream = await response.stream({
269
- * direction: StreamDirection.BOTH,
270
- * format: StreamAudioFormat.WAV,
271
- * enableVad: true
267
+ * direction: StreamDirection.BOTH
272
268
  * });
273
269
  *
274
270
  * stream.onPayload((payload) => {
@@ -304,6 +300,42 @@ class VoiceResponse {
304
300
  return stream;
305
301
  });
306
302
  }
303
+ /**
304
+ * Starts a server-side stream gather operation which sends transcription data to the voice server.
305
+ *
306
+ * @param {StreamGatherOptions} options - Options to control the stream gather operation
307
+ * @param {StreamGatherSource} options.source - The source to gather data from (DTMF, SPEECH, SPEECH_AND_DTMF). Default is SPEECH
308
+ * @return {Promise<StreamGatherStream>} The stream gather object
309
+ * @see Gather
310
+ * @example
311
+ *
312
+ * async function handler (request, response) {
313
+ * await response.answer();
314
+ * const sGather = await response.streamGather({ source: StreamGatherSource.SPEECH });
315
+ * sGather.onPayload((payload) => {
316
+ * console.log("Payload: %s", payload);
317
+ * });
318
+ * }
319
+ */
320
+ sgather(options) {
321
+ return __awaiter(this, void 0, void 0, function* () {
322
+ const stream = new verbs_1.StreamGatherStream();
323
+ const startStreamGather = new verbs_1.StartStreamGather(this.request, this.voice);
324
+ const stopStreamGather = new verbs_1.StopStreamGather(this.request, this.voice);
325
+ yield startStreamGather.run(Object.assign({ sessionRef: this.request.sessionRef }, options));
326
+ this.voice.on(common_1.StreamEvent.DATA, (result) => {
327
+ if (result.streamGatherPayload) {
328
+ stream.emit("data", result.streamGatherPayload);
329
+ }
330
+ });
331
+ stream.cleanup(() => {
332
+ stopStreamGather.run({
333
+ sessionRef: this.request.sessionRef
334
+ });
335
+ });
336
+ return stream;
337
+ });
338
+ }
307
339
  /**
308
340
  * Mutes a call.
309
341
  *
@@ -54,7 +54,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
54
54
  * limitations under the License.
55
55
  */
56
56
  const common_1 = require("@fonoster/common");
57
- const identity_1 = require("@fonoster/identity");
58
57
  const logger_1 = require("@fonoster/logger");
59
58
  const grpc = __importStar(require("@grpc/grpc-js"));
60
59
  const deepmerge_1 = __importDefault(require("deepmerge"));
@@ -72,20 +71,20 @@ class VoiceServer {
72
71
  try {
73
72
  const healthImpl = new grpc_health_check_1.HealthImplementation(common_1.statusMap);
74
73
  const credentials = yield (0, common_1.getServerCredentials)({});
75
- let server;
76
- if (this.config.skipIdentity) {
77
- server = new grpc.Server();
78
- }
79
- else {
80
- // Get the public key from the identity service
81
- const response = yield (0, identity_1.getPublicKeyClient)(this.config.identityAddress);
82
- const authorization = (0, identity_1.createAuthInterceptor)(response.publicKey, [
83
- "/grpc.health.v1.Health/Check"
84
- ]);
85
- server = new grpc.Server({
86
- interceptors: [authorization]
87
- });
88
- }
74
+ const server = new grpc.Server();
75
+ // FIXME: Must re-implement without dependency on identity package
76
+ // if (this.config.skipIdentity) {
77
+ // server = new grpc.Server();
78
+ // } else {
79
+ // // Get the public key from the identity service
80
+ // const response = await getPublicKeyClient(this.config.identityAddress);
81
+ // const authorization = createAuthInterceptor(response.publicKey, [
82
+ // "/grpc.health.v1.Health/Check"
83
+ // ]);
84
+ // server = new grpc.Server({
85
+ // interceptors: [authorization]
86
+ // });
87
+ // }
89
88
  server.addService(serviceDefinition_1.serviceDefinition, {
90
89
  createSession: (0, createSession_1.createSession)(handler)
91
90
  });
@@ -29,16 +29,25 @@ exports.createSession = createSession;
29
29
  * limitations under the License.
30
30
  */
31
31
  const common_1 = require("@fonoster/common");
32
+ const logger_1 = require("@fonoster/logger");
32
33
  const VoiceResponse_1 = require("./VoiceResponse");
34
+ const logger = (0, logger_1.getLogger)({ service: "voice", filePath: __filename });
33
35
  function createSession(handler) {
34
36
  return (voice) => new Promise((resolve) => {
35
- voice.on(common_1.StreamEvent.DATA, (params) => __awaiter(this, void 0, void 0, function* () {
37
+ let sessionRef;
38
+ voice.once(common_1.StreamEvent.DATA, (params) => __awaiter(this, void 0, void 0, function* () {
36
39
  const { request } = params;
37
40
  if (params.request) {
41
+ sessionRef = params.request.sessionRef;
38
42
  const response = new VoiceResponse_1.VoiceResponse(request, voice);
39
43
  yield handler(request, response);
40
44
  resolve();
41
45
  }
42
46
  }));
47
+ voice.once(common_1.StreamEvent.END, () => {
48
+ logger.verbose("session ended", { sessionRef });
49
+ voice.end();
50
+ resolve();
51
+ });
43
52
  });
44
53
  }
package/dist/demo.js CHANGED
@@ -34,12 +34,11 @@ const common_1 = require("@fonoster/common");
34
34
  const logger_1 = require("@fonoster/logger");
35
35
  const VoiceServer_1 = __importDefault(require("./VoiceServer"));
36
36
  const logger = (0, logger_1.getLogger)({ service: "voice", filePath: __filename });
37
- const config = {
38
- // Only do this for testing
39
- skipIdentity: true
40
- };
41
- new VoiceServer_1.default(config).listen((req, res) => __awaiter(void 0, void 0, void 0, function* () {
42
- logger.verbose("voice request", JSON.stringify(req, null, 2));
37
+ const skipIdentity = process.env.NODE_ENV === "dev";
38
+ // Only skip identity for local development
39
+ new VoiceServer_1.default({ skipIdentity }).listen((req, res) => __awaiter(void 0, void 0, void 0, function* () {
40
+ const { ingressNumber, sessionRef, appRef } = req;
41
+ logger.verbose("voice request", { ingressNumber, sessionRef, appRef });
43
42
  yield res.answer();
44
43
  yield res.say("Hi there! What's your name?");
45
44
  const { speech: name } = yield res.gather({
package/dist/index.d.ts CHANGED
@@ -2,5 +2,6 @@
2
2
  import { AzureVoice, AzureVoiceDetails, GoogleVoice, GoogleVoiceDetails } from "@fonoster/common";
3
3
  import VoiceServer from "./VoiceServer";
4
4
  export default VoiceServer;
5
+ export * from "./VoiceResponse";
5
6
  export * from "./types";
6
7
  export { GoogleVoice, GoogleVoiceDetails, AzureVoice, AzureVoiceDetails };
package/dist/index.js CHANGED
@@ -44,4 +44,5 @@ Object.defineProperty(exports, "GoogleVoice", { enumerable: true, get: function
44
44
  Object.defineProperty(exports, "GoogleVoiceDetails", { enumerable: true, get: function () { return common_1.GoogleVoiceDetails; } });
45
45
  const VoiceServer_1 = __importDefault(require("./VoiceServer"));
46
46
  exports.default = VoiceServer_1.default;
47
+ __exportStar(require("./VoiceResponse"), exports);
47
48
  __exportStar(require("./types"), exports);
@@ -26,9 +26,8 @@ const Verb_1 = require("./Verb");
26
26
  class StartStream extends Verb_1.Verb {
27
27
  getValidationSchema() {
28
28
  return zod_1.z.object({
29
- direction: zod_1.z.nativeEnum(common_1.StreamDirection),
30
- format: zod_1.z.nativeEnum(common_1.StreamAudioFormat),
31
- enableVad: zod_1.z.boolean()
29
+ direction: zod_1.z.nativeEnum(common_1.StreamDirection).optional().nullable(),
30
+ format: zod_1.z.nativeEnum(common_1.StreamAudioFormat).optional().nullable()
32
31
  });
33
32
  }
34
33
  }
@@ -0,0 +1,19 @@
1
+ import { Stream as StreamObj } from "stream";
2
+ import { StartStreamGatherRequest, StreamGatherPayload, VerbRequest } from "@fonoster/common";
3
+ import { z } from "zod";
4
+ import { Verb } from "./Verb";
5
+ declare class StartStreamGather extends Verb<StartStreamGatherRequest> {
6
+ getValidationSchema(): z.Schema;
7
+ }
8
+ declare class StopStreamGather extends Verb<VerbRequest> {
9
+ getValidationSchema(): z.Schema;
10
+ }
11
+ declare class StreamGatherStream {
12
+ stream: StreamObj;
13
+ constructor();
14
+ close(): void;
15
+ onPayload(callback: (payload: StreamGatherPayload) => void): void;
16
+ emit(event: "data", payload: StreamGatherPayload): void;
17
+ cleanup(callback: () => void): void;
18
+ }
19
+ export { StartStreamGather, StopStreamGather, StreamGatherStream };
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StreamGatherStream = exports.StopStreamGather = exports.StartStreamGather = void 0;
4
+ /*
5
+ * Copyright (C) 2024 by Fonoster Inc (https://fonoster.com)
6
+ * http://github.com/fonoster/fonoster
7
+ *
8
+ * This file is part of Fonoster
9
+ *
10
+ * Licensed under the MIT License (the "License");
11
+ * you may not use this file except in compliance with
12
+ * the License. You may obtain a copy of the License at
13
+ *
14
+ * https://opensource.org/licenses/MIT
15
+ *
16
+ * Unless required by applicable law or agreed to in writing, software
17
+ * distributed under the License is distributed on an "AS IS" BASIS,
18
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
+ * See the License for the specific language governing permissions and
20
+ * limitations under the License.
21
+ */
22
+ const stream_1 = require("stream");
23
+ const common_1 = require("@fonoster/common");
24
+ const zod_1 = require("zod");
25
+ const Verb_1 = require("./Verb");
26
+ class StartStreamGather extends Verb_1.Verb {
27
+ getValidationSchema() {
28
+ return zod_1.z.object({
29
+ source: zod_1.z.optional(zod_1.z.nativeEnum(common_1.StreamGatherSource))
30
+ });
31
+ }
32
+ }
33
+ exports.StartStreamGather = StartStreamGather;
34
+ class StopStreamGather extends Verb_1.Verb {
35
+ getValidationSchema() {
36
+ return zod_1.z.object({
37
+ sessionRef: zod_1.z.string()
38
+ });
39
+ }
40
+ }
41
+ exports.StopStreamGather = StopStreamGather;
42
+ class StreamGatherStream {
43
+ constructor() {
44
+ this.stream = new stream_1.Stream();
45
+ }
46
+ close() {
47
+ this.stream.removeAllListeners();
48
+ }
49
+ // TODO: We should hide this method from the public API
50
+ // Private API
51
+ onPayload(callback) {
52
+ this.stream.on("data", (payload) => {
53
+ callback(payload);
54
+ });
55
+ }
56
+ // Private API
57
+ emit(event, payload) {
58
+ this.stream.emit(event, payload);
59
+ }
60
+ // Private API
61
+ cleanup(callback) {
62
+ this.stream.on("close", callback);
63
+ }
64
+ }
65
+ exports.StreamGatherStream = StreamGatherStream;
@@ -30,6 +30,7 @@ exports.Verb = void 0;
30
30
  */
31
31
  const common_1 = require("@fonoster/common");
32
32
  const logger_1 = require("@fonoster/logger");
33
+ const getExpectedContent_1 = require("./getExpectedContent");
33
34
  const validateRequest_1 = require("./validateRequest");
34
35
  const logger = (0, logger_1.getLogger)({ service: "voice", filePath: __filename });
35
36
  class Verb {
@@ -52,6 +53,10 @@ class Verb {
52
53
  [`${(0, common_1.toCamelCase)(this.constructor.name)}Request`]: fullRequest
53
54
  });
54
55
  const dataListener = (result) => {
56
+ const expectedContent = (0, getExpectedContent_1.getExpectedContent)(this.constructor.name);
57
+ if (expectedContent !== result.content) {
58
+ return;
59
+ }
55
60
  logger.verbose(`received ${this.constructor.name} response`, {
56
61
  sessionRef
57
62
  });
@@ -0,0 +1,2 @@
1
+ declare function getExpectedContent(name: string): string;
2
+ export { getExpectedContent };
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getExpectedContent = getExpectedContent;
4
+ /*
5
+ * Copyright (C) 2024 by Fonoster Inc (https://fonoster.com)
6
+ * http://github.com/fonoster/fonoster
7
+ *
8
+ * This file is part of Fonoster
9
+ *
10
+ * Licensed under the MIT License (the "License");
11
+ * you may not use this file except in compliance with
12
+ * the License. You may obtain a copy of the License at
13
+ *
14
+ * https://opensource.org/licenses/MIT
15
+ *
16
+ * Unless required by applicable law or agreed to in writing, software
17
+ * distributed under the License is distributed on an "AS IS" BASIS,
18
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
+ * See the License for the specific language governing permissions and
20
+ * limitations under the License.
21
+ */
22
+ const common_1 = require("@fonoster/common");
23
+ function getExpectedContent(name) {
24
+ switch (name) {
25
+ case "StartStreamGather":
26
+ return "startStreamGatherResponse";
27
+ case "StartStream":
28
+ return "startStreamResponse";
29
+ default:
30
+ return `${(0, common_1.toCamelCase)(name)}Response`;
31
+ }
32
+ }
@@ -9,6 +9,7 @@ export * from "./PlaybackControl";
9
9
  export * from "./Record";
10
10
  export * from "./Say";
11
11
  export * from "./Stream";
12
+ export * from "./StreamGather";
12
13
  export * from "./Unmute";
13
14
  export * from "./validateRequest";
14
15
  export * from "./Verb";
@@ -43,6 +43,7 @@ __exportStar(require("./PlaybackControl"), exports);
43
43
  __exportStar(require("./Record"), exports);
44
44
  __exportStar(require("./Say"), exports);
45
45
  __exportStar(require("./Stream"), exports);
46
+ __exportStar(require("./StreamGather"), exports);
46
47
  __exportStar(require("./Unmute"), exports);
47
48
  __exportStar(require("./validateRequest"), exports);
48
49
  __exportStar(require("./Verb"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fonoster/voice",
3
- "version": "0.6.6",
3
+ "version": "0.7.2",
4
4
  "description": "Voice Server for Fonoster",
5
5
  "author": "Pedro Sanders <psanders@fonoster.com>",
6
6
  "homepage": "https://github.com/fonoster/fonoster#readme",
@@ -20,9 +20,8 @@
20
20
  "fonoster": "./dist/index.js"
21
21
  },
22
22
  "dependencies": {
23
- "@fonoster/common": "^0.6.6",
24
- "@fonoster/identity": "^0.6.6",
25
- "@fonoster/logger": "^0.6.2",
23
+ "@fonoster/common": "^0.7.2",
24
+ "@fonoster/logger": "^0.7.2",
26
25
  "@grpc/grpc-js": "~1.10.6",
27
26
  "deepmerge": "^4.3.1",
28
27
  "grpc-health-check": "^2.0.2",
@@ -43,5 +42,5 @@
43
42
  "bugs": {
44
43
  "url": "https://github.com/fonoster/fonoster/issues"
45
44
  },
46
- "gitHead": "258137711c4897a4bda49de369db1722da17ec3f"
45
+ "gitHead": "6d858d9920132dfef0dba8965e5005f44de61563"
47
46
  }