@fonoster/common 0.27.0 → 0.30.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 (176) hide show
  1. package/dist/assistants/index.d.ts +2 -6
  2. package/dist/assistants/index.js +2 -6
  3. package/dist/assistants/sessionAssistantSchema.d.ts +741 -0
  4. package/dist/assistants/sessionAssistantSchema.js +112 -0
  5. package/dist/assistants/testCasesSchema.d.ts +4 -4
  6. package/dist/{voice/Dial.d.ts → calls/index.d.ts} +16 -13
  7. package/dist/calls/index.js +44 -0
  8. package/dist/cellsecrets/index.d.ts +91 -0
  9. package/dist/cellsecrets/index.js +299 -0
  10. package/dist/config/cli/check.d.ts +6 -0
  11. package/dist/config/cli/check.js +202 -0
  12. package/dist/config/cli/envFile.d.ts +23 -0
  13. package/dist/config/cli/envFile.js +81 -0
  14. package/dist/config/cli/exposure.d.ts +10 -0
  15. package/dist/config/cli/exposure.js +93 -0
  16. package/dist/config/cli/index.d.ts +2 -0
  17. package/dist/{identity/tokenHasAccessKeyId.js → config/cli/index.js} +7 -8
  18. package/dist/config/cli/info.d.ts +6 -0
  19. package/dist/config/cli/info.js +62 -0
  20. package/dist/config/cli/init.d.ts +9 -0
  21. package/dist/config/cli/init.js +152 -0
  22. package/dist/config/cli/main.d.ts +11 -0
  23. package/dist/config/cli/main.js +155 -0
  24. package/dist/config/cli/render.d.ts +44 -0
  25. package/dist/config/cli/render.js +129 -0
  26. package/dist/config/cli/secrets.d.ts +21 -0
  27. package/dist/config/cli/secrets.js +117 -0
  28. package/dist/{utils/findIntegrationsCredentials.d.ts → config/cli/types.d.ts} +6 -5
  29. package/dist/config/cli/types.js +7 -0
  30. package/dist/config/configHints.d.ts +39 -0
  31. package/dist/config/configHints.js +61 -0
  32. package/dist/{voice → config}/index.d.ts +12 -13
  33. package/dist/config/index.js +46 -0
  34. package/dist/config/integrationsSchema.d.ts +32 -0
  35. package/dist/config/integrationsSchema.js +56 -0
  36. package/dist/config/interpolateEnv.d.ts +34 -0
  37. package/dist/config/interpolateEnv.js +70 -0
  38. package/dist/config/loadServiceConfig.d.ts +33 -0
  39. package/dist/config/loadServiceConfig.js +121 -0
  40. package/dist/config/logsSchema.d.ts +76 -0
  41. package/dist/config/logsSchema.js +49 -0
  42. package/dist/config/natsConnection.d.ts +53 -0
  43. package/dist/config/natsConnection.js +58 -0
  44. package/dist/config/parseConfigFile.d.ts +7 -0
  45. package/dist/config/parseConfigFile.js +60 -0
  46. package/dist/config/resolveConfigFile.d.ts +17 -0
  47. package/dist/config/resolveConfigFile.js +54 -0
  48. package/dist/config/resolveSecretRefs.d.ts +35 -0
  49. package/dist/config/resolveSecretRefs.js +129 -0
  50. package/dist/{assistants/audioFiltersSchema.d.ts → config/strictDeep.d.ts} +9 -12
  51. package/dist/config/strictDeep.js +58 -0
  52. package/dist/config/telemetrySchema.d.ts +39 -0
  53. package/dist/config/telemetrySchema.js +44 -0
  54. package/dist/config/toJsonSchema.d.ts +30 -0
  55. package/dist/config/toJsonSchema.js +161 -0
  56. package/dist/constants.d.ts +1 -4
  57. package/dist/constants.js +4 -5
  58. package/dist/envs.d.ts +2 -1
  59. package/dist/envs.js +13 -11
  60. package/dist/identity/createAuthInterceptor.d.ts +9 -2
  61. package/dist/identity/createAuthInterceptor.js +36 -30
  62. package/dist/identity/hasAccess.d.ts +9 -3
  63. package/dist/identity/hasAccess.js +32 -8
  64. package/dist/identity/index.d.ts +1 -0
  65. package/dist/identity/index.js +1 -0
  66. package/dist/identity/isValidToken.d.ts +12 -2
  67. package/dist/identity/isValidToken.js +25 -9
  68. package/dist/identity/roles.js +22 -6
  69. package/dist/identity/types.d.ts +6 -1
  70. package/dist/identity/types.js +1 -0
  71. package/dist/{assistants/LanguageModelProvider.js → identity/verifyAccessToken.d.ts} +10 -11
  72. package/dist/identity/verifyAccessToken.js +44 -0
  73. package/dist/index.d.ts +11 -1
  74. package/dist/index.js +14 -2
  75. package/dist/net/assertPublicDestination.d.ts +52 -0
  76. package/dist/net/assertPublicDestination.js +342 -0
  77. package/dist/{voice/PlayDtmf.d.ts → net/index.d.ts} +1 -5
  78. package/dist/{voice → net}/index.js +1 -13
  79. package/dist/notifications/createSendEmail.js +3 -1
  80. package/dist/protos/applications.proto +124 -3
  81. package/dist/protos/authz.proto +23 -4
  82. package/dist/protos/capabilities.proto +94 -0
  83. package/dist/protos/identity.proto +26 -1
  84. package/dist/protos/providers.proto +135 -0
  85. package/dist/protos/webhooks.proto +140 -0
  86. package/dist/{voice/StopSay.d.ts → regions/hub/index.d.ts} +1 -6
  87. package/dist/regions/hub/index.js +35 -0
  88. package/dist/regions/hub/natsJwt.d.ts +63 -0
  89. package/dist/regions/hub/natsJwt.js +195 -0
  90. package/dist/registry/Registry.d.ts +79 -0
  91. package/dist/registry/Registry.js +418 -0
  92. package/dist/registry/cli.d.ts +20 -0
  93. package/dist/registry/cli.js +424 -0
  94. package/dist/{assistants/audioFiltersSchema.js → registry/features.d.ts} +13 -14
  95. package/dist/registry/features.js +36 -0
  96. package/dist/{assistants/LanguageModelProvider.d.ts → registry/index.d.ts} +6 -8
  97. package/dist/registry/index.js +40 -0
  98. package/dist/registry/prisma.d.ts +67 -0
  99. package/dist/registry/prisma.js +391 -0
  100. package/dist/registry/stores.d.ts +21 -0
  101. package/dist/{assistants/eventsHookSchema.js → registry/stores.js} +55 -18
  102. package/dist/registry/types.d.ts +98 -0
  103. package/dist/{voice/Say.d.ts → registry/types.js} +4 -10
  104. package/dist/registry/workspaceKeys.d.ts +41 -0
  105. package/dist/registry/workspaceKeys.js +64 -0
  106. package/dist/session/index.d.ts +19 -0
  107. package/dist/session/index.js +35 -0
  108. package/dist/session/schema.d.ts +1951 -0
  109. package/dist/session/schema.js +397 -0
  110. package/dist/sessions/index.d.ts +93 -0
  111. package/dist/sessions/index.js +194 -0
  112. package/dist/telemetry.d.ts +76 -0
  113. package/dist/telemetry.js +360 -0
  114. package/dist/types.d.ts +3 -5
  115. package/dist/utils/{findIntegrationsCredentials.js → WriteBehind.d.ts} +13 -10
  116. package/dist/utils/WriteBehind.js +64 -0
  117. package/dist/utils/index.d.ts +1 -1
  118. package/dist/utils/index.js +1 -1
  119. package/dist/{voice/Play.d.ts → utils/logUnhandledRejections.d.ts} +8 -12
  120. package/dist/utils/logUnhandledRejections.js +37 -0
  121. package/dist/validators/calls.d.ts +2 -2
  122. package/dist/validators/calls.js +5 -2
  123. package/dist/validators/identity.d.ts +37 -8
  124. package/dist/validators/identity.js +22 -3
  125. package/dist/validators/index.d.ts +1 -0
  126. package/dist/validators/index.js +1 -0
  127. package/dist/validators/sip.d.ts +40 -0
  128. package/dist/validators/sip.js +99 -0
  129. package/dist/validators/sipnet/credentials.d.ts +2 -2
  130. package/dist/validators/sipnet/domains.js +7 -7
  131. package/dist/validators/sipnet/trunks.d.ts +14 -14
  132. package/dist/validators/sipnet/trunks.js +4 -2
  133. package/dist/webhooks/Dispatcher.d.ts +87 -0
  134. package/dist/webhooks/Dispatcher.js +225 -0
  135. package/dist/webhooks/bus.d.ts +49 -0
  136. package/dist/webhooks/bus.js +170 -0
  137. package/dist/webhooks/events.d.ts +66 -0
  138. package/dist/webhooks/events.js +62 -0
  139. package/dist/webhooks/index.d.ts +23 -0
  140. package/dist/webhooks/index.js +39 -0
  141. package/dist/webhooks/prisma.d.ts +55 -0
  142. package/dist/webhooks/prisma.js +242 -0
  143. package/dist/webhooks/signing.d.ts +8 -0
  144. package/dist/webhooks/signing.js +53 -0
  145. package/package.json +25 -12
  146. package/schema/session.v1.json +791 -0
  147. package/dist/assistants/assistantSchema.d.ts +0 -739
  148. package/dist/assistants/assistantSchema.js +0 -37
  149. package/dist/assistants/conversationSettingsSchema.d.ts +0 -122
  150. package/dist/assistants/conversationSettingsSchema.js +0 -133
  151. package/dist/assistants/eventsHookSchema.d.ts +0 -38
  152. package/dist/assistants/languageModelConfigSchema.d.ts +0 -206
  153. package/dist/assistants/languageModelConfigSchema.js +0 -91
  154. package/dist/identity/tokenHasAccessKeyId.d.ts +0 -2
  155. package/dist/protos/voice.proto +0 -557
  156. package/dist/voice/Dial.js +0 -20
  157. package/dist/voice/Gather.d.ts +0 -37
  158. package/dist/voice/Gather.js +0 -9
  159. package/dist/voice/Mute.d.ts +0 -31
  160. package/dist/voice/Mute.js +0 -9
  161. package/dist/voice/Play.js +0 -2
  162. package/dist/voice/PlayDtmf.js +0 -2
  163. package/dist/voice/PlaybackControl.d.ts +0 -31
  164. package/dist/voice/PlaybackControl.js +0 -11
  165. package/dist/voice/Record.d.ts +0 -36
  166. package/dist/voice/Record.js +0 -7
  167. package/dist/voice/Say.js +0 -2
  168. package/dist/voice/StopSay.js +0 -2
  169. package/dist/voice/Stream.d.ts +0 -53
  170. package/dist/voice/Stream.js +0 -19
  171. package/dist/voice/StreamGather.d.ts +0 -34
  172. package/dist/voice/StreamGather.js +0 -9
  173. package/dist/voice/Verb.d.ts +0 -27
  174. package/dist/voice/Verb.js +0 -2
  175. package/dist/voice/voice.d.ts +0 -171
  176. package/dist/voice/voice.js +0 -65
@@ -0,0 +1,342 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.UnsafeDestinationError = void 0;
16
+ exports.isPrivateAddress = isPrivateAddress;
17
+ exports.assertPublicDestination = assertPublicDestination;
18
+ exports.assertPublicUrlShape = assertPublicUrlShape;
19
+ exports.publicWebSocketGuard = publicWebSocketGuard;
20
+ exports.assertPublicWebSocketUrlShape = assertPublicWebSocketUrlShape;
21
+ exports.safeFetch = safeFetch;
22
+ /**
23
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
24
+ * http://github.com/fonoster/fonoster
25
+ *
26
+ * This file is part of Fonoster
27
+ *
28
+ * Licensed under the MIT License (the "License");
29
+ * you may not use this file except in compliance with
30
+ * the License. You may obtain a copy of the License at
31
+ *
32
+ * https://opensource.org/licenses/MIT
33
+ *
34
+ * Unless required by applicable law or agreed to in writing, software
35
+ * distributed under the License is distributed on an "AS IS" BASIS,
36
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
37
+ * See the License for the specific language governing permissions and
38
+ * limitations under the License.
39
+ */
40
+ const node_dns_1 = __importDefault(require("node:dns"));
41
+ const node_http_1 = __importDefault(require("node:http"));
42
+ const node_https_1 = __importDefault(require("node:https"));
43
+ const node_net_1 = __importDefault(require("node:net"));
44
+ const node_url_1 = require("node:url");
45
+ /* global Response, Headers */
46
+ /**
47
+ * Outbound requests to tenant-chosen URLs (Autopilot HTTP tools, webhooks) must not reach the
48
+ * platform's own network (SSRF, RV-B2): http/https only, and every address the host resolves to
49
+ * must be public. Operators who self-host can allow internal destinations with
50
+ * `FONOSTER_ALLOWED_DESTINATIONS` (comma-separated host names, IPs or CIDRs; default empty).
51
+ */
52
+ class UnsafeDestinationError extends Error {
53
+ constructor(message) {
54
+ super(message);
55
+ this.name = "UnsafeDestinationError";
56
+ }
57
+ }
58
+ exports.UnsafeDestinationError = UnsafeDestinationError;
59
+ const blocked = new node_net_1.default.BlockList();
60
+ for (const [net4, prefix] of [
61
+ ["0.0.0.0", 8], // "this network", 0.0.0.0
62
+ ["10.0.0.0", 8],
63
+ ["100.64.0.0", 10], // CGNAT
64
+ ["127.0.0.0", 8],
65
+ ["169.254.0.0", 16], // link-local, cloud metadata
66
+ ["172.16.0.0", 12],
67
+ ["192.0.0.0", 24],
68
+ ["192.0.2.0", 24],
69
+ ["192.168.0.0", 16],
70
+ ["198.18.0.0", 15],
71
+ ["198.51.100.0", 24],
72
+ ["203.0.113.0", 24],
73
+ ["224.0.0.0", 4], // multicast
74
+ ["240.0.0.0", 4] // reserved, broadcast
75
+ ])
76
+ blocked.addSubnet(net4, prefix, "ipv4");
77
+ for (const [net6, prefix] of [
78
+ ["::", 128],
79
+ ["::1", 128],
80
+ ["64:ff9b::", 96], // NAT64 can reach IPv4 private ranges
81
+ ["100::", 64],
82
+ ["2001:db8::", 32],
83
+ ["fc00::", 7], // ULA
84
+ ["fe80::", 10], // link-local
85
+ ["ff00::", 8] // multicast
86
+ ])
87
+ blocked.addSubnet(net6, prefix, "ipv6");
88
+ const INTERNAL_SUFFIXES = [
89
+ ".localhost",
90
+ ".local",
91
+ ".internal",
92
+ ".intranet",
93
+ ".lan",
94
+ ".home.arpa",
95
+ ".cluster.local",
96
+ ".svc"
97
+ ];
98
+ /** The IPv4 address inside an IPv4-mapped IPv6 address (::ffff:a.b.c.d or ::ffff:hhhh:hhhh). */
99
+ function mappedV4(ip) {
100
+ const m = /^::ffff:(.+)$/i.exec(ip);
101
+ if (!m)
102
+ return undefined;
103
+ if (node_net_1.default.isIPv4(m[1]))
104
+ return m[1];
105
+ const hex = /^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i.exec(m[1]);
106
+ if (!hex)
107
+ return undefined;
108
+ const hi = parseInt(hex[1], 16);
109
+ const lo = parseInt(hex[2], 16);
110
+ return [hi >> 8, hi & 255, lo >> 8, lo & 255].join(".");
111
+ }
112
+ /** True when the address is loopback, private, link-local, ULA, CGNAT, multicast or reserved. */
113
+ function isPrivateAddress(ip) {
114
+ const bare = ip.replace(/^\[|\]$/g, "").split("%")[0];
115
+ const v4 = mappedV4(bare);
116
+ if (v4)
117
+ return blocked.check(v4, "ipv4");
118
+ if (node_net_1.default.isIPv4(bare))
119
+ return blocked.check(bare, "ipv4");
120
+ if (node_net_1.default.isIPv6(bare))
121
+ return blocked.check(bare, "ipv6");
122
+ return true; // not an address: refuse
123
+ }
124
+ function allowList(policy) {
125
+ var _a, _b;
126
+ return ((_a = policy === null || policy === void 0 ? void 0 : policy.allow) !== null && _a !== void 0 ? _a : ((_b = process.env.FONOSTER_ALLOWED_DESTINATIONS) !== null && _b !== void 0 ? _b : "").split(","))
127
+ .map((s) => s.trim().toLowerCase())
128
+ .filter(Boolean);
129
+ }
130
+ function allowedByOperator(host, ip, allow) {
131
+ var _a;
132
+ for (const entry of allow) {
133
+ if (entry === host)
134
+ return true;
135
+ if (!ip)
136
+ continue;
137
+ const [base, bits] = entry.split("/");
138
+ if (!node_net_1.default.isIP(base))
139
+ continue;
140
+ const family = node_net_1.default.isIPv4(base) ? "ipv4" : "ipv6";
141
+ const list = new node_net_1.default.BlockList();
142
+ const prefix = bits ? Number(bits) : family === "ipv4" ? 32 : 128;
143
+ list.addSubnet(base, prefix, family);
144
+ const target = (_a = mappedV4(ip)) !== null && _a !== void 0 ? _a : ip;
145
+ const targetFamily = node_net_1.default.isIPv4(target) ? "ipv4" : "ipv6";
146
+ if (targetFamily === family && list.check(target, family))
147
+ return true;
148
+ }
149
+ return false;
150
+ }
151
+ function checkAddress(host, ip, allow) {
152
+ if (isPrivateAddress(ip) && !allowedByOperator(host, ip, allow))
153
+ throw new UnsafeDestinationError(`destination ${host} resolves to a non-public address`);
154
+ }
155
+ function hostOf(url) {
156
+ return url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
157
+ }
158
+ /** Throws unless the URL is http(s) and its host is public, before any DNS lookup. */
159
+ function checkUrlShape(url, allow) {
160
+ if (url.protocol !== "http:" && url.protocol !== "https:")
161
+ throw new UnsafeDestinationError(`unsupported scheme ${url.protocol}`);
162
+ const host = hostOf(url);
163
+ if (!host)
164
+ throw new UnsafeDestinationError("missing host");
165
+ if (node_net_1.default.isIP(host)) {
166
+ checkAddress(host, host, allow);
167
+ return host;
168
+ }
169
+ if (allow.includes(host))
170
+ return host;
171
+ const name = host.replace(/\.$/, "");
172
+ if (!name.includes(".") ||
173
+ name === "localhost" ||
174
+ INTERNAL_SUFFIXES.some((s) => name.endsWith(s)))
175
+ throw new UnsafeDestinationError(`destination ${host} is an internal name`);
176
+ return host;
177
+ }
178
+ /**
179
+ * Checks a tenant-chosen URL: http/https, not an internal name, and every resolved address
180
+ * public (or allowed by the operator). Returns the parsed URL.
181
+ */
182
+ function assertPublicDestination(input, policy) {
183
+ return __awaiter(this, void 0, void 0, function* () {
184
+ let url;
185
+ try {
186
+ url = new node_url_1.URL(String(input));
187
+ }
188
+ catch (_a) {
189
+ throw new UnsafeDestinationError("invalid URL");
190
+ }
191
+ const allow = allowList(policy);
192
+ const host = checkUrlShape(url, allow);
193
+ if (node_net_1.default.isIP(host) || allow.includes(host))
194
+ return url;
195
+ let addresses;
196
+ try {
197
+ addresses = yield node_dns_1.default.promises.lookup(host, { all: true });
198
+ }
199
+ catch (_b) {
200
+ throw new UnsafeDestinationError(`cannot resolve ${host}`);
201
+ }
202
+ for (const a of addresses)
203
+ checkAddress(host, a.address, allow);
204
+ return url;
205
+ });
206
+ }
207
+ /** Synchronous shape check (scheme, literal IPs, internal names) for request validation. */
208
+ function assertPublicUrlShape(input, policy) {
209
+ let url;
210
+ try {
211
+ url = new node_url_1.URL(String(input));
212
+ }
213
+ catch (_a) {
214
+ throw new UnsafeDestinationError("invalid URL");
215
+ }
216
+ checkUrlShape(url, allowList(policy));
217
+ return url;
218
+ }
219
+ // The connection is checked when the socket resolves the name, so a DNS answer that changes
220
+ // between the check and the request (rebinding) cannot reach a private address.
221
+ function guardedLookup(host, allow) {
222
+ return (hostname, options, callback) => {
223
+ node_dns_1.default.lookup(hostname, Object.assign(Object.assign({}, options), { all: true }), (err, addrs) => {
224
+ if (err)
225
+ return callback(err, "", 4);
226
+ const list = addrs;
227
+ try {
228
+ for (const a of list)
229
+ checkAddress(host, a.address, allow);
230
+ }
231
+ catch (e) {
232
+ return callback(e, "", 4);
233
+ }
234
+ if (options.all)
235
+ return callback(null, list);
236
+ callback(null, list[0].address, list[0].family);
237
+ });
238
+ };
239
+ }
240
+ /** A ws:// or wss:// URL as the http(s) URL its handshake uses. */
241
+ function asHttpUrl(input) {
242
+ return String(input).replace(/^ws(s?):/i, "http$1:");
243
+ }
244
+ /**
245
+ * For WebSocket connections to tenant-chosen URLs (session apps, listeners): checks the URL's
246
+ * shape (ws/wss, no internal names or private literal IPs) and returns a socket `lookup` that
247
+ * refuses private addresses when the name resolves. Pass it as the `lookup` option of `ws`.
248
+ */
249
+ function publicWebSocketGuard(input, policy) {
250
+ if (!/^wss?:\/\//i.test(String(input)))
251
+ throw new UnsafeDestinationError("unsupported scheme");
252
+ const allow = allowList(policy);
253
+ const url = assertPublicUrlShape(asHttpUrl(input), { allow });
254
+ return { lookup: guardedLookup(hostOf(url), allow) };
255
+ }
256
+ /** Synchronous shape check of a ws:// or wss:// URL (see assertPublicUrlShape). */
257
+ function assertPublicWebSocketUrlShape(input, policy) {
258
+ if (!/^wss?:\/\//i.test(String(input)))
259
+ throw new UnsafeDestinationError("unsupported scheme");
260
+ return assertPublicUrlShape(asHttpUrl(input), policy);
261
+ }
262
+ const NO_BODY = new Set([101, 204, 205, 304]);
263
+ function requestOnce(url, init, method, body, signal, allow) {
264
+ const lookup = guardedLookup(hostOf(url), allow);
265
+ const lib = url.protocol === "https:" ? node_https_1.default : node_http_1.default;
266
+ return new Promise((resolve, reject) => {
267
+ var _a;
268
+ const req = lib.request(url, {
269
+ method,
270
+ headers: Object.assign(Object.assign({}, ((_a = init.headers) !== null && _a !== void 0 ? _a : {})), (body !== undefined
271
+ ? { "content-length": String(Buffer.byteLength(body)) }
272
+ : {})),
273
+ lookup,
274
+ signal
275
+ }, (res) => {
276
+ var _a;
277
+ const chunks = [];
278
+ let size = 0;
279
+ const max = (_a = init.maxBytes) !== null && _a !== void 0 ? _a : 5 * 1024 * 1024;
280
+ res.on("data", (c) => {
281
+ size += c.length;
282
+ if (size > max) {
283
+ res.destroy(new UnsafeDestinationError("response too large"));
284
+ return;
285
+ }
286
+ chunks.push(c);
287
+ });
288
+ res.on("error", reject);
289
+ res.on("end", () => {
290
+ var _a;
291
+ const headers = new Headers();
292
+ for (const [k, v] of Object.entries(res.headers)) {
293
+ if (v === undefined)
294
+ continue;
295
+ for (const item of Array.isArray(v) ? v : [v])
296
+ headers.append(k, item);
297
+ }
298
+ const status = (_a = res.statusCode) !== null && _a !== void 0 ? _a : 0;
299
+ resolve(new Response(NO_BODY.has(status) ? null : Buffer.concat(chunks), {
300
+ status: status < 200 || status > 599 ? 502 : status,
301
+ statusText: res.statusMessage,
302
+ headers
303
+ }));
304
+ });
305
+ });
306
+ req.on("error", reject);
307
+ if (body !== undefined)
308
+ req.write(body);
309
+ req.end();
310
+ });
311
+ }
312
+ /**
313
+ * fetch for tenant-chosen URLs: checks the destination (and each redirect) with
314
+ * assertPublicDestination, re-checks the addresses the socket connects to, and times out.
315
+ */
316
+ function safeFetch(input_1) {
317
+ return __awaiter(this, arguments, void 0, function* (input, init = {}) {
318
+ var _a, _b, _c;
319
+ const allow = allowList(init.policy);
320
+ const timeout = AbortSignal.timeout((_a = init.timeoutMs) !== null && _a !== void 0 ? _a : 10000);
321
+ const signal = init.signal
322
+ ? AbortSignal.any([init.signal, timeout])
323
+ : timeout;
324
+ let url = yield assertPublicDestination(input, { allow });
325
+ let method = ((_b = init.method) !== null && _b !== void 0 ? _b : "GET").toUpperCase();
326
+ let body = init.body;
327
+ const maxRedirects = (_c = init.maxRedirects) !== null && _c !== void 0 ? _c : 3;
328
+ for (let hop = 0;; hop++) {
329
+ const res = yield requestOnce(url, init, method, body, signal, allow);
330
+ const location = res.headers.get("location");
331
+ if (![301, 302, 303, 307, 308].includes(res.status) || !location)
332
+ return res;
333
+ if (hop >= maxRedirects)
334
+ throw new UnsafeDestinationError("too many redirects");
335
+ url = yield assertPublicDestination(new node_url_1.URL(location, url), { allow });
336
+ if (res.status === 303 || (res.status <= 302 && method === "POST")) {
337
+ method = "GET";
338
+ body = undefined;
339
+ }
340
+ }
341
+ });
342
+ }
@@ -16,8 +16,4 @@
16
16
  * See the License for the specific language governing permissions and
17
17
  * limitations under the License.
18
18
  */
19
- import { VerbRequest } from "./Verb";
20
- type PlayDtmfRequest = VerbRequest & {
21
- digits: string;
22
- };
23
- export { PlayDtmfRequest };
19
+ export * from "./assertPublicDestination";
@@ -32,16 +32,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
32
32
  * See the License for the specific language governing permissions and
33
33
  * limitations under the License.
34
34
  */
35
- __exportStar(require("./Dial"), exports);
36
- __exportStar(require("./Gather"), exports);
37
- __exportStar(require("./Mute"), exports);
38
- __exportStar(require("./Play"), exports);
39
- __exportStar(require("./PlayDtmf"), exports);
40
- __exportStar(require("./PlaybackControl"), exports);
41
- __exportStar(require("./Record"), exports);
42
- __exportStar(require("./Say"), exports);
43
- __exportStar(require("./Stream"), exports);
44
- __exportStar(require("./StreamGather"), exports);
45
- __exportStar(require("./StopSay"), exports);
46
- __exportStar(require("./Verb"), exports);
47
- __exportStar(require("./voice"), exports);
35
+ __exportStar(require("./assertPublicDestination"), exports);
@@ -37,7 +37,9 @@ function createSendEmail(config) {
37
37
  host,
38
38
  port,
39
39
  secure,
40
- auth
40
+ // Relays without authentication (e.g. a local MailHog) get no auth block;
41
+ // an empty one makes nodemailer fail with "Missing credentials".
42
+ auth: (auth === null || auth === void 0 ? void 0 : auth.user) ? auth : undefined
41
43
  });
42
44
  return function sendEmail(params) {
43
45
  return __awaiter(this, void 0, void 0, function* () {
@@ -39,13 +39,69 @@ service Applications {
39
39
  rpc EvaluateIntelligence(EvaluateIntelligenceRequest) returns (stream EvaluateIntelligenceEvent);
40
40
  // Create an Ephemeral Agent to perform test calls to an application
41
41
  rpc CreateTestToken(google.protobuf.Empty) returns (TestTokenResponse) {}
42
+ // Replace an application's signing secret; the old one stops working at once (no overlap)
43
+ rpc RotateSigningSecret(RotateSigningSecretRequest) returns (RotateSigningSecretResponse) {}
44
+ }
45
+
46
+ // Speech defaults of an application; voices and models are catalog ids (provider/model-or-voice)
47
+ message SpeechDefaults {
48
+ // Default voice, e.g. elevenlabs/rachel
49
+ string voice = 1;
50
+ // Default language (BCP 47), e.g. en-US
51
+ string language = 2;
52
+ // Speech-to-text model, e.g. deepgram/nova-3 (empty: the platform default)
53
+ string stt_model = 3;
54
+ // Text-to-speech model (empty: the voice's default)
55
+ string tts_model = 4;
56
+ // Silence timeout in milliseconds (0: the gateway default)
57
+ int32 silence_timeout_ms = 5;
58
+ // The Pacing policy's patience: low, normal or high (empty: normal)
59
+ string patience = 6;
60
+ }
61
+
62
+ // Caller and agent audio format for the fonoster protocol
63
+ message AudioFormat {
64
+ // pcm16 or pcmu
65
+ string codec = 1;
66
+ // Sample rate in Hz: 8000, 16000 or 24000 (pcmu is 8000 only)
67
+ int32 rate = 2;
68
+ }
69
+
70
+ // Answering-machine detection: one amd.result event per call
71
+ message AmdSettings {
72
+ // Classify the caller's first seconds of speech
73
+ bool enabled = 1;
74
+ // How much caller speech to classify, in milliseconds (0: 4000; 500 to 15000)
75
+ int32 window_ms = 2;
76
+ }
77
+
78
+ // A listen-only socket dialed for every session of the app
79
+ message Listener {
80
+ // ws:// or wss:// address
81
+ string url = 1;
82
+ // inbound, outbound or both
83
+ string tracks = 2;
84
+ }
85
+
86
+ message RotateSigningSecretRequest {
87
+ // The reference to the application
88
+ string ref = 1;
89
+ }
90
+
91
+ message RotateSigningSecretResponse {
92
+ // The reference to the application
93
+ string ref = 1;
94
+ // The new secret; shown only in this response
95
+ string signing_secret = 2;
96
+ // The last characters of the secret
97
+ string signing_secret_hint = 3;
42
98
  }
43
99
 
44
100
  // The type of application
45
101
  enum ApplicationType {
46
- // Programmable Voice
47
- EXTERNAL = 0;
48
- // Internal LLM based applications
102
+ // A stream: your own WebSocket server
103
+ STREAM = 0;
104
+ // An Autopilot agent (hosted by Fonoster)
49
105
  AUTOPILOT = 1;
50
106
  }
51
107
 
@@ -78,6 +134,26 @@ message Application {
78
134
  int32 created_at = 8;
79
135
  // Update time
80
136
  int32 updated_at = 9;
137
+ // The WebSocket URL the gateway dials
138
+ string url = 10;
139
+ // Stream protocol: fonoster or twilio
140
+ string protocol = 11;
141
+ // The last characters of the signing secret (the secret is never returned on reads)
142
+ string signing_secret_hint = 12;
143
+ // Speech defaults
144
+ SpeechDefaults speech = 13;
145
+ // Audio format (fonoster protocol)
146
+ AudioFormat audio = 14;
147
+ // App used when this one cannot be reached
148
+ string fallback_app_ref = 15;
149
+ // Message spoken when neither the app nor its fallback can be reached
150
+ string fallback_message = 16;
151
+ // Listen-only sockets
152
+ repeated Listener listeners = 17;
153
+ // Recording policy: off, on or consent (empty: the workspace's)
154
+ string recording = 18;
155
+ // Answering-machine detection (off when unset)
156
+ AmdSettings amd = 19;
81
157
  }
82
158
 
83
159
  // Request to create a new application
@@ -94,12 +170,34 @@ message CreateApplicationRequest {
94
170
  ProductContainer speech_to_text = 5;
95
171
  // Inference engine
96
172
  ProductContainer intelligence = 6;
173
+ // The WebSocket URL (Autopilot agents default to the hosted Autopilot)
174
+ string url = 7;
175
+ // fonoster or twilio (default fonoster)
176
+ string protocol = 8;
177
+ // Speech defaults
178
+ SpeechDefaults speech = 9;
179
+ // Audio format (fonoster protocol)
180
+ AudioFormat audio = 10;
181
+ // App used when this one cannot be reached
182
+ string fallback_app_ref = 11;
183
+ // Message spoken when neither the app nor its fallback can be reached
184
+ string fallback_message = 12;
185
+ // Listen-only sockets
186
+ repeated Listener listeners = 13;
187
+ // Recording policy: off, on or consent (empty: the workspace's)
188
+ string recording = 14;
189
+ // Answering-machine detection (off when unset)
190
+ AmdSettings amd = 15;
97
191
  }
98
192
 
99
193
  // Response for create application
100
194
  message CreateApplicationResponse {
101
195
  // Echo the reference of the application
102
196
  string ref = 1;
197
+ // The app's signing secret; shown only in this response
198
+ string signing_secret = 2;
199
+ // The last characters of the secret
200
+ string signing_secret_hint = 3;
103
201
  }
104
202
 
105
203
  // Request to get an application
@@ -140,12 +238,35 @@ message UpdateApplicationRequest {
140
238
  ProductContainer speech_to_text = 6;
141
239
  // Intelligence product
142
240
  ProductContainer intelligence = 7;
241
+ // The WebSocket URL (Autopilot agents default to the hosted Autopilot)
242
+ string url = 8;
243
+ // fonoster or twilio (default fonoster)
244
+ string protocol = 9;
245
+ // Speech defaults
246
+ SpeechDefaults speech = 10;
247
+ // Audio format (fonoster protocol)
248
+ AudioFormat audio = 11;
249
+ // App used when this one cannot be reached
250
+ string fallback_app_ref = 12;
251
+ // Message spoken when neither the app nor its fallback can be reached
252
+ string fallback_message = 13;
253
+ // Listen-only sockets
254
+ repeated Listener listeners = 14;
255
+ // Recording policy: off, on or consent (empty: the workspace's)
256
+ string recording = 15;
257
+ // Answering-machine detection (off when unset)
258
+ AmdSettings amd = 16;
143
259
  }
144
260
 
145
261
  // Response for update application
146
262
  message UpdateApplicationResponse {
147
263
  // Echo the reference of the application
148
264
  string ref = 1;
265
+ // A new signing secret when the update generated one (the app stopped being an Autopilot
266
+ // app); shown only in this response. Empty otherwise.
267
+ string signing_secret = 2;
268
+ // The last characters of the new secret (empty when there is none)
269
+ string signing_secret_hint = 3;
149
270
  }
150
271
 
151
272
  // Request to delete an application
@@ -23,19 +23,38 @@ package fonoster.authz.v1beta2;
23
23
  import "google/protobuf/struct.proto";
24
24
  import "google/protobuf/empty.proto";
25
25
 
26
- import "voice.proto";
27
-
28
26
  // Authentication service definition
29
27
  service Authz {
30
28
  // Check if a user has permission to perform an action
31
- rpc CheckSessionAuthorized (fonoster.voice.v1beta2.CreateSessionRequest) returns (CheckSessionAuthorizedResponse) {}
29
+ rpc CheckSessionAuthorized (CheckSessionAuthorizedRequest) returns (CheckSessionAuthorizedResponse) {}
32
30
  // Check if a given accessKeyId has permission to access a method
33
31
  rpc CheckMethodAuthorized (CheckMethodAuthorizedRequest) returns (CheckMethodAuthorizedResponse) {}
34
32
  // Add billing meter event to an account
35
33
  rpc AddBillingMeterEvent (AddBillingMeterEventRequest) returns (google.protobuf.Empty) {}
36
34
  }
37
35
 
38
- // Response message for the CheckVoiceRequestAuthorized method
36
+ // The call a session would serve. Field numbers match the retired
37
+ // fonoster.voice.v1beta2.CreateSessionRequest, so existing authorization servers keep decoding it.
38
+ message CheckSessionAuthorizedRequest {
39
+ string access_key_id = 1;
40
+ string app_ref = 2;
41
+ string endpoint = 3;
42
+ string ingress_number = 4;
43
+ string caller_name = 5;
44
+ string caller_number = 6;
45
+ string media_session_ref = 7;
46
+ string session_token = 8;
47
+ enum CallDirection {
48
+ FROM_PSTN = 0;
49
+ TO_PSTN = 1;
50
+ INTRA_NETWORK = 2;
51
+ }
52
+ CallDirection call_direction = 9;
53
+ map<string, string> metadata = 10;
54
+ string call_ref = 11;
55
+ }
56
+
57
+ // Response message for the CheckSessionAuthorized method
39
58
  message CheckSessionAuthorizedResponse {
40
59
  // The authorization server will look for one or more parameters in the request
41
60
  // to determine if the request is authorized. If the request is authorized, the
@@ -0,0 +1,94 @@
1
+ /*
2
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
3
+ * http://github.com/fonoster/fonoster
4
+ *
5
+ * This file is part of Fonoster
6
+ *
7
+ * Licensed under the MIT License (the "License");
8
+ * you may not use this file except in compliance with
9
+ * the License. You may obtain a copy of the License at
10
+ *
11
+ * https://opensource.org/licenses/MIT
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+ syntax = "proto3";
20
+
21
+ package fonoster.capabilities.v1beta2;
22
+
23
+ import "google/protobuf/empty.proto";
24
+
25
+ // What this deployment supports, from fonoster.yaml. Public (no credentials
26
+ // needed) so sign-in screens can render from it; it never returns secrets.
27
+ service Capabilities {
28
+ // Gets the deployment's capabilities
29
+ rpc GetCapabilities (google.protobuf.Empty) returns (GetCapabilitiesResponse) {}
30
+ }
31
+
32
+ message GitHubCapability {
33
+ string client_id = 1;
34
+ string authorize_url = 2;
35
+ }
36
+
37
+ message AuthCapabilities {
38
+ // Enabled sign-in methods: password | email_code | github
39
+ repeated string methods = 1;
40
+ // Who may create an account: invite | open
41
+ string signup = 2;
42
+ // Set when github is among the methods
43
+ GitHubCapability github = 3;
44
+ }
45
+
46
+ message BillingCapabilities {
47
+ bool enabled = 1;
48
+ }
49
+
50
+ message CostsCapabilities {
51
+ // none | usage | prices
52
+ string display = 1;
53
+ }
54
+
55
+ // Private regions (customer-run regions enrolled through the region hub).
56
+ // The local public region always exists and is not reported here.
57
+ message RegionsCapabilities {
58
+ // True when the operator configured the region hub and the region-signing
59
+ // key (regions.hub and regions.signingKey), so workspaces can enroll
60
+ // private regions. Never carries hub URLs or keys.
61
+ bool private = 1;
62
+ }
63
+
64
+ message SipEndpoint {
65
+ string host = 1;
66
+ int32 port = 2;
67
+ // udp | tcp | tls | ws | wss
68
+ string transport = 3;
69
+ }
70
+
71
+ message PortRange {
72
+ int32 min = 1;
73
+ int32 max = 2;
74
+ }
75
+
76
+ // Where carriers and softphones reach this deployment: the default public
77
+ // region's endpoints (regions.public in fonoster.yaml). Addresses and ports
78
+ // only, never credentials. Empty when the operator listed none.
79
+ message ConnectionCapabilities {
80
+ repeated SipEndpoint sip = 1;
81
+ repeated string signaling_addresses = 2;
82
+ repeated string media_addresses = 3;
83
+ PortRange rtp_ports = 4;
84
+ }
85
+
86
+ message GetCapabilitiesResponse {
87
+ AuthCapabilities auth = 1;
88
+ BillingCapabilities billing = 2;
89
+ CostsCapabilities costs = 3;
90
+ string domain = 4;
91
+ string app_url = 5;
92
+ RegionsCapabilities regions = 6;
93
+ ConnectionCapabilities connection = 7;
94
+ }