@fonoster/apiserver 0.7.18 → 0.7.23

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.
Files changed (53) hide show
  1. package/dist/core/filesServer.d.ts +6 -2
  2. package/dist/core/filesServer.js +31 -35
  3. package/dist/core/runServices.js +1 -2
  4. package/dist/envs.d.ts +0 -1
  5. package/dist/envs.js +1 -3
  6. package/dist/voice/VoiceClientImpl.d.ts +2 -2
  7. package/dist/voice/VoiceClientImpl.js +8 -8
  8. package/dist/voice/VoiceDispatcher.js +3 -3
  9. package/dist/voice/connectToAri.d.ts +1 -1
  10. package/dist/voice/connectToAri.js +5 -5
  11. package/dist/voice/{ChannelVarNotFoundError.d.ts → errors/ChannelVarNotFoundError.d.ts} +1 -1
  12. package/dist/voice/errors/index.d.ts +2 -0
  13. package/dist/voice/errors/index.js +36 -0
  14. package/dist/voice/handlers/Say.js +2 -2
  15. package/dist/voice/handlers/dial/Dial.js +1 -1
  16. package/dist/voice/integrations/getTtsConfig.d.ts +0 -1
  17. package/dist/voice/integrations/getTtsConfig.js +1 -3
  18. package/dist/voice/makeCreateVoiceClient.d.ts +1 -1
  19. package/dist/voice/makeCreateVoiceClient.js +4 -4
  20. package/dist/voice/stt/AbstractSpeechToText.js +1 -1
  21. package/dist/voice/tts/AbstractTextToSpeech.d.ts +8 -9
  22. package/dist/voice/tts/AbstractTextToSpeech.js +5 -67
  23. package/dist/voice/tts/Azure.d.ts +7 -5
  24. package/dist/voice/tts/Azure.js +9 -12
  25. package/dist/voice/tts/Deepgram.d.ts +7 -3
  26. package/dist/voice/tts/Deepgram.js +4 -44
  27. package/dist/voice/tts/ElevenLabs.d.ts +7 -3
  28. package/dist/voice/tts/ElevenLabs.js +7 -34
  29. package/dist/voice/tts/Google.d.ts +7 -3
  30. package/dist/voice/tts/Google.js +6 -12
  31. package/dist/voice/tts/TextToSpeechFactory.d.ts +3 -4
  32. package/dist/voice/tts/types.d.ts +1 -4
  33. package/dist/voice/types/voice.d.ts +5 -4
  34. package/dist/voice/utils/VoiceServiceClientConstructor.d.ts +3 -0
  35. package/dist/voice/{VoiceServiceClientConst.js → utils/VoiceServiceClientConstructor.js} +3 -3
  36. package/dist/voice/{createExternalMediaConfig.js → utils/createExternalMediaConfig.js} +1 -1
  37. package/dist/voice/utils/index.d.ts +4 -0
  38. package/dist/voice/utils/index.js +38 -0
  39. package/dist/voice/utils/makeGetChannelVar.d.ts +4 -0
  40. package/dist/voice/{makeGetChannelVar.js → utils/makeGetChannelVar.js} +1 -14
  41. package/dist/voice/utils/makeGetChannelVarWithoutThrow.d.ts +4 -0
  42. package/dist/voice/utils/makeGetChannelVarWithoutThrow.js +24 -0
  43. package/package.json +8 -8
  44. package/dist/voice/VoiceServiceClientConst.d.ts +0 -3
  45. package/dist/voice/makeGetChannelVar.d.ts +0 -5
  46. package/dist/voice/tts/computeFilename.d.ts +0 -7
  47. package/dist/voice/tts/computeFilename.js +0 -38
  48. package/dist/voice/tts/flattenObject.d.ts +0 -2
  49. package/dist/voice/tts/flattenObject.js +0 -38
  50. /package/dist/voice/{ChannelVarNotFoundError.js → errors/ChannelVarNotFoundError.js} +0 -0
  51. /package/dist/voice/{MethodNotImplementedError.d.ts → errors/MethodNotImplementedError.d.ts} +0 -0
  52. /package/dist/voice/{MethodNotImplementedError.js → errors/MethodNotImplementedError.js} +0 -0
  53. /package/dist/voice/{createExternalMediaConfig.d.ts → utils/createExternalMediaConfig.d.ts} +0 -0
@@ -1,5 +1,9 @@
1
+ import { Readable } from "stream";
1
2
  declare function filesServer(params: {
2
- pathToFiles: string;
3
3
  port: number;
4
- }): void;
4
+ }): {
5
+ addStream: (id: string, stream: Readable) => void;
6
+ removeStream: (id: string) => void;
7
+ getStream: (id: string) => Readable;
8
+ };
5
9
  export { filesServer };
@@ -4,50 +4,46 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.filesServer = filesServer;
7
- /*
8
- * Copyright (C) 2024 by Fonoster Inc (https://fonoster.com)
9
- * http://github.com/fonoster/fonoster
10
- *
11
- * This file is part of Fonoster
12
- *
13
- * Licensed under the MIT License (the "License");
14
- * you may not use this file except in compliance with
15
- * the License. You may obtain a copy of the License at
16
- *
17
- * https://opensource.org/licenses/MIT
18
- *
19
- * Unless required by applicable law or agreed to in writing, software
20
- * distributed under the License is distributed on an "AS IS" BASIS,
21
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22
- * See the License for the specific language governing permissions and
23
- * limitations under the License.
24
- */
25
- const fs_1 = __importDefault(require("fs"));
26
- const path_1 = require("path");
27
7
  const logger_1 = require("@fonoster/logger");
28
8
  const express_1 = __importDefault(require("express"));
29
9
  const logger = (0, logger_1.getLogger)({ service: "apiserver", filePath: __filename });
30
10
  const CONTENT_TYPE = "audio/L16;rate=16000;channels=1";
31
11
  function filesServer(params) {
32
- const { pathToFiles, port } = params;
12
+ const { port } = params;
33
13
  const app = (0, express_1.default)();
34
- app.get("/sounds/:file", (req, res) => {
35
- const filePath = (0, path_1.join)(pathToFiles, req.params.file);
36
- fs_1.default.access(filePath, fs_1.default.constants.F_OK, (err) => {
37
- if (err) {
38
- res.status(404).send("File not found!");
39
- return;
40
- }
41
- res.setHeader("content-type", CONTENT_TYPE);
42
- const readStream = fs_1.default.createReadStream(filePath);
43
- readStream.on("error", (error) => {
44
- logger.error(`Error reading file: ${error.message}`);
45
- res.status(500).send("Error reading file!");
46
- });
47
- readStream.pipe(res);
14
+ const streamMap = new Map();
15
+ app.get("/sounds/:id", (req, res) => {
16
+ const idWithoutExtension = req.params.id.split(".")[0];
17
+ const stream = streamMap.get(idWithoutExtension);
18
+ if (!stream) {
19
+ res.status(404).send(`Stream not found for id: ${req.params.id}`);
20
+ return;
21
+ }
22
+ res.setHeader("content-type", CONTENT_TYPE);
23
+ stream.on("error", (error) => {
24
+ logger.error(`Error reading file: ${error.message}`);
25
+ res.status(500).send("Error reading file!");
48
26
  });
27
+ stream.on("end", () => {
28
+ res.end();
29
+ });
30
+ stream.on("close", () => {
31
+ res.end();
32
+ });
33
+ stream.pipe(res);
49
34
  });
50
35
  app.listen(port, () => {
51
36
  logger.info(`Files server is running on port ${port}`);
52
37
  });
38
+ return {
39
+ addStream: (id, stream) => {
40
+ streamMap.set(id, stream);
41
+ },
42
+ removeStream: (id) => {
43
+ streamMap.delete(id);
44
+ },
45
+ getStream: (id) => {
46
+ return streamMap.get(id);
47
+ }
48
+ };
53
49
  }
@@ -79,7 +79,7 @@ function runServices() {
79
79
  // Load the remaining services
80
80
  (0, loadServices_1.default)(server, yield services_1.default);
81
81
  // Connecting to Asterisk ARI
82
- yield (0, connectToAri_1.connectToAri)();
82
+ yield (0, connectToAri_1.connectToAri)((0, filesServer_1.filesServer)({ port: envs_1.FILES_SERVER_PORT }));
83
83
  // Additional Call Managers subscriber may be added here to handle call events
84
84
  yield (0, runCallManager_1.createCreateCallSubscriber)({
85
85
  natsUrl: envs_1.NATS_URL,
@@ -87,7 +87,6 @@ function runServices() {
87
87
  ariUsername: envs_1.ASTERISK_ARI_USERNAME,
88
88
  ariPassword: envs_1.ASTERISK_ARI_SECRET
89
89
  });
90
- (0, filesServer_1.filesServer)({ pathToFiles: envs_1.TTS_PATH_TO_FILES, port: envs_1.FILES_SERVER_PORT });
91
90
  server.bindAsync(envs_1.APISERVER_BIND_ADDR, credentials, () => __awaiter(this, void 0, void 0, function* () {
92
91
  healthImpl.setStatus("", common_1.GRPC_SERVING_STATUS);
93
92
  logger.info(`apiserver running at ${envs_1.APISERVER_BIND_ADDR}`);
package/dist/envs.d.ts CHANGED
@@ -41,4 +41,3 @@ export declare const SMTP_HOST: string;
41
41
  export declare const SMTP_PORT: number;
42
42
  export declare const SMTP_SECURE: boolean;
43
43
  export declare const SMTP_SENDER: string;
44
- export declare const TTS_PATH_TO_FILES: string;
package/dist/envs.js CHANGED
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  var _a;
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.TTS_PATH_TO_FILES = exports.SMTP_SENDER = exports.SMTP_SECURE = exports.SMTP_PORT = exports.SMTP_HOST = exports.SMTP_AUTH_USER = exports.SMTP_AUTH_PASS = exports.ROUTR_DEFAULT_PEER_USERNAME = exports.ROUTR_DEFAULT_PEER_PASSWORD = exports.ROUTR_DEFAULT_PEER_NAME = exports.ROUTR_DEFAULT_PEER_AOR = exports.ROUTR_API_ENDPOINT = exports.OWNER_PASSWORD = exports.OWNER_NAME = exports.OWNER_EMAIL = exports.NATS_URL = exports.INTEGRATIONS_FILE = exports.INFLUXDB_USERNAME = exports.INFLUXDB_URL = exports.INFLUXDB_TOKEN = exports.INFLUXDB_PASSWORD = exports.INFLUXDB_ORG = exports.INFLUXDB_BUCKET = exports.IDENTITY_REFRESH_TOKEN_EXPIRES_IN = exports.IDENTITY_PUBLIC_KEY = exports.IDENTITY_PRIVATE_KEY = exports.IDENTITY_ISSUER = exports.IDENTITY_ID_TOKEN_EXPIRES_IN = exports.IDENTITY_AUDIENCE = exports.IDENTITY_ACCESS_TOKEN_EXPIRES_IN = exports.FILES_SERVER_PORT = exports.EMAIL_TEMPLATES_DIR = exports.DEFAULT_NATS_QUEUE_GROUP = exports.CLOAK_ENCRYPTION_KEY = exports.CALLS_TRACK_CALL_SUBJECT = exports.CALLS_CREATE_SUBJECT = exports.ASTERISK_TRUNK = exports.ASTERISK_SYSTEM_DOMAIN = exports.ASTERISK_ARI_USERNAME = exports.ASTERISK_ARI_SECRET = exports.ASTERISK_ARI_PROXY_URL = exports.APP_URL = exports.APISERVER_HOST = exports.APISERVER_BIND_ADDR = void 0;
7
+ exports.SMTP_SENDER = exports.SMTP_SECURE = exports.SMTP_PORT = exports.SMTP_HOST = exports.SMTP_AUTH_USER = exports.SMTP_AUTH_PASS = exports.ROUTR_DEFAULT_PEER_USERNAME = exports.ROUTR_DEFAULT_PEER_PASSWORD = exports.ROUTR_DEFAULT_PEER_NAME = exports.ROUTR_DEFAULT_PEER_AOR = exports.ROUTR_API_ENDPOINT = exports.OWNER_PASSWORD = exports.OWNER_NAME = exports.OWNER_EMAIL = exports.NATS_URL = exports.INTEGRATIONS_FILE = exports.INFLUXDB_USERNAME = exports.INFLUXDB_URL = exports.INFLUXDB_TOKEN = exports.INFLUXDB_PASSWORD = exports.INFLUXDB_ORG = exports.INFLUXDB_BUCKET = exports.IDENTITY_REFRESH_TOKEN_EXPIRES_IN = exports.IDENTITY_PUBLIC_KEY = exports.IDENTITY_PRIVATE_KEY = exports.IDENTITY_ISSUER = exports.IDENTITY_ID_TOKEN_EXPIRES_IN = exports.IDENTITY_AUDIENCE = exports.IDENTITY_ACCESS_TOKEN_EXPIRES_IN = exports.FILES_SERVER_PORT = exports.EMAIL_TEMPLATES_DIR = exports.DEFAULT_NATS_QUEUE_GROUP = exports.CLOAK_ENCRYPTION_KEY = exports.CALLS_TRACK_CALL_SUBJECT = exports.CALLS_CREATE_SUBJECT = exports.ASTERISK_TRUNK = exports.ASTERISK_SYSTEM_DOMAIN = exports.ASTERISK_ARI_USERNAME = exports.ASTERISK_ARI_SECRET = exports.ASTERISK_ARI_PROXY_URL = exports.APP_URL = exports.APISERVER_HOST = exports.APISERVER_BIND_ADDR = void 0;
8
8
  /*
9
9
  * Copyright (C) 2024 by Fonoster Inc (https://fonoster.com)
10
10
  * http://github.com/fonoster/fonoster
@@ -24,7 +24,6 @@ exports.TTS_PATH_TO_FILES = exports.SMTP_SENDER = exports.SMTP_SECURE = exports.
24
24
  * limitations under the License.
25
25
  */
26
26
  const fs_1 = __importDefault(require("fs"));
27
- const os_1 = __importDefault(require("os"));
28
27
  const path_1 = require("path");
29
28
  const common_1 = require("@fonoster/common");
30
29
  const dotenv_1 = __importDefault(require("dotenv"));
@@ -108,4 +107,3 @@ exports.SMTP_HOST = e.SMTP_HOST;
108
107
  exports.SMTP_PORT = e.SMTP_PORT ? parseInt(e.SMTP_PORT) : 587;
109
108
  exports.SMTP_SECURE = ((_a = e.SMTP_SECURE) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === "true";
110
109
  exports.SMTP_SENDER = e.SMTP_SENDER;
111
- exports.TTS_PATH_TO_FILES = e.TTS_PATH_TO_FILES || os_1.default.tmpdir();
@@ -16,17 +16,17 @@ declare class VoiceClientImpl implements VoiceClient {
16
16
  asStream: Stream;
17
17
  ari: Client;
18
18
  bridge: Bridge;
19
+ filesServer: any;
19
20
  constructor(params: {
20
21
  ari: Client;
21
22
  config: VoiceClientConfig;
22
23
  tts: TextToSpeech;
23
24
  stt: SpeechToText;
24
- });
25
+ }, filesServer: any);
25
26
  connect(): Promise<void>;
26
27
  setupAudioSocket(port: number): void;
27
28
  on(type: string, callback: (data: VoiceIn) => void): void;
28
29
  sendResponse(response: VoiceIn): void;
29
- getBridge(): Bridge;
30
30
  getTranscriptionsStream(): Stream;
31
31
  setupExternalMedia(port: number): Promise<void>;
32
32
  synthesize(text: string, options: SayOptions): Promise<string>;
@@ -58,13 +58,13 @@ const logger_1 = require("@fonoster/logger");
58
58
  const streams_1 = require("@fonoster/streams");
59
59
  const grpc = __importStar(require("@grpc/grpc-js"));
60
60
  const pick_port_1 = require("pick-port");
61
- const createExternalMediaConfig_1 = require("./createExternalMediaConfig");
62
61
  const transcribeOnConnection_1 = require("./transcribeOnConnection");
63
62
  const types_1 = require("./types");
64
- const VoiceServiceClientConst_1 = require("./VoiceServiceClientConst");
63
+ const createExternalMediaConfig_1 = require("./utils/createExternalMediaConfig");
64
+ const VoiceServiceClientConstructor_1 = require("./utils/VoiceServiceClientConstructor");
65
65
  const logger = (0, logger_1.getLogger)({ service: "apiserver", filePath: __filename });
66
66
  class VoiceClientImpl {
67
- constructor(params) {
67
+ constructor(params, filesServer) {
68
68
  const { config, tts, stt, ari } = params;
69
69
  this.config = config;
70
70
  this.verbsStream = new stream_1.Stream();
@@ -72,10 +72,11 @@ class VoiceClientImpl {
72
72
  this.tts = tts;
73
73
  this.stt = stt;
74
74
  this.ari = ari;
75
+ this.filesServer = filesServer;
75
76
  }
76
77
  connect() {
77
78
  return __awaiter(this, void 0, void 0, function* () {
78
- this.grpcClient = new VoiceServiceClientConst_1.VoiceServiceClient(this.config.endpoint, grpc.credentials.createInsecure());
79
+ this.grpcClient = new VoiceServiceClientConstructor_1.VoiceServiceClientConstructor(this.config.endpoint, grpc.credentials.createInsecure());
79
80
  const metadata = new grpc.Metadata();
80
81
  metadata.add("accessKeyId", this.config.accessKeyId);
81
82
  metadata.add("token", this.config.sessionToken);
@@ -115,9 +116,6 @@ class VoiceClientImpl {
115
116
  sendResponse(response) {
116
117
  this.voice.write(response);
117
118
  }
118
- getBridge() {
119
- return this.bridge;
120
- }
121
119
  getTranscriptionsStream() {
122
120
  return this.transcriptionsStream;
123
121
  }
@@ -150,7 +148,9 @@ class VoiceClientImpl {
150
148
  }
151
149
  synthesize(text, options) {
152
150
  return __awaiter(this, void 0, void 0, function* () {
153
- return yield this.tts.synthesize(text, options);
151
+ const { ref, stream } = yield this.tts.synthesize(text, options);
152
+ this.filesServer.addStream(ref, stream);
153
+ return ref;
154
154
  });
155
155
  }
156
156
  transcribe() {
@@ -32,8 +32,8 @@ const common_1 = require("@fonoster/common");
32
32
  const logger_1 = require("@fonoster/logger");
33
33
  const handlers_1 = require("./handlers");
34
34
  const Stream_1 = require("./handlers/Stream");
35
- const makeGetChannelVar_1 = require("./makeGetChannelVar");
36
35
  const types_1 = require("./types");
36
+ const makeGetChannelVarWithoutThrow_1 = require("./utils/makeGetChannelVarWithoutThrow");
37
37
  const utils_1 = require("../utils");
38
38
  const logger = (0, logger_1.getLogger)({ service: "apiserver", filePath: __filename });
39
39
  class VoiceDispatcher {
@@ -55,7 +55,7 @@ class VoiceDispatcher {
55
55
  return __awaiter(this, void 0, void 0, function* () {
56
56
  var _a;
57
57
  const { ari, voiceClients, createVoiceClient, isHandledElsewhere } = this;
58
- const getChannelVar = (0, makeGetChannelVar_1.makeGetChannelVarWithoutThrow)(channel);
58
+ const getChannelVar = (0, makeGetChannelVarWithoutThrow_1.makeGetChannelVarWithoutThrow)(channel);
59
59
  const appRef = (_a = (yield getChannelVar(types_1.ChannelVar.APP_REF))) === null || _a === void 0 ? void 0 : _a.value;
60
60
  // This check feels strange but is necessary as ARI calls this event twice
61
61
  if (!appRef) {
@@ -107,7 +107,7 @@ class VoiceDispatcher {
107
107
  isHandledElsewhere(channel) {
108
108
  return __awaiter(this, void 0, void 0, function* () {
109
109
  var _a;
110
- return (((_a = (yield (0, makeGetChannelVar_1.makeGetChannelVarWithoutThrow)(channel)(types_1.ChannelVar.FROM_EXTERNAL_MEDIA))) === null || _a === void 0 ? void 0 : _a.value) === "true");
110
+ return (((_a = (yield (0, makeGetChannelVarWithoutThrow_1.makeGetChannelVarWithoutThrow)(channel)(types_1.ChannelVar.FROM_EXTERNAL_MEDIA))) === null || _a === void 0 ? void 0 : _a.value) === "true");
111
111
  });
112
112
  }
113
113
  }
@@ -1,2 +1,2 @@
1
- declare function connectToAri(): Promise<void>;
1
+ declare function connectToAri(filesServer: any): Promise<void>;
2
2
  export { connectToAri };
@@ -48,7 +48,7 @@ const connection = {
48
48
  timeout: 5000,
49
49
  output: "silent"
50
50
  };
51
- function connectToAri() {
51
+ function connectToAri(filesServer) {
52
52
  return __awaiter(this, void 0, void 0, function* () {
53
53
  logger.info("waiting for asterisk server");
54
54
  const open = yield (0, wait_port_1.default)(connection);
@@ -59,12 +59,12 @@ function connectToAri() {
59
59
  });
60
60
  ari.on(types_1.AriEvent.WEB_SOCKET_MAX_RETRIES, () => {
61
61
  logger.error("max retries reconnecting to asterisk");
62
- attemptReconnection();
62
+ attemptReconnection(filesServer);
63
63
  });
64
64
  logger.info("asterisk is ready");
65
65
  const createContainer = (0, integrations_1.makeCreateContainer)(db_1.prisma, envs_1.INTEGRATIONS_FILE);
66
66
  const nats = yield (0, nats_1.connect)({ servers: envs_1.NATS_URL });
67
- const dispatcher = new VoiceDispatcher_1.VoiceDispatcher(ari, nats, (0, makeCreateVoiceClient_1.makeCreateVoiceClient)(createContainer));
67
+ const dispatcher = new VoiceDispatcher_1.VoiceDispatcher(ari, nats, (0, makeCreateVoiceClient_1.makeCreateVoiceClient)(createContainer, filesServer));
68
68
  dispatcher.start();
69
69
  }
70
70
  else {
@@ -73,9 +73,9 @@ function connectToAri() {
73
73
  }
74
74
  });
75
75
  }
76
- function attemptReconnection() {
76
+ function attemptReconnection(filesServer) {
77
77
  logger.info("attempting to reconnect in 5 seconds...");
78
78
  setTimeout(() => {
79
- connectToAri();
79
+ connectToAri(filesServer);
80
80
  }, 5000); // Reconnect after 5 seconds
81
81
  }
@@ -1,4 +1,4 @@
1
- import { ChannelVar } from "./types";
1
+ import { ChannelVar } from "../types";
2
2
  declare class ChannelVarNotFoundError extends Error {
3
3
  constructor(variable: ChannelVar);
4
4
  }
@@ -0,0 +1,2 @@
1
+ export * from "./ChannelVarNotFoundError";
2
+ export * from "./MethodNotImplementedError";
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ /*
18
+ * Copyright (C) 2024 by Fonoster Inc (https://fonoster.com)
19
+ * http://github.com/fonoster/fonoster
20
+ *
21
+ * This file is part of Fonoster
22
+ *
23
+ * Licensed under the MIT License (the "License");
24
+ * you may not use this file except in compliance with
25
+ * the License. You may obtain a copy of the License at
26
+ *
27
+ * https://opensource.org/licenses/MIT
28
+ *
29
+ * Unless required by applicable law or agreed to in writing, software
30
+ * distributed under the License is distributed on an "AS IS" BASIS,
31
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
32
+ * See the License for the specific language governing permissions and
33
+ * limitations under the License.
34
+ */
35
+ __exportStar(require("./ChannelVarNotFoundError"), exports);
36
+ __exportStar(require("./MethodNotImplementedError"), exports);
@@ -28,10 +28,10 @@ function sayHandler(ari, voiceClient) {
28
28
  const { sessionRef: channelId } = request;
29
29
  sayRequestSchema.parse(request);
30
30
  const playbackRef = request.playbackRef || (0, nanoid_1.nanoid)(10);
31
- const filename = yield voiceClient.synthesize(request.text, request.options ? pb_util_1.struct.decode(request.options) : {});
31
+ const mediaId = yield voiceClient.synthesize(request.text, request.options ? pb_util_1.struct.decode(request.options) : {});
32
32
  yield ari.channels.play({
33
33
  channelId,
34
- media: getMediaUrl(filename),
34
+ media: getMediaUrl(mediaId),
35
35
  playbackId: playbackRef
36
36
  });
37
37
  yield (0, awaitForPlaybackFinished_1.awaitForPlaybackFinished)(ari, playbackRef);
@@ -35,8 +35,8 @@ const handleStasisEnd_1 = require("./handleStasisEnd");
35
35
  const handleStasisStart_1 = require("./handleStasisStart");
36
36
  const envs_1 = require("../../../envs");
37
37
  const utils_1 = require("../../../utils");
38
- const makeGetChannelVar_1 = require("../../makeGetChannelVar");
39
38
  const types_1 = require("../../types");
39
+ const makeGetChannelVar_1 = require("../../utils/makeGetChannelVar");
40
40
  // TODO: Needs request validation
41
41
  function dialHandler(ari, voiceClient) {
42
42
  return (request) => __awaiter(this, void 0, void 0, function* () {
@@ -3,6 +3,5 @@ import { IntegrationConfig } from "./types";
3
3
  declare function getTtsConfig(integrations: IntegrationConfig[], app: Application): {
4
4
  config: Record<string, unknown>;
5
5
  credentials: Record<string, unknown>;
6
- pathToFiles: string;
7
6
  };
8
7
  export { getTtsConfig };
@@ -2,13 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getTtsConfig = getTtsConfig;
4
4
  const findIntegrationsCredentials_1 = require("./findIntegrationsCredentials");
5
- const envs_1 = require("../../envs");
6
5
  function getTtsConfig(integrations, app) {
7
6
  const config = app.textToSpeech.config;
8
7
  const credentials = (0, findIntegrationsCredentials_1.findIntegrationsCredentials)(integrations, app.textToSpeech.productRef);
9
8
  return {
10
9
  config,
11
- credentials,
12
- pathToFiles: envs_1.TTS_PATH_TO_FILES
10
+ credentials
13
11
  };
14
12
  }
@@ -1,7 +1,7 @@
1
1
  import { Channel, Client, StasisStart } from "ari-client";
2
2
  import { CreateContainer } from "./integrations/types";
3
3
  import { VoiceClient } from "./types";
4
- declare function makeCreateVoiceClient(createContainer: CreateContainer): (params: {
4
+ declare function makeCreateVoiceClient(createContainer: CreateContainer, filesServer: any): (params: {
5
5
  ari: Client;
6
6
  event: StasisStart;
7
7
  channel: Channel;
@@ -30,21 +30,21 @@ exports.makeCreateVoiceClient = makeCreateVoiceClient;
30
30
  */
31
31
  const identity_1 = require("@fonoster/identity");
32
32
  const logger_1 = require("@fonoster/logger");
33
- const makeGetChannelVar_1 = require("./makeGetChannelVar");
34
33
  const types_1 = require("./types");
34
+ const makeGetChannelVarWithoutThrow_1 = require("./utils/makeGetChannelVarWithoutThrow");
35
35
  const VoiceClientImpl_1 = require("./VoiceClientImpl");
36
36
  const identityConfig_1 = require("../core/identityConfig");
37
37
  const mapCallDirectionToEnum_1 = require("../events/mapCallDirectionToEnum");
38
38
  const logger = (0, logger_1.getLogger)({ service: "apiserver", filePath: __filename });
39
39
  const createToken = (0, identity_1.createCallAccessToken)(identityConfig_1.identityConfig);
40
40
  // Note: By the time the call arrives here the owner of the app MUST be authenticated
41
- function makeCreateVoiceClient(createContainer) {
41
+ function makeCreateVoiceClient(createContainer, filesServer) {
42
42
  return (params) => __awaiter(this, void 0, void 0, function* () {
43
43
  var _a, _b, _c, _d;
44
44
  const { ari, event, channel } = params;
45
45
  const { id: sessionRef, caller } = event.channel;
46
46
  const { name: callerName, number: callerNumber } = caller;
47
- const getChannelVar = (0, makeGetChannelVar_1.makeGetChannelVarWithoutThrow)(channel);
47
+ const getChannelVar = (0, makeGetChannelVarWithoutThrow_1.makeGetChannelVarWithoutThrow)(channel);
48
48
  // Variables set by Asterisk's dialplan
49
49
  const callDirection = (_a = (yield getChannelVar(types_1.ChannelVar.CALL_DIRECTION))) === null || _a === void 0 ? void 0 : _a.value;
50
50
  const appRef = (_b = (yield getChannelVar(types_1.ChannelVar.APP_REF))) === null || _b === void 0 ? void 0 : _b.value;
@@ -69,6 +69,6 @@ function makeCreateVoiceClient(createContainer) {
69
69
  callerNumber,
70
70
  ingressNumber
71
71
  });
72
- return new VoiceClientImpl_1.VoiceClientImpl({ ari, config, tts, stt });
72
+ return new VoiceClientImpl_1.VoiceClientImpl({ ari, config, tts, stt }, filesServer);
73
73
  });
74
74
  }
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AbstractSpeechToText = void 0;
4
- const MethodNotImplementedError_1 = require("../MethodNotImplementedError");
4
+ const MethodNotImplementedError_1 = require("../errors/MethodNotImplementedError");
5
5
  class AbstractSpeechToText {
6
6
  constructor(config) {
7
7
  this.config = config;
@@ -1,18 +1,17 @@
1
+ import { Readable } from "stream";
1
2
  import * as z from "zod";
2
- import { SynthOptions, TtsConfig } from "./types";
3
- declare abstract class AbstractTextToSpeech<E, T extends TtsConfig = TtsConfig, S extends SynthOptions = SynthOptions> {
3
+ import { SynthOptions } from "./types";
4
+ declare abstract class AbstractTextToSpeech<E, S extends SynthOptions = SynthOptions> {
4
5
  abstract readonly engineName: E;
5
6
  protected abstract OUTPUT_FORMAT: "wav" | "sln16";
6
7
  protected abstract CACHING_FIELDS: string[];
7
- config: T;
8
- constructor(config: T);
9
- protected createFilename(text: string, options: SynthOptions): string;
10
- protected fileExists(filename: string): boolean;
11
- protected getFilenameWithoutExtension(filename: string): string;
12
- protected getFullPathToFile(filename: string): string;
13
- abstract synthesize(text: string, options: S): Promise<string>;
8
+ abstract synthesize(text: string, options: S): Promise<{
9
+ ref: string;
10
+ stream: Readable;
11
+ }>;
14
12
  static getConfigValidationSchema(): z.Schema;
15
13
  static getCredentialsValidationSchema(): z.Schema;
14
+ protected createMediaReference(): string;
16
15
  getName(): E;
17
16
  }
18
17
  export { AbstractTextToSpeech };
@@ -1,80 +1,18 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
2
  Object.defineProperty(exports, "__esModule", { value: true });
26
3
  exports.AbstractTextToSpeech = void 0;
27
- /*
28
- * Copyright (C) 2024 by Fonoster Inc (https://fonoster.com)
29
- * http://github.com/fonoster/fonoster
30
- *
31
- * This file is part of Fonoster
32
- *
33
- * Licensed under the MIT License (the "License");
34
- * you may not use this file except in compliance with
35
- * the License. You may obtain a copy of the License at
36
- *
37
- * https://opensource.org/licenses/MIT
38
- *
39
- * Unless required by applicable law or agreed to in writing, software
40
- * distributed under the License is distributed on an "AS IS" BASIS,
41
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
42
- * See the License for the specific language governing permissions and
43
- * limitations under the License.
44
- */
45
- const fs = __importStar(require("fs"));
46
- const logger_1 = require("@fonoster/logger");
47
- const computeFilename_1 = require("./computeFilename");
48
- const MethodNotImplementedError_1 = require("../MethodNotImplementedError");
49
- const logger = (0, logger_1.getLogger)({ service: "apiserver", filePath: __filename });
4
+ const uuid_1 = require("uuid");
5
+ const MethodNotImplementedError_1 = require("../errors/MethodNotImplementedError");
50
6
  class AbstractTextToSpeech {
51
- constructor(config) {
52
- logger.silly("tts pathToFiles", { engine: config.pathToFiles });
53
- this.config = config;
54
- }
55
- createFilename(text, options) {
56
- return (0, computeFilename_1.computeFilename)({
57
- text,
58
- options,
59
- cachingFields: this.CACHING_FIELDS,
60
- format: this.OUTPUT_FORMAT
61
- });
62
- }
63
- fileExists(filename) {
64
- return fs.existsSync(filename);
65
- }
66
- getFilenameWithoutExtension(filename) {
67
- return filename.replace(`.${this.OUTPUT_FORMAT}`, "");
68
- }
69
- getFullPathToFile(filename) {
70
- return `${this.config.pathToFiles}/${filename}`;
71
- }
72
7
  static getConfigValidationSchema() {
73
8
  throw new MethodNotImplementedError_1.MethodNotImplementedError();
74
9
  }
75
10
  static getCredentialsValidationSchema() {
76
11
  throw new MethodNotImplementedError_1.MethodNotImplementedError();
77
12
  }
13
+ createMediaReference() {
14
+ return (0, uuid_1.v4)();
15
+ }
78
16
  getName() {
79
17
  return this.engineName;
80
18
  }
@@ -1,9 +1,10 @@
1
+ import { Readable } from "stream";
1
2
  import * as sdk from "microsoft-cognitiveservices-speech-sdk";
2
3
  import * as z from "zod";
3
4
  import { AbstractTextToSpeech } from "./AbstractTextToSpeech";
4
- import { SynthOptions, TtsConfig } from "./types";
5
+ import { SynthOptions } from "./types";
5
6
  declare const ENGINE_NAME = "tts.azure";
6
- type AzureTTSConfig = TtsConfig & {
7
+ type AzureTTSConfig = {
7
8
  [key: string]: Record<string, string>;
8
9
  credentials: {
9
10
  subscriptionKey: string;
@@ -12,15 +13,16 @@ type AzureTTSConfig = TtsConfig & {
12
13
  };
13
14
  declare class Azure extends AbstractTextToSpeech<typeof ENGINE_NAME> {
14
15
  config: AzureTTSConfig;
15
- pathToFiles: string;
16
16
  readonly engineName = "tts.azure";
17
17
  protected readonly OUTPUT_FORMAT = "sln16";
18
18
  protected readonly CACHING_FIELDS: string[];
19
19
  constructor(config: AzureTTSConfig);
20
- synthesize(text: string, options: SynthOptions): Promise<string>;
20
+ synthesize(text: string, options: SynthOptions): Promise<{
21
+ ref: string;
22
+ stream: Readable;
23
+ }>;
21
24
  doSynthesize(params: {
22
25
  text: string;
23
- filename: string;
24
26
  speechConfig: sdk.SpeechConfig;
25
27
  isSSML?: boolean;
26
28
  }): Promise<unknown>;
@@ -51,6 +51,7 @@ exports.ENGINE_NAME = exports.Azure = void 0;
51
51
  * See the License for the specific language governing permissions and
52
52
  * limitations under the License.
53
53
  */
54
+ const stream_1 = require("stream");
54
55
  const common_1 = require("@fonoster/common");
55
56
  const logger_1 = require("@fonoster/logger");
56
57
  const sdk = __importStar(require("microsoft-cognitiveservices-speech-sdk"));
@@ -60,9 +61,10 @@ const isSsml_1 = require("./isSsml");
60
61
  const ENGINE_NAME = "tts.azure";
61
62
  exports.ENGINE_NAME = ENGINE_NAME;
62
63
  const logger = (0, logger_1.getLogger)({ service: "apiserver", filePath: __filename });
64
+ // XXX: Must re-implement to provide an id an a Readable stream
63
65
  class Azure extends AbstractTextToSpeech_1.AbstractTextToSpeech {
64
66
  constructor(config) {
65
- super(config);
67
+ super();
66
68
  this.engineName = ENGINE_NAME;
67
69
  this.OUTPUT_FORMAT = "sln16";
68
70
  this.CACHING_FIELDS = ["voice"];
@@ -71,11 +73,6 @@ class Azure extends AbstractTextToSpeech_1.AbstractTextToSpeech {
71
73
  synthesize(text, options) {
72
74
  return __awaiter(this, void 0, void 0, function* () {
73
75
  logger.verbose(`synthesize [input: ${text}, isSsml=${(0, isSsml_1.isSsml)(text)} options: ${JSON.stringify(options)}]`);
74
- const effectiveOptions = Object.assign(Object.assign({}, this.config), options);
75
- const filename = this.createFilename(text, effectiveOptions);
76
- if (this.fileExists(this.getFullPathToFile(filename))) {
77
- return this.getFilenameWithoutExtension(filename);
78
- }
79
76
  const { subscriptionKey, serviceRegion } = this.config.credentials;
80
77
  const speechConfig = sdk.SpeechConfig.fromSubscription(subscriptionKey, serviceRegion);
81
78
  speechConfig.speechSynthesisVoiceName = options.voice;
@@ -83,24 +80,24 @@ class Azure extends AbstractTextToSpeech_1.AbstractTextToSpeech {
83
80
  sdk.SpeechSynthesisOutputFormat.Riff16Khz16BitMonoPcm;
84
81
  yield this.doSynthesize({
85
82
  text,
86
- filename: this.getFullPathToFile(filename),
87
83
  speechConfig,
88
84
  isSSML: (0, isSsml_1.isSsml)(text)
89
85
  });
90
- return this.getFilenameWithoutExtension(filename);
86
+ const ref = this.createMediaReference();
87
+ // TODO: Fix this placeholder
88
+ return { ref, stream: new stream_1.Readable() };
91
89
  });
92
90
  }
93
91
  doSynthesize(params) {
94
92
  return __awaiter(this, void 0, void 0, function* () {
95
- const { text, filename, speechConfig } = params;
96
- const audioConfig = sdk.AudioConfig.fromAudioFileOutput(filename);
97
- const synthesizer = new sdk.SpeechSynthesizer(speechConfig, audioConfig);
93
+ const { text, speechConfig } = params;
94
+ const synthesizer = new sdk.SpeechSynthesizer(speechConfig);
98
95
  // FIXME: Let's turn this into constants
99
96
  const func = params.isSSML ? "speakSsmlAsync" : "speakTextAsync";
100
97
  return new Promise((resolve, reject) => {
101
98
  synthesizer[func](text, function (result) {
102
99
  if (result.reason === sdk.ResultReason.SynthesizingAudioCompleted) {
103
- resolve(filename);
100
+ resolve("");
104
101
  }
105
102
  else {
106
103
  reject(new Error("speech synthesis canceled: " + result.errorDetails));