@fonoster/apiserver 0.6.6 → 0.7.0

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 (105) hide show
  1. package/dist/applications/createGetFnUtil.d.ts +1 -1
  2. package/dist/applications/getApplication.js +1 -3
  3. package/dist/applications/types.d.ts +1 -1
  4. package/dist/applications/utils/applicationWithEncodedStruct.js +20 -1
  5. package/dist/applications/utils/convertToApplicationData.js +1 -1
  6. package/dist/applications/utils/getApplicationValidationSchema.d.ts +3 -3
  7. package/dist/applications/utils/getApplicationValidationSchema.js +6 -3
  8. package/dist/applications/utils/prepareForValidation.js +1 -1
  9. package/dist/calls/ListCallsRequestSchema.d.ts +1 -4
  10. package/dist/calls/ListCallsRequestSchema.js +0 -4
  11. package/dist/calls/buildService.d.ts +2 -2
  12. package/dist/calls/buildService.js +6 -5
  13. package/dist/calls/createCall.d.ts +6 -4
  14. package/dist/calls/createCall.js +18 -8
  15. package/dist/calls/createFetchCalls.js +3 -7
  16. package/dist/calls/createFetchSingleCall.js +9 -2
  17. package/dist/calls/{trackCall.d.ts → makeTrackCall.d.ts} +3 -3
  18. package/dist/calls/makeTrackCall.js +67 -0
  19. package/dist/calls/runCallManager.js +12 -13
  20. package/dist/calls/types.d.ts +8 -42
  21. package/dist/calls/types.js +5 -38
  22. package/dist/core/seed.js +20 -0
  23. package/dist/core/services.d.ts +2 -2
  24. package/dist/envs.d.ts +0 -2
  25. package/dist/envs.js +1 -3
  26. package/dist/events/createInfluxDbPub.js +2 -1
  27. package/dist/events/mapCallDirectionToEnum.d.ts +3 -0
  28. package/dist/events/mapCallDirectionToEnum.js +37 -0
  29. package/dist/events/nats.js +5 -6
  30. package/dist/events/transformEvent.d.ts +2 -0
  31. package/dist/events/transformEvent.js +72 -0
  32. package/dist/index.js +4 -5
  33. package/dist/utils/index.d.ts +2 -0
  34. package/dist/utils/index.js +2 -0
  35. package/dist/utils/makeHandleDialEventsWithNats.d.ts +5 -0
  36. package/dist/{voice/handlers/StasisEnd.js → utils/makeHandleDialEventsWithNats.js} +10 -15
  37. package/dist/utils/makeHandleDialEventsWithVoiceClient.d.ts +5 -0
  38. package/dist/{voice/handlers/dial/handleDialEvents.js → utils/makeHandleDialEventsWithVoiceClient.js} +6 -17
  39. package/dist/utils/mapDialStatus.d.ts +3 -0
  40. package/dist/utils/mapDialStatus.js +38 -0
  41. package/dist/voice/VoiceClientImpl.d.ts +10 -1
  42. package/dist/voice/VoiceClientImpl.js +47 -7
  43. package/dist/voice/VoiceDispatcher.d.ts +6 -2
  44. package/dist/voice/VoiceDispatcher.js +50 -37
  45. package/dist/voice/connectToAri.js +3 -1
  46. package/dist/voice/createExternalMediaConfig.d.ts +3 -0
  47. package/dist/voice/createExternalMediaConfig.js +4 -1
  48. package/dist/voice/handlers/Answer.js +1 -1
  49. package/dist/voice/handlers/Hangup.js +1 -1
  50. package/dist/voice/handlers/Mute.js +1 -1
  51. package/dist/voice/handlers/Play.js +2 -2
  52. package/dist/voice/handlers/PlayDtmf.js +1 -1
  53. package/dist/voice/handlers/PlaybackControl.js +1 -1
  54. package/dist/voice/handlers/Record.js +2 -2
  55. package/dist/voice/handlers/Say.js +2 -2
  56. package/dist/voice/handlers/StreamGather.d.ts +3 -0
  57. package/dist/voice/handlers/StreamGather.js +66 -0
  58. package/dist/voice/handlers/Unmute.js +1 -1
  59. package/dist/voice/handlers/dial/Dial.js +10 -6
  60. package/dist/voice/handlers/gather/Gather.js +5 -4
  61. package/dist/voice/handlers/index.d.ts +12 -0
  62. package/dist/voice/handlers/index.js +46 -0
  63. package/dist/voice/handlers/{awaitForPlaybackFinished.js → utils/awaitForPlaybackFinished.js} +1 -1
  64. package/dist/voice/handlers/{awaitForRecordingFinished.js → utils/awaitForRecordingFinished.js} +1 -1
  65. package/dist/voice/handlers/utils/index.d.ts +3 -0
  66. package/dist/voice/handlers/utils/index.js +37 -0
  67. package/dist/voice/handlers/utils/isDtmf.d.ts +2 -0
  68. package/dist/voice/handlers/utils/isDtmf.js +24 -0
  69. package/dist/voice/integrations/findIntegrationsCredentials.d.ts +1 -1
  70. package/dist/voice/integrations/findIntegrationsCredentials.js +2 -1
  71. package/dist/voice/integrations/getSttConfig.d.ts +4 -2
  72. package/dist/voice/integrations/getSttConfig.js +4 -1
  73. package/dist/voice/integrations/getTtsConfig.d.ts +2 -1
  74. package/dist/voice/integrations/getTtsConfig.js +5 -1
  75. package/dist/voice/integrations/makeCreateContainer.js +1 -1
  76. package/dist/voice/integrations/types.d.ts +1 -1
  77. package/dist/voice/makeCreateVoiceClient.js +4 -10
  78. package/dist/voice/makeGetChannelVar.d.ts +2 -1
  79. package/dist/voice/makeGetChannelVar.js +13 -0
  80. package/dist/voice/stt/AbstractSpeechToText.d.ts +4 -3
  81. package/dist/voice/stt/Deepgram.d.ts +18 -0
  82. package/dist/voice/stt/Deepgram.js +156 -0
  83. package/dist/voice/stt/Google.d.ts +5 -6
  84. package/dist/voice/stt/Google.js +13 -13
  85. package/dist/voice/stt/SpeechToTextFactory.js +2 -0
  86. package/dist/voice/stt/types.d.ts +22 -10
  87. package/dist/voice/stt/types.js +7 -0
  88. package/dist/voice/tts/Deepgram.d.ts +25 -0
  89. package/dist/voice/tts/Deepgram.js +122 -0
  90. package/dist/voice/tts/Google.d.ts +2 -1
  91. package/dist/voice/tts/Google.js +7 -8
  92. package/dist/voice/tts/TextToSpeechFactory.js +2 -0
  93. package/dist/voice/types/ari.d.ts +2 -1
  94. package/dist/voice/types/ari.js +1 -0
  95. package/dist/voice/types/voice.d.ts +9 -1
  96. package/package.json +9 -8
  97. package/dist/calls/createTrackCallSubscriber.d.ts +0 -5
  98. package/dist/calls/createTrackCallSubscriber.js +0 -52
  99. package/dist/calls/trackCall.js +0 -63
  100. package/dist/voice/handlers/StasisEnd.d.ts +0 -4
  101. package/dist/voice/handlers/dial/handleDialEvents.d.ts +0 -5
  102. /package/dist/voice/handlers/{awaitForPlaybackFinished.d.ts → utils/awaitForPlaybackFinished.d.ts} +0 -0
  103. /package/dist/voice/handlers/{awaitForRecordingFinished.d.ts → utils/awaitForRecordingFinished.d.ts} +0 -0
  104. /package/dist/voice/handlers/{withErrorHandling.d.ts → utils/withErrorHandling.d.ts} +0 -0
  105. /package/dist/voice/handlers/{withErrorHandling.js → utils/withErrorHandling.js} +0 -0
package/dist/core/seed.js CHANGED
@@ -53,6 +53,26 @@ function main() {
53
53
  type: "STT"
54
54
  }
55
55
  });
56
+ yield prisma.product.upsert({
57
+ where: { ref: "stt.deepgram" },
58
+ update: {},
59
+ create: {
60
+ ref: "stt.deepgram",
61
+ name: "Deepgram Speech-to-Text",
62
+ vendor: "DEEPGRAM",
63
+ type: "STT"
64
+ }
65
+ });
66
+ yield prisma.product.upsert({
67
+ where: { ref: "tts.deepgram" },
68
+ update: {},
69
+ create: {
70
+ ref: "tts.deepgram",
71
+ name: "Deepgram Text-to-Speech",
72
+ vendor: "DEEPGRAM",
73
+ type: "TTS"
74
+ }
75
+ });
56
76
  yield prisma.product.upsert({
57
77
  where: { ref: "nlu.dialogflowcx" },
58
78
  update: {},
@@ -59,8 +59,8 @@ declare const services: Promise<[{
59
59
  };
60
60
  handlers: {
61
61
  createCall: (call: {
62
- request: import("../calls/types").CreateCallRequest;
63
- }, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: import("../calls/types").CreateCallResponse) => void) => Promise<void>;
62
+ request: import("@fonoster/types").CreateCallRequest;
63
+ }, callback: (error?: import("@fonoster/common").GrpcErrorMessage, response?: import("@fonoster/common").BaseApiObject) => void) => Promise<void>;
64
64
  listCalls: (call: {
65
65
  request: import("../calls/types").ListCallsRequest;
66
66
  }, callback: (error: import("@fonoster/common").GrpcErrorMessage, response?: import("../calls/types").ListCallsResponse) => void) => Promise<void>;
package/dist/envs.d.ts CHANGED
@@ -34,8 +34,6 @@ export declare const NATS_URL: string;
34
34
  export declare const DEFAULT_NATS_QUEUE_GROUP = "apiserver";
35
35
  export declare const CALLS_CREATE_SUBJECT = "calls.create";
36
36
  export declare const CALLS_TRACK_CALL_SUBJECT = "calls.track";
37
- export declare const ASTERISK_CONTEXT = "local-ctx";
38
- export declare const ASTERISK_EXTENSION = "voice";
39
37
  export declare const ASTERISK_TRUNK = "routr";
40
38
  export declare const ASTERISK_ARI_PROXY_URL: string;
41
39
  export declare const ASTERISK_ARI_USERNAME: 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.INTEGRATIONS_FILE = exports.FILES_SERVER_PORT = exports.TTS_PATH_TO_FILES = exports.ASTERISK_SYSTEM_DOMAIN = exports.ASTERISK_ARI_SECRET = exports.ASTERISK_ARI_USERNAME = exports.ASTERISK_ARI_PROXY_URL = exports.ASTERISK_TRUNK = exports.ASTERISK_EXTENSION = exports.ASTERISK_CONTEXT = exports.CALLS_TRACK_CALL_SUBJECT = exports.CALLS_CREATE_SUBJECT = exports.DEFAULT_NATS_QUEUE_GROUP = exports.NATS_URL = exports.APISERVER_HOST = exports.APISERVER_BIND_ADDR = exports.ROUTR_DEFAULT_PEER_PASSWORD = exports.ROUTR_DEFAULT_PEER_AOR = exports.ROUTR_DEFAULT_PEER_USERNAME = exports.ROUTR_DEFAULT_PEER_NAME = exports.ROUTR_API_ENDPOINT = exports.CLOAK_ENCRYPTION_KEY = exports.INFLUXDB_TOKEN = exports.INFLUXDB_BUCKET = exports.INFLUXDB_ORG = exports.INFLUXDB_PASSWORD = exports.INFLUXDB_USERNAME = exports.INFLUXDB_URL = exports.OWNER_PASSWORD = exports.OWNER_EMAIL = exports.OWNER_NAME = exports.EMAIL_TEMPLATES_DIR = exports.SMTP_SENDER = exports.SMTP_AUTH_PASS = exports.SMTP_AUTH_USER = exports.SMTP_SECURE = exports.SMTP_PORT = exports.SMTP_HOST = exports.IDENTITY_REFRESH_TOKEN_EXPIRES_IN = exports.IDENTITY_ACCESS_TOKEN_EXPIRES_IN = exports.IDENTITY_ID_TOKEN_EXPIRES_IN = exports.IDENTITY_PUBLIC_KEY = exports.IDENTITY_PRIVATE_KEY = exports.IDENTITY_AUDIENCE = exports.IDENTITY_ISSUER = exports.APP_URL = void 0;
7
+ exports.INTEGRATIONS_FILE = exports.FILES_SERVER_PORT = exports.TTS_PATH_TO_FILES = exports.ASTERISK_SYSTEM_DOMAIN = exports.ASTERISK_ARI_SECRET = exports.ASTERISK_ARI_USERNAME = exports.ASTERISK_ARI_PROXY_URL = exports.ASTERISK_TRUNK = exports.CALLS_TRACK_CALL_SUBJECT = exports.CALLS_CREATE_SUBJECT = exports.DEFAULT_NATS_QUEUE_GROUP = exports.NATS_URL = exports.APISERVER_HOST = exports.APISERVER_BIND_ADDR = exports.ROUTR_DEFAULT_PEER_PASSWORD = exports.ROUTR_DEFAULT_PEER_AOR = exports.ROUTR_DEFAULT_PEER_USERNAME = exports.ROUTR_DEFAULT_PEER_NAME = exports.ROUTR_API_ENDPOINT = exports.CLOAK_ENCRYPTION_KEY = exports.INFLUXDB_TOKEN = exports.INFLUXDB_BUCKET = exports.INFLUXDB_ORG = exports.INFLUXDB_PASSWORD = exports.INFLUXDB_USERNAME = exports.INFLUXDB_URL = exports.OWNER_PASSWORD = exports.OWNER_EMAIL = exports.OWNER_NAME = exports.EMAIL_TEMPLATES_DIR = exports.SMTP_SENDER = exports.SMTP_AUTH_PASS = exports.SMTP_AUTH_USER = exports.SMTP_SECURE = exports.SMTP_PORT = exports.SMTP_HOST = exports.IDENTITY_REFRESH_TOKEN_EXPIRES_IN = exports.IDENTITY_ACCESS_TOKEN_EXPIRES_IN = exports.IDENTITY_ID_TOKEN_EXPIRES_IN = exports.IDENTITY_PUBLIC_KEY = exports.IDENTITY_PRIVATE_KEY = exports.IDENTITY_AUDIENCE = exports.IDENTITY_ISSUER = exports.APP_URL = void 0;
8
8
  /*
9
9
  * Copyright (C) 2024 by Fonoster Inc (https://fonoster.com)
10
10
  * http://github.com/fonoster/fonoster
@@ -99,8 +99,6 @@ exports.NATS_URL = e.NATS_URL;
99
99
  exports.DEFAULT_NATS_QUEUE_GROUP = "apiserver";
100
100
  exports.CALLS_CREATE_SUBJECT = "calls.create";
101
101
  exports.CALLS_TRACK_CALL_SUBJECT = "calls.track";
102
- exports.ASTERISK_CONTEXT = "local-ctx";
103
- exports.ASTERISK_EXTENSION = "voice";
104
102
  exports.ASTERISK_TRUNK = "routr";
105
103
  exports.ASTERISK_ARI_PROXY_URL = e.ASTERISK_ARI_PROXY_URL;
106
104
  exports.ASTERISK_ARI_USERNAME = e.ASTERISK_ARI_USERNAME;
@@ -24,10 +24,11 @@ const influxdb_client_1 = require("@influxdata/influxdb-client");
24
24
  const logger = (0, logger_1.getLogger)({ service: "apiserver", filePath: __filename });
25
25
  function createInfluxDbPub(config) {
26
26
  const { url, token, org, bucket } = config;
27
+ logger.info("creating influxdb client", { url, org, bucket });
27
28
  const client = new influxdb_client_1.InfluxDB({ url, token });
28
29
  const writeClient = client.getWriteApi(org, bucket, "ns");
29
30
  return (event) => {
30
- const point = new influxdb_client_1.Point(event.name).tag("ref", event.tag);
31
+ const point = new influxdb_client_1.Point(event.name).tag("callId", event.tag);
31
32
  Object.entries(event.data).forEach(([key, value]) => {
32
33
  if (typeof value === "number") {
33
34
  point.intField(key, value); // Or floatField for floating-point numbers
@@ -0,0 +1,3 @@
1
+ import { CallDirection } from "../calls/types";
2
+ declare function mapCallDirectionToEnum(direction: string): CallDirection;
3
+ export { mapCallDirectionToEnum };
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mapCallDirectionToEnum = mapCallDirectionToEnum;
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 types_1 = require("../calls/types");
23
+ function mapCallDirectionToEnum(direction) {
24
+ switch (direction) {
25
+ case "from-pstn":
26
+ return types_1.CallDirection.FROM_PSTN;
27
+ case "peer-to-pstn":
28
+ case "agent-to-pstn":
29
+ return types_1.CallDirection.TO_PSTN;
30
+ case "agent-to-agent":
31
+ case "agent-to-peer":
32
+ case "peer-to-agent":
33
+ return types_1.CallDirection.INTRA_NETWORK;
34
+ default:
35
+ throw new Error(`Invalid call direction: ${direction}`);
36
+ }
37
+ }
@@ -38,8 +38,8 @@ exports.watchNats = watchNats;
38
38
  const logger_1 = require("@fonoster/logger");
39
39
  const nats_1 = require("nats");
40
40
  const logger = (0, logger_1.getLogger)({ service: "apiserver", filePath: __filename });
41
- const ROUTR_SUBJECT = "routr.endpoint.registered";
42
- const FONOSTER_SUBJECT = "fonoster.events";
41
+ // const ROUTR_REGISTRATION_SUBJECT = "routr.endpoint.registered";
42
+ const ROUTR_CALL_SUBJECT = "routr.call.*";
43
43
  function streamEvents(subscription, callback) {
44
44
  return __awaiter(this, void 0, void 0, function* () {
45
45
  var _a, subscription_1, subscription_1_1;
@@ -73,12 +73,11 @@ function streamEvents(subscription, callback) {
73
73
  function watchNats(natsUrl, callback) {
74
74
  (() => __awaiter(this, void 0, void 0, function* () {
75
75
  const nc = yield (0, nats_1.connect)({ servers: natsUrl });
76
- const r = nc.subscribe(ROUTR_SUBJECT);
77
- const f = nc.subscribe("foo");
76
+ const a = nc.subscribe(ROUTR_CALL_SUBJECT);
78
77
  logger.verbose("connected to nats", { natsUrl });
79
78
  logger.verbose("subscribed to subjects", {
80
- subjects: [ROUTR_SUBJECT, FONOSTER_SUBJECT]
79
+ subjects: [ROUTR_CALL_SUBJECT]
81
80
  });
82
- yield Promise.all([streamEvents(r, callback), streamEvents(f, callback)]);
81
+ yield Promise.all([streamEvents(a, callback)]);
83
82
  }))();
84
83
  }
@@ -0,0 +1,2 @@
1
+ declare function transformEvent(event: Record<string, unknown>): Record<string, unknown>;
2
+ export { transformEvent };
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.transformEvent = transformEvent;
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 types_1 = require("@fonoster/types");
23
+ const mapCallDirectionToEnum_1 = require("./mapCallDirectionToEnum");
24
+ const ACCESS_KEY_ID_HEADER = "X-Access-Key-Id";
25
+ const CALL_REF_HEADER = "X-Call-Ref";
26
+ const CALL_DIRECTION_HEADER = "X-Call-Direction";
27
+ const DOD_NUMBER_HEADER = "X-Dod-Number";
28
+ const API_ORIGINATED_TYPE_HEADER = "X-Is-Api-Originated-Type";
29
+ function transformEvent(event) {
30
+ const transformedEvent = Object.assign({}, event);
31
+ if (event.startTime) {
32
+ const time = new Date(event.startTime).getTime() / 1000;
33
+ transformedEvent.startedAt = time;
34
+ transformedEvent.endedAt = time;
35
+ delete transformedEvent.startTime;
36
+ }
37
+ if (event.endTime) {
38
+ transformedEvent.endedAt =
39
+ new Date(event.endTime).getTime() / 1000;
40
+ delete transformedEvent.endTime;
41
+ }
42
+ if (event.hangupCause) {
43
+ transformedEvent.status = event.hangupCause;
44
+ delete transformedEvent.hangupCause;
45
+ }
46
+ if (event.to) {
47
+ const to = event.to;
48
+ transformedEvent.to = to.split("@")[0].replace("sip:", "");
49
+ }
50
+ const extraHeaders = event.extraHeaders;
51
+ if (extraHeaders) {
52
+ if (extraHeaders[ACCESS_KEY_ID_HEADER]) {
53
+ transformedEvent.accessKeyId = extraHeaders[ACCESS_KEY_ID_HEADER];
54
+ }
55
+ if (extraHeaders[CALL_REF_HEADER]) {
56
+ transformedEvent.ref = extraHeaders[CALL_REF_HEADER];
57
+ }
58
+ if (extraHeaders[DOD_NUMBER_HEADER]) {
59
+ transformedEvent.from = extraHeaders[DOD_NUMBER_HEADER];
60
+ }
61
+ if (extraHeaders[API_ORIGINATED_TYPE_HEADER]) {
62
+ transformedEvent.type = types_1.CallType.API_ORIGINATED;
63
+ }
64
+ if (extraHeaders[CALL_DIRECTION_HEADER]) {
65
+ transformedEvent.direction = (0, mapCallDirectionToEnum_1.mapCallDirectionToEnum)(extraHeaders[CALL_DIRECTION_HEADER]);
66
+ }
67
+ }
68
+ // Delete the extra headers as they may contain sensitive information
69
+ delete transformedEvent.extraHeaders;
70
+ delete transformedEvent.callId;
71
+ return transformedEvent;
72
+ }
package/dist/index.js CHANGED
@@ -62,6 +62,7 @@ const upsertDefaultPeer_1 = require("./core/upsertDefaultPeer");
62
62
  const envs_1 = require("./envs");
63
63
  const createInfluxDbPub_1 = require("./events/createInfluxDbPub");
64
64
  const nats_1 = require("./events/nats");
65
+ const transformEvent_1 = require("./events/transformEvent");
65
66
  Promise.resolve().then(() => __importStar(require("./core/removeSwaggerNotice")));
66
67
  function main() {
67
68
  return __awaiter(this, void 0, void 0, function* () {
@@ -85,13 +86,11 @@ function main() {
85
86
  });
86
87
  // Subscribe to NATs events
87
88
  (0, nats_1.watchNats)(envs_1.NATS_URL, (event) => {
88
- logger.info("Received event", { event });
89
- // FIXME: Remember to re-map callRef from callId
90
- const callRecord = event;
89
+ logger.verbose("received nats event", Object.assign({}, event));
91
90
  pubToInfluxDb({
92
91
  name: types_1.CALL_DETAIL_RECORD_MEASUREMENT,
93
- tag: callRecord.callRef,
94
- data: event
92
+ tag: event.callId,
93
+ data: (0, transformEvent_1.transformEvent)(event)
95
94
  });
96
95
  });
97
96
  });
@@ -1 +1,3 @@
1
1
  export * from "./makeCheckNumberPreconditions";
2
+ export * from "./makeHandleDialEventsWithNats";
3
+ export * from "./makeHandleDialEventsWithVoiceClient";
@@ -33,3 +33,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
33
33
  * limitations under the License.
34
34
  */
35
35
  __exportStar(require("./makeCheckNumberPreconditions"), exports);
36
+ __exportStar(require("./makeHandleDialEventsWithNats"), exports);
37
+ __exportStar(require("./makeHandleDialEventsWithVoiceClient"), exports);
@@ -0,0 +1,5 @@
1
+ import { NatsConnection } from "nats";
2
+ declare function makeHandleDialEventsWithNats(nc: NatsConnection): (callRef: string, event: {
3
+ dialstatus: string;
4
+ }) => Promise<void>;
5
+ export { makeHandleDialEventsWithNats };
@@ -9,19 +9,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.muteHandler = muteHandler;
13
- const withErrorHandling_1 = require("./withErrorHandling");
14
- function muteHandler(ari, voiceClient) {
15
- return (0, withErrorHandling_1.withErrorHandling)((request) => __awaiter(this, void 0, void 0, function* () {
16
- const { sessionRef, direction } = request;
17
- yield ari.channels.mute({
18
- channelId: sessionRef,
19
- direction
20
- });
21
- voiceClient.sendResponse({
22
- muteResponse: {
23
- sessionRef
24
- }
25
- });
26
- }));
12
+ exports.makeHandleDialEventsWithNats = makeHandleDialEventsWithNats;
13
+ const mapDialStatus_1 = require("./mapDialStatus");
14
+ const envs_1 = require("../envs");
15
+ function makeHandleDialEventsWithNats(nc) {
16
+ return (callRef, event) => __awaiter(this, void 0, void 0, function* () {
17
+ const mappedStatus = (0, mapDialStatus_1.mapDialStatus)(event.dialstatus);
18
+ if (!mappedStatus)
19
+ return; // Ignore the event if status is not mapped
20
+ nc.publish(envs_1.CALLS_TRACK_CALL_SUBJECT, JSON.stringify({ ref: callRef, status: mappedStatus }));
21
+ });
27
22
  }
@@ -0,0 +1,5 @@
1
+ import { VoiceClient } from "../voice/types";
2
+ declare function makeHandleDialEventsWithVoiceClient(voiceClient: VoiceClient): (event: {
3
+ dialstatus: string;
4
+ }) => Promise<void>;
5
+ export { makeHandleDialEventsWithVoiceClient };
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.handleDialEvents = handleDialEvents;
12
+ exports.makeHandleDialEventsWithVoiceClient = makeHandleDialEventsWithVoiceClient;
13
13
  /*
14
14
  * Copyright (C) 2024 by Fonoster Inc (https://fonoster.com)
15
15
  * http://github.com/fonoster/fonoster
@@ -28,23 +28,12 @@ exports.handleDialEvents = handleDialEvents;
28
28
  * See the License for the specific language governing permissions and
29
29
  * limitations under the License.
30
30
  */
31
- const common_1 = require("@fonoster/common");
32
- const FailedStatus = ["CHANUNAVAIL", "CONGESTION"];
33
- function handleDialEvents(voiceClient) {
31
+ const mapDialStatus_1 = require("./mapDialStatus");
32
+ function makeHandleDialEventsWithVoiceClient(voiceClient) {
34
33
  return (event) => __awaiter(this, void 0, void 0, function* () {
35
- const status = event.dialstatus.toUpperCase();
36
- const dialStatusArray = Object.keys(common_1.DialStatus).map((key) => common_1.DialStatus[key]);
37
- let mappedStatus;
38
- if (FailedStatus.includes(status)) {
39
- mappedStatus = common_1.DialStatus.FAILED;
40
- }
41
- else if (dialStatusArray.includes(status)) {
42
- mappedStatus = common_1.DialStatus[status];
43
- }
44
- else {
45
- // If the status is not in the DialStatus enum, we ignore the event
46
- return;
47
- }
34
+ const mappedStatus = (0, mapDialStatus_1.mapDialStatus)(event.dialstatus);
35
+ if (!mappedStatus)
36
+ return; // Ignore the event if status is not mapped
48
37
  voiceClient.sendResponse({
49
38
  dialResponse: {
50
39
  status: mappedStatus
@@ -0,0 +1,3 @@
1
+ import { DialStatus } from "@fonoster/common";
2
+ declare function mapDialStatus(status: string): DialStatus | undefined;
3
+ export { mapDialStatus };
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mapDialStatus = mapDialStatus;
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
+ const FailedStatus = ["CHANUNAVAIL", "CONGESTION"];
24
+ function mapDialStatus(status) {
25
+ if (status === "") {
26
+ return common_1.DialStatus.TRYING;
27
+ }
28
+ const normalizedStatus = status.toUpperCase();
29
+ const dialStatusArray = Object.keys(common_1.DialStatus).map((key) => common_1.DialStatus[key]);
30
+ if (FailedStatus.includes(normalizedStatus)) {
31
+ return common_1.DialStatus.FAILED;
32
+ }
33
+ else if (dialStatusArray.includes(normalizedStatus)) {
34
+ return common_1.DialStatus[normalizedStatus];
35
+ }
36
+ // If the status is not in the DialStatus enum, return undefined
37
+ return undefined;
38
+ }
@@ -6,7 +6,8 @@ import { SpeechResult } from "./stt/types";
6
6
  import { GRPCClient, SpeechToText, TextToSpeech, VoiceClient } from "./types";
7
7
  declare class VoiceClientImpl implements VoiceClient {
8
8
  config: VoiceClientConfig;
9
- stream: Stream;
9
+ verbsStream: Stream;
10
+ transcriptionsStream: Stream;
10
11
  voice: VoiceSessionStreamClient;
11
12
  tts: TextToSpeech;
12
13
  stt: SpeechToText;
@@ -27,6 +28,14 @@ declare class VoiceClientImpl implements VoiceClient {
27
28
  sendResponse(response: VoiceIn): void;
28
29
  synthesize(text: string, options: SayOptions): Promise<string>;
29
30
  transcribe(): Promise<SpeechResult>;
31
+ startSpeechGather(callback: (stream: {
32
+ speech?: string;
33
+ digit?: string;
34
+ }) => void): Promise<void>;
35
+ startDtmfGather(sessionRef: string, callback: (event: {
36
+ digit: string;
37
+ }) => void): Promise<void>;
38
+ stopStreamGather(): Promise<void>;
30
39
  waitForDtmf(params: {
31
40
  sessionRef: string;
32
41
  finishOnKey: string;
@@ -67,26 +67,27 @@ class VoiceClientImpl {
67
67
  constructor(params) {
68
68
  const { config, tts, stt, ari } = params;
69
69
  this.config = config;
70
- this.stream = new stream_1.Stream();
70
+ this.verbsStream = new stream_1.Stream();
71
+ this.transcriptionsStream = new stream_1.Stream();
71
72
  this.tts = tts;
72
73
  this.stt = stt;
73
74
  this.ari = ari;
74
75
  }
75
76
  connect() {
76
77
  return __awaiter(this, void 0, void 0, function* () {
77
- this.grpcClient = new VoiceServiceClientConst_1.VoiceServiceClient(this.config.appEndpoint, grpc.credentials.createInsecure());
78
+ this.grpcClient = new VoiceServiceClientConst_1.VoiceServiceClient(this.config.endpoint, grpc.credentials.createInsecure());
78
79
  const metadata = new grpc.Metadata();
79
80
  metadata.add("accessKeyId", this.config.accessKeyId);
80
81
  metadata.add("token", this.config.sessionToken);
81
82
  this.voice = this.grpcClient.createSession(metadata);
82
83
  this.voice.on(common_1.StreamEvent.DATA, (data) => {
83
- this.stream.emit(data.content, data);
84
+ this.verbsStream.emit(data.content, data);
84
85
  });
85
86
  this.voice.write({ request: this.config });
86
87
  this.voice.on(common_1.StreamEvent.ERROR, (error) => {
87
88
  if (error.code === grpc.status.UNAVAILABLE) {
88
89
  // FIXME: This error should be sent back to the user
89
- logger.error(`voice server not available at "${this.config.appEndpoint}"`);
90
+ logger.error(`voice server not available at "${this.config.endpoint}"`);
90
91
  return;
91
92
  }
92
93
  logger.error(error.message);
@@ -98,7 +99,7 @@ class VoiceClientImpl {
98
99
  }
99
100
  setupAudioSocket(port) {
100
101
  this.audioSocket = new streams_1.AudioSocket();
101
- this.audioSocket.onConnection((0, transcribeOnConnection_1.transcribeOnConnection)(this.stream));
102
+ this.audioSocket.onConnection((0, transcribeOnConnection_1.transcribeOnConnection)(this.transcriptionsStream));
102
103
  this.audioSocket.listen(port, () => {
103
104
  logger.verbose("starting audio socket for voice client", {
104
105
  port,
@@ -118,10 +119,22 @@ class VoiceClientImpl {
118
119
  });
119
120
  yield channel.answer();
120
121
  }));
122
+ channel.once("ChannelLeftBridge", () => __awaiter(this, void 0, void 0, function* () {
123
+ // TODO: Evaluate a better way to handle this
124
+ // We should keep track of the channels and bridges and destroy them
125
+ // even if the apiserver crashes. Otherwise we risk having a lot of
126
+ // unused channels and bridges.
127
+ try {
128
+ yield bridge.destroy();
129
+ }
130
+ catch (e) {
131
+ // We can only try
132
+ }
133
+ }));
121
134
  });
122
135
  }
123
136
  on(type, callback) {
124
- this.stream.on(type.toString(), (data) => {
137
+ this.verbsStream.on(type.toString(), (data) => {
125
138
  callback(data[type]);
126
139
  });
127
140
  }
@@ -136,7 +149,7 @@ class VoiceClientImpl {
136
149
  transcribe() {
137
150
  return __awaiter(this, void 0, void 0, function* () {
138
151
  try {
139
- return yield this.stt.transcribe(this.stream);
152
+ return yield this.stt.transcribe(this.transcriptionsStream);
140
153
  }
141
154
  catch (e) {
142
155
  logger.warn("transcription error", e);
@@ -144,6 +157,32 @@ class VoiceClientImpl {
144
157
  }
145
158
  });
146
159
  }
160
+ startSpeechGather(callback) {
161
+ return __awaiter(this, void 0, void 0, function* () {
162
+ try {
163
+ const out = this.stt.streamTranscribe(this.transcriptionsStream);
164
+ out.on("data", callback);
165
+ }
166
+ catch (e) {
167
+ logger.error(e);
168
+ }
169
+ });
170
+ }
171
+ startDtmfGather(sessionRef, callback) {
172
+ return __awaiter(this, void 0, void 0, function* () {
173
+ const channel = yield this.ari.channels.get({ channelId: sessionRef });
174
+ channel.on(types_1.AriEvent.CHANNEL_DTMF_RECEIVED, (event) => {
175
+ const { digit } = event;
176
+ callback({ digit });
177
+ });
178
+ });
179
+ }
180
+ // Stops both speech and dtmf gather
181
+ stopStreamGather() {
182
+ return __awaiter(this, void 0, void 0, function* () {
183
+ throw new Error("Method 'stopStreamGather' not implemented.");
184
+ });
185
+ }
147
186
  waitForDtmf(params) {
148
187
  return __awaiter(this, void 0, void 0, function* () {
149
188
  const { onDigitReceived, sessionRef, finishOnKey, maxDigits, timeout } = params;
@@ -181,6 +220,7 @@ class VoiceClientImpl {
181
220
  }
182
221
  close() {
183
222
  try {
223
+ this.voice.end();
184
224
  this.grpcClient.close();
185
225
  this.audioSocket.close();
186
226
  }
@@ -1,4 +1,5 @@
1
- import { Channel, Client, StasisStart } from "ari-client";
1
+ import { Channel, Client, Dial, StasisStart } from "ari-client";
2
+ import { NatsConnection } from "nats";
2
3
  import { VoiceClient } from "./types";
3
4
  type CreateVoiceClient = (params: {
4
5
  ari: Client;
@@ -8,10 +9,13 @@ type CreateVoiceClient = (params: {
8
9
  declare class VoiceDispatcher {
9
10
  voiceClients: Map<string, VoiceClient>;
10
11
  ari: Client;
12
+ nc: NatsConnection;
11
13
  createVoiceClient: CreateVoiceClient;
12
- constructor(ari: Client, createVoiceClient: CreateVoiceClient);
14
+ constructor(ari: Client, nc: NatsConnection, createVoiceClient: CreateVoiceClient);
13
15
  start(): void;
14
16
  handleStasisStart(event: StasisStart, channel: Channel): Promise<void>;
15
17
  handleStasisEnd(_: undefined, channel: Channel): void;
18
+ handleDial(event: Dial, channel: Channel): Promise<void>;
19
+ isHandledElsewhere(channel: Channel): Promise<boolean>;
16
20
  }
17
21
  export { VoiceDispatcher };