@fonoster/common 0.27.0 → 0.31.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.
- package/dist/{voice/PlayDtmf.d.ts → agents/index.d.ts} +3 -5
- package/dist/{assistants → agents}/index.js +2 -6
- package/dist/agents/sessionAgentSchema.d.ts +755 -0
- package/dist/agents/sessionAgentSchema.js +117 -0
- package/dist/{voice/Dial.d.ts → calls/index.d.ts} +16 -13
- package/dist/calls/index.js +44 -0
- package/dist/cellsecrets/index.d.ts +91 -0
- package/dist/cellsecrets/index.js +299 -0
- package/dist/config/cli/check.d.ts +6 -0
- package/dist/config/cli/check.js +202 -0
- package/dist/config/cli/envFile.d.ts +23 -0
- package/dist/config/cli/envFile.js +81 -0
- package/dist/config/cli/exposure.d.ts +10 -0
- package/dist/config/cli/exposure.js +93 -0
- package/dist/config/cli/index.d.ts +2 -0
- package/dist/{identity/tokenHasAccessKeyId.js → config/cli/index.js} +7 -8
- package/dist/config/cli/info.d.ts +6 -0
- package/dist/config/cli/info.js +62 -0
- package/dist/config/cli/init.d.ts +9 -0
- package/dist/config/cli/init.js +152 -0
- package/dist/config/cli/main.d.ts +11 -0
- package/dist/config/cli/main.js +155 -0
- package/dist/config/cli/render.d.ts +44 -0
- package/dist/config/cli/render.js +129 -0
- package/dist/config/cli/secrets.d.ts +21 -0
- package/dist/config/cli/secrets.js +117 -0
- package/dist/config/cli/types.d.ts +24 -0
- package/dist/config/cli/types.js +7 -0
- package/dist/{assistants/index.d.ts → config/configHints.d.ts} +8 -7
- package/dist/config/configHints.js +47 -0
- package/dist/{voice → config}/index.d.ts +11 -13
- package/dist/{voice → config}/index.js +11 -13
- package/dist/config/interpolateEnv.d.ts +34 -0
- package/dist/config/interpolateEnv.js +70 -0
- package/dist/config/loadServiceConfig.d.ts +28 -0
- package/dist/config/loadServiceConfig.js +112 -0
- package/dist/config/logsSchema.d.ts +76 -0
- package/dist/config/logsSchema.js +49 -0
- package/dist/config/natsConnection.d.ts +53 -0
- package/dist/config/natsConnection.js +58 -0
- package/dist/config/parseConfigFile.d.ts +7 -0
- package/dist/config/parseConfigFile.js +60 -0
- package/dist/config/resolveConfigFile.d.ts +17 -0
- package/dist/config/resolveConfigFile.js +54 -0
- package/dist/config/resolveSecretRefs.d.ts +35 -0
- package/dist/config/resolveSecretRefs.js +129 -0
- package/dist/{assistants/audioFiltersSchema.d.ts → config/strictDeep.d.ts} +9 -12
- package/dist/config/strictDeep.js +58 -0
- package/dist/config/telemetrySchema.d.ts +39 -0
- package/dist/config/telemetrySchema.js +44 -0
- package/dist/config/toJsonSchema.d.ts +30 -0
- package/dist/config/toJsonSchema.js +161 -0
- package/dist/constants.d.ts +1 -6
- package/dist/constants.js +4 -7
- package/dist/envs.d.ts +2 -1
- package/dist/envs.js +13 -11
- package/dist/errors/handleZodError.js +3 -1
- package/dist/filters/index.d.ts +70 -0
- package/dist/filters/index.js +101 -0
- package/dist/identity/createAuthInterceptor.d.ts +9 -2
- package/dist/identity/createAuthInterceptor.js +36 -30
- package/dist/identity/hasAccess.d.ts +9 -3
- package/dist/identity/hasAccess.js +32 -8
- package/dist/identity/index.d.ts +1 -0
- package/dist/identity/index.js +1 -0
- package/dist/identity/isValidToken.d.ts +12 -2
- package/dist/identity/isValidToken.js +25 -9
- package/dist/identity/roles.js +33 -7
- package/dist/identity/types.d.ts +6 -1
- package/dist/identity/types.js +1 -0
- package/dist/{assistants/LanguageModelProvider.js → identity/verifyAccessToken.d.ts} +10 -11
- package/dist/identity/verifyAccessToken.js +44 -0
- package/dist/index.d.ts +15 -2
- package/dist/index.js +18 -3
- package/dist/net/assertPublicDestination.d.ts +52 -0
- package/dist/net/assertPublicDestination.js +342 -0
- package/dist/{voice/StopSay.d.ts → net/index.d.ts} +1 -6
- package/dist/net/index.js +35 -0
- package/dist/notifications/createSendEmail.js +3 -1
- package/dist/protos/applications.proto +138 -3
- package/dist/protos/authz.proto +26 -4
- package/dist/protos/billing.proto +92 -0
- package/dist/protos/calls.proto +4 -0
- package/dist/protos/capabilities.proto +102 -0
- package/dist/protos/identity.proto +26 -1
- package/dist/protos/providers.proto +139 -0
- package/dist/protos/roomr.proto +146 -0
- package/dist/protos/session.proto +194 -0
- package/dist/protos/usage.proto +59 -0
- package/dist/protos/webhooks.proto +140 -0
- package/dist/{utils/types.d.ts → regions/hub/index.d.ts} +1 -5
- package/dist/regions/hub/index.js +35 -0
- package/dist/regions/hub/natsJwt.d.ts +63 -0
- package/dist/regions/hub/natsJwt.js +195 -0
- package/dist/registry/Registry.d.ts +87 -0
- package/dist/registry/Registry.js +432 -0
- package/dist/registry/cli.d.ts +20 -0
- package/dist/registry/cli.js +436 -0
- package/dist/registry/features.d.ts +31 -0
- package/dist/registry/features.js +38 -0
- package/dist/registry/index.d.ts +24 -0
- package/dist/registry/index.js +40 -0
- package/dist/registry/prisma.d.ts +67 -0
- package/dist/registry/prisma.js +399 -0
- package/dist/registry/stores.d.ts +21 -0
- package/dist/{assistants/languageModelConfigSchema.js → registry/stores.js} +55 -38
- package/dist/registry/types.d.ts +110 -0
- package/dist/{assistants/LanguageModelProvider.d.ts → registry/types.js} +6 -8
- package/dist/registry/workspaceKeys.d.ts +41 -0
- package/dist/registry/workspaceKeys.js +64 -0
- package/dist/roomr/health.d.ts +9 -0
- package/dist/{assistants/eventsHookSchema.js → roomr/health.js} +22 -18
- package/dist/roomr/index.d.ts +75 -0
- package/dist/roomr/index.js +146 -0
- package/dist/roomr/session.d.ts +133 -0
- package/dist/roomr/session.js +132 -0
- package/dist/session/index.d.ts +19 -0
- package/dist/session/index.js +35 -0
- package/dist/session/schema.d.ts +2140 -0
- package/dist/session/schema.js +448 -0
- package/dist/sessions/index.d.ts +96 -0
- package/dist/sessions/index.js +197 -0
- package/dist/telemetry.d.ts +76 -0
- package/dist/telemetry.js +360 -0
- package/dist/types.d.ts +3 -5
- package/dist/{voice/Gather.d.ts → usage/CallUsage.d.ts} +19 -18
- package/dist/usage/CallUsage.js +76 -0
- package/dist/usage/index.d.ts +22 -0
- package/dist/usage/index.js +38 -0
- package/dist/usage/meters.d.ts +113 -0
- package/dist/usage/meters.js +90 -0
- package/dist/usage/types.d.ts +159 -0
- package/dist/usage/types.js +84 -0
- package/dist/usage/usageId.d.ts +6 -0
- package/dist/usage/usageId.js +45 -0
- package/dist/utils/{findIntegrationsCredentials.js → WriteBehind.d.ts} +13 -10
- package/dist/utils/WriteBehind.js +64 -0
- package/dist/utils/index.d.ts +1 -3
- package/dist/utils/index.js +1 -3
- package/dist/utils/{createFetchSingleCallByCallId.d.ts → logUnhandledRejections.d.ts} +8 -4
- package/dist/{assistants/audioFiltersSchema.js → utils/logUnhandledRejections.js} +17 -12
- package/dist/utils/withErrorHandling.js +1 -1
- package/dist/validators/calls.d.ts +3 -0
- package/dist/validators/calls.js +12 -2
- package/dist/validators/identity.d.ts +37 -8
- package/dist/validators/identity.js +22 -3
- package/dist/validators/index.d.ts +1 -0
- package/dist/validators/index.js +1 -0
- package/dist/validators/sip.d.ts +40 -0
- package/dist/validators/sip.js +99 -0
- package/dist/validators/sipnet/credentials.d.ts +2 -2
- package/dist/validators/sipnet/domains.js +7 -7
- package/dist/validators/sipnet/trunks.d.ts +14 -14
- package/dist/validators/sipnet/trunks.js +4 -2
- package/dist/webhooks/Dispatcher.d.ts +87 -0
- package/dist/webhooks/Dispatcher.js +225 -0
- package/dist/webhooks/bus.d.ts +68 -0
- package/dist/webhooks/bus.js +178 -0
- package/dist/webhooks/events.d.ts +66 -0
- package/dist/webhooks/events.js +62 -0
- package/dist/webhooks/index.d.ts +23 -0
- package/dist/webhooks/index.js +39 -0
- package/dist/webhooks/prisma.d.ts +55 -0
- package/dist/webhooks/prisma.js +242 -0
- package/dist/webhooks/signing.d.ts +8 -0
- package/dist/webhooks/signing.js +53 -0
- package/package.json +25 -11
- package/schema/session.v1.json +871 -0
- package/dist/assistants/assistantSchema.d.ts +0 -739
- package/dist/assistants/assistantSchema.js +0 -37
- package/dist/assistants/conversationSettingsSchema.d.ts +0 -122
- package/dist/assistants/conversationSettingsSchema.js +0 -133
- package/dist/assistants/eventsHookSchema.d.ts +0 -38
- package/dist/assistants/languageModelConfigSchema.d.ts +0 -206
- package/dist/identity/tokenHasAccessKeyId.d.ts +0 -2
- package/dist/protos/voice.proto +0 -557
- package/dist/utils/createFetchSingleCallByCallId.js +0 -33
- package/dist/utils/findIntegrationsCredentials.d.ts +0 -23
- package/dist/utils/types.js +0 -2
- package/dist/voice/Dial.js +0 -20
- package/dist/voice/Gather.js +0 -9
- package/dist/voice/Mute.d.ts +0 -31
- package/dist/voice/Mute.js +0 -9
- package/dist/voice/Play.d.ts +0 -30
- package/dist/voice/Play.js +0 -2
- package/dist/voice/PlayDtmf.js +0 -2
- package/dist/voice/PlaybackControl.d.ts +0 -31
- package/dist/voice/PlaybackControl.js +0 -11
- package/dist/voice/Record.d.ts +0 -36
- package/dist/voice/Record.js +0 -7
- package/dist/voice/Say.d.ts +0 -28
- package/dist/voice/Say.js +0 -2
- package/dist/voice/StopSay.js +0 -2
- package/dist/voice/Stream.d.ts +0 -53
- package/dist/voice/Stream.js +0 -19
- package/dist/voice/StreamGather.d.ts +0 -34
- package/dist/voice/StreamGather.js +0 -9
- package/dist/voice/Verb.d.ts +0 -27
- package/dist/voice/Verb.js +0 -2
- package/dist/voice/voice.d.ts +0 -171
- package/dist/voice/voice.js +0 -65
- package/dist/{assistants → agents}/testCasesSchema.d.ts +4 -4
- /package/dist/{assistants → agents}/testCasesSchema.js +0 -0
- /package/dist/{assistants → agents}/tools/index.d.ts +0 -0
- /package/dist/{assistants → agents}/tools/index.js +0 -0
- /package/dist/{assistants → agents}/tools/propertySchema.d.ts +0 -0
- /package/dist/{assistants → agents}/tools/propertySchema.js +0 -0
- /package/dist/{assistants → agents}/tools/toolSchema.d.ts +0 -0
- /package/dist/{assistants → agents}/tools/toolSchema.js +0 -0
|
@@ -20,7 +20,7 @@ import { Transport } from "@fonoster/types";
|
|
|
20
20
|
import { z } from "zod";
|
|
21
21
|
declare const createTrunkRequestSchema: z.ZodObject<{
|
|
22
22
|
name: z.ZodString;
|
|
23
|
-
sendRegister: z.ZodBoolean
|
|
23
|
+
sendRegister: z.ZodOptional<z.ZodBoolean>;
|
|
24
24
|
inboundUri: z.ZodEffects<z.ZodString, string, string>;
|
|
25
25
|
uris: z.ZodArray<z.ZodObject<{
|
|
26
26
|
host: z.ZodEffects<z.ZodString, string, string>;
|
|
@@ -29,49 +29,49 @@ declare const createTrunkRequestSchema: z.ZodObject<{
|
|
|
29
29
|
user: z.ZodOptional<z.ZodString>;
|
|
30
30
|
weight: z.ZodNumber;
|
|
31
31
|
priority: z.ZodNumber;
|
|
32
|
-
enabled: z.ZodBoolean
|
|
32
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
33
33
|
}, "strip", z.ZodTypeAny, {
|
|
34
|
+
transport?: Transport;
|
|
34
35
|
host?: string;
|
|
35
36
|
port?: number;
|
|
36
|
-
priority?: number;
|
|
37
|
-
enabled?: boolean;
|
|
38
|
-
transport?: Transport;
|
|
39
37
|
user?: string;
|
|
38
|
+
enabled?: boolean;
|
|
40
39
|
weight?: number;
|
|
40
|
+
priority?: number;
|
|
41
41
|
}, {
|
|
42
|
+
transport?: Transport;
|
|
42
43
|
host?: string;
|
|
43
44
|
port?: number;
|
|
44
|
-
priority?: number;
|
|
45
|
-
enabled?: boolean;
|
|
46
|
-
transport?: Transport;
|
|
47
45
|
user?: string;
|
|
46
|
+
enabled?: boolean;
|
|
48
47
|
weight?: number;
|
|
48
|
+
priority?: number;
|
|
49
49
|
}>, "many">;
|
|
50
50
|
}, "strip", z.ZodTypeAny, {
|
|
51
51
|
name?: string;
|
|
52
52
|
sendRegister?: boolean;
|
|
53
53
|
inboundUri?: string;
|
|
54
54
|
uris?: {
|
|
55
|
+
transport?: Transport;
|
|
55
56
|
host?: string;
|
|
56
57
|
port?: number;
|
|
57
|
-
priority?: number;
|
|
58
|
-
enabled?: boolean;
|
|
59
|
-
transport?: Transport;
|
|
60
58
|
user?: string;
|
|
59
|
+
enabled?: boolean;
|
|
61
60
|
weight?: number;
|
|
61
|
+
priority?: number;
|
|
62
62
|
}[];
|
|
63
63
|
}, {
|
|
64
64
|
name?: string;
|
|
65
65
|
sendRegister?: boolean;
|
|
66
66
|
inboundUri?: string;
|
|
67
67
|
uris?: {
|
|
68
|
+
transport?: Transport;
|
|
68
69
|
host?: string;
|
|
69
70
|
port?: number;
|
|
70
|
-
priority?: number;
|
|
71
|
-
enabled?: boolean;
|
|
72
|
-
transport?: Transport;
|
|
73
71
|
user?: string;
|
|
72
|
+
enabled?: boolean;
|
|
74
73
|
weight?: number;
|
|
74
|
+
priority?: number;
|
|
75
75
|
}[];
|
|
76
76
|
}>;
|
|
77
77
|
declare const updateTrunkRequestSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
|
|
@@ -61,9 +61,11 @@ const hostOrIPSchema = zod_1.z
|
|
|
61
61
|
.refine((host) => Validator.isIP(host, 4) || Validator.isFQDN(host), {
|
|
62
62
|
message: "Must be a valid IP or FQDN"
|
|
63
63
|
});
|
|
64
|
+
// Booleans are optional: the gRPC loader drops proto3 defaults, so `false`
|
|
65
|
+
// arrives as a missing field.
|
|
64
66
|
const createTrunkRequestSchema = zod_1.z.object({
|
|
65
67
|
name: common_1.nameSchema,
|
|
66
|
-
sendRegister: zod_1.z.boolean(),
|
|
68
|
+
sendRegister: zod_1.z.boolean().optional(),
|
|
67
69
|
inboundUri: hostOrIPSchema,
|
|
68
70
|
uris: zod_1.z.array(zod_1.z.object({
|
|
69
71
|
host: hostOrIPSchema,
|
|
@@ -72,7 +74,7 @@ const createTrunkRequestSchema = zod_1.z.object({
|
|
|
72
74
|
user: zod_1.z.string().optional(),
|
|
73
75
|
weight: zod_1.z.number(),
|
|
74
76
|
priority: zod_1.z.number(),
|
|
75
|
-
enabled: zod_1.z.boolean()
|
|
77
|
+
enabled: zod_1.z.boolean().optional()
|
|
76
78
|
}))
|
|
77
79
|
});
|
|
78
80
|
exports.createTrunkRequestSchema = createTrunkRequestSchema;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { WebhookEvent, WebhookEventType } from "./events";
|
|
2
|
+
export type WebhookEndpoint = {
|
|
3
|
+
id: string;
|
|
4
|
+
workspaceRef: string;
|
|
5
|
+
url: string;
|
|
6
|
+
events: WebhookEventType[];
|
|
7
|
+
secret: string;
|
|
8
|
+
/** The previous secret stays valid until `previousUntil` (rotation window). */
|
|
9
|
+
previousSecret?: string;
|
|
10
|
+
previousUntil?: number;
|
|
11
|
+
};
|
|
12
|
+
export type Delivery = {
|
|
13
|
+
id: string;
|
|
14
|
+
endpointId: string;
|
|
15
|
+
event: WebhookEvent;
|
|
16
|
+
status: "pending" | "succeeded" | "failed";
|
|
17
|
+
attempts: number;
|
|
18
|
+
firstAttemptAt?: number;
|
|
19
|
+
lastAttemptAt?: number;
|
|
20
|
+
lastStatus?: number;
|
|
21
|
+
nextAttemptAt: number;
|
|
22
|
+
};
|
|
23
|
+
export interface WebhookStore {
|
|
24
|
+
endpoints(workspaceRef?: string): WebhookEndpoint[];
|
|
25
|
+
putEndpoint(e: WebhookEndpoint): void;
|
|
26
|
+
deleteEndpoint(id: string): void;
|
|
27
|
+
deliveries(endpointId?: string): Delivery[];
|
|
28
|
+
putDelivery(d: Delivery): void;
|
|
29
|
+
}
|
|
30
|
+
export declare class MemoryWebhookStore implements WebhookStore {
|
|
31
|
+
private e;
|
|
32
|
+
private d;
|
|
33
|
+
endpoints(workspaceRef?: string): WebhookEndpoint[];
|
|
34
|
+
putEndpoint(x: WebhookEndpoint): void;
|
|
35
|
+
deleteEndpoint(id: string): void;
|
|
36
|
+
deliveries(endpointId?: string): Delivery[];
|
|
37
|
+
putDelivery(x: Delivery): void;
|
|
38
|
+
}
|
|
39
|
+
export declare class WebhookError extends Error {
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The id of an event's delivery to one endpoint. It depends only on the two, so the same event
|
|
43
|
+
* fanned out again (a bus message redelivered after a replica died, or published twice) finds
|
|
44
|
+
* the delivery it already made instead of making a second one (ST-2).
|
|
45
|
+
*/
|
|
46
|
+
export declare function deliveryId(eventId: string, endpointId: string): string;
|
|
47
|
+
export type DispatcherOptions = {
|
|
48
|
+
store: WebhookStore;
|
|
49
|
+
/** Per-attempt timeout (design OQ3 proposal: 10 s). */
|
|
50
|
+
attemptTimeoutMs?: number;
|
|
51
|
+
/** First retry delay; each next one doubles, capped at maxDelayMs (OQ2: from 10 s). */
|
|
52
|
+
firstDelayMs?: number;
|
|
53
|
+
maxDelayMs?: number;
|
|
54
|
+
/** Retries stop this long after the first attempt (OQ2: 24 h). */
|
|
55
|
+
retryPeriodMs?: number;
|
|
56
|
+
/** Lets tests use plain http:// receivers. Never in production. */
|
|
57
|
+
allowInsecureUrls?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Private destinations (host names, IPs, CIDRs) endpoints may use; defaults to
|
|
60
|
+
* FONOSTER_ALLOWED_DESTINATIONS. Everything else must be public (RV-B2).
|
|
61
|
+
*/
|
|
62
|
+
allowedDestinations?: string[];
|
|
63
|
+
now?: () => number;
|
|
64
|
+
};
|
|
65
|
+
export declare class WebhookDispatcher {
|
|
66
|
+
private readonly options;
|
|
67
|
+
constructor(options: DispatcherOptions);
|
|
68
|
+
private policy;
|
|
69
|
+
private now;
|
|
70
|
+
/** Registers an endpoint; the returned secret is the only time it is shown. */
|
|
71
|
+
createEndpoint(input: {
|
|
72
|
+
workspaceRef: string;
|
|
73
|
+
url: string;
|
|
74
|
+
events: WebhookEventType[];
|
|
75
|
+
}): WebhookEndpoint;
|
|
76
|
+
/** New secret; the old one keeps verifying for `windowMs`. Returns the new secret. */
|
|
77
|
+
rotateSecret(endpointId: string, windowMs?: number): string;
|
|
78
|
+
/** The deliveries an event makes, one per subscribed endpoint of its workspace (not stored). */
|
|
79
|
+
plan(event: WebhookEvent): Delivery[];
|
|
80
|
+
/** Fans an event out to every endpoint of its workspace subscribed to its type. */
|
|
81
|
+
publish(event: WebhookEvent): Delivery[];
|
|
82
|
+
/** Attempts every pending delivery that is due; returns how many were attempted. */
|
|
83
|
+
runDue(): Promise<number>;
|
|
84
|
+
/** Sends a delivery again now, with the original event id and a fresh signature. */
|
|
85
|
+
resend(deliveryId: string): Promise<Delivery>;
|
|
86
|
+
private attempt;
|
|
87
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.WebhookDispatcher = exports.WebhookError = exports.MemoryWebhookStore = void 0;
|
|
13
|
+
exports.deliveryId = deliveryId;
|
|
14
|
+
/**
|
|
15
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
16
|
+
* http://github.com/fonoster/fonoster
|
|
17
|
+
*
|
|
18
|
+
* This file is part of Fonoster
|
|
19
|
+
*
|
|
20
|
+
* Licensed under the MIT License (the "License");
|
|
21
|
+
* you may not use this file except in compliance with
|
|
22
|
+
* the License. You may obtain a copy of the License at
|
|
23
|
+
*
|
|
24
|
+
* https://opensource.org/licenses/MIT
|
|
25
|
+
*
|
|
26
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
27
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
28
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
29
|
+
* See the License for the specific language governing permissions and
|
|
30
|
+
* limitations under the License.
|
|
31
|
+
*/
|
|
32
|
+
const crypto_1 = require("crypto");
|
|
33
|
+
const url_1 = require("url");
|
|
34
|
+
const net_1 = require("../net");
|
|
35
|
+
const events_1 = require("./events");
|
|
36
|
+
const signing_1 = require("./signing");
|
|
37
|
+
class MemoryWebhookStore {
|
|
38
|
+
constructor() {
|
|
39
|
+
this.e = new Map();
|
|
40
|
+
this.d = new Map();
|
|
41
|
+
}
|
|
42
|
+
endpoints(workspaceRef) {
|
|
43
|
+
return [...this.e.values()].filter((x) => !workspaceRef || x.workspaceRef === workspaceRef);
|
|
44
|
+
}
|
|
45
|
+
putEndpoint(x) {
|
|
46
|
+
this.e.set(x.id, x);
|
|
47
|
+
}
|
|
48
|
+
deleteEndpoint(id) {
|
|
49
|
+
this.e.delete(id);
|
|
50
|
+
}
|
|
51
|
+
deliveries(endpointId) {
|
|
52
|
+
return [...this.d.values()].filter((x) => !endpointId || x.endpointId === endpointId);
|
|
53
|
+
}
|
|
54
|
+
putDelivery(x) {
|
|
55
|
+
this.d.set(x.id, x);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.MemoryWebhookStore = MemoryWebhookStore;
|
|
59
|
+
class WebhookError extends Error {
|
|
60
|
+
}
|
|
61
|
+
exports.WebhookError = WebhookError;
|
|
62
|
+
/**
|
|
63
|
+
* The id of an event's delivery to one endpoint. It depends only on the two, so the same event
|
|
64
|
+
* fanned out again (a bus message redelivered after a replica died, or published twice) finds
|
|
65
|
+
* the delivery it already made instead of making a second one (ST-2).
|
|
66
|
+
*/
|
|
67
|
+
function deliveryId(eventId, endpointId) {
|
|
68
|
+
const h = (0, crypto_1.createHash)("sha256")
|
|
69
|
+
.update(`${eventId}\0${endpointId}`)
|
|
70
|
+
.digest("hex");
|
|
71
|
+
return `whd_${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
|
|
72
|
+
}
|
|
73
|
+
class WebhookDispatcher {
|
|
74
|
+
constructor(options) {
|
|
75
|
+
this.options = options;
|
|
76
|
+
}
|
|
77
|
+
policy() {
|
|
78
|
+
return this.options.allowedDestinations
|
|
79
|
+
? { allow: this.options.allowedDestinations }
|
|
80
|
+
: undefined;
|
|
81
|
+
}
|
|
82
|
+
now() {
|
|
83
|
+
var _a, _b, _c;
|
|
84
|
+
return (_c = (_b = (_a = this.options).now) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : Date.now();
|
|
85
|
+
}
|
|
86
|
+
/** Registers an endpoint; the returned secret is the only time it is shown. */
|
|
87
|
+
createEndpoint(input) {
|
|
88
|
+
let url;
|
|
89
|
+
try {
|
|
90
|
+
url = new url_1.URL(input.url);
|
|
91
|
+
}
|
|
92
|
+
catch (_a) {
|
|
93
|
+
throw new WebhookError("invalid webhook URL");
|
|
94
|
+
}
|
|
95
|
+
if (url.protocol !== "https:" &&
|
|
96
|
+
!(this.options.allowInsecureUrls && url.protocol === "http:")) {
|
|
97
|
+
throw new WebhookError("webhook URLs must use HTTPS");
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
(0, net_1.assertPublicUrlShape)(url, this.policy());
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
throw new WebhookError(`webhook URL not allowed: ${e.message}`);
|
|
104
|
+
}
|
|
105
|
+
const unknown = input.events.filter((e) => !events_1.WEBHOOK_EVENT_TYPES.includes(e));
|
|
106
|
+
const retired = unknown.filter((e) => events_1.RETIRED_EVENT_TYPES[e]);
|
|
107
|
+
if (retired.length)
|
|
108
|
+
throw new WebhookError(`retired event types: ${retired
|
|
109
|
+
.map((e) => `${e} (use ${events_1.RETIRED_EVENT_TYPES[e].join(" or ")})`)
|
|
110
|
+
.join(", ")}; the conversation's transcript and recording are in the call record`);
|
|
111
|
+
if (unknown.length)
|
|
112
|
+
throw new WebhookError(`unknown event types: ${unknown.join(", ")}`);
|
|
113
|
+
const endpoint = {
|
|
114
|
+
id: `whe_${(0, crypto_1.randomUUID)()}`,
|
|
115
|
+
workspaceRef: input.workspaceRef,
|
|
116
|
+
url: input.url,
|
|
117
|
+
events: input.events,
|
|
118
|
+
secret: `whsec_${(0, crypto_1.randomBytes)(24).toString("base64url")}`
|
|
119
|
+
};
|
|
120
|
+
this.options.store.putEndpoint(endpoint);
|
|
121
|
+
return endpoint;
|
|
122
|
+
}
|
|
123
|
+
/** New secret; the old one keeps verifying for `windowMs`. Returns the new secret. */
|
|
124
|
+
rotateSecret(endpointId, windowMs = 24 * 3600 * 1000) {
|
|
125
|
+
const e = this.options.store.endpoints().find((x) => x.id === endpointId);
|
|
126
|
+
if (!e)
|
|
127
|
+
throw new WebhookError(`no endpoint ${endpointId}`);
|
|
128
|
+
const secret = `whsec_${(0, crypto_1.randomBytes)(24).toString("base64url")}`;
|
|
129
|
+
this.options.store.putEndpoint(Object.assign(Object.assign({}, e), { secret, previousSecret: e.secret, previousUntil: this.now() + windowMs }));
|
|
130
|
+
return secret;
|
|
131
|
+
}
|
|
132
|
+
/** The deliveries an event makes, one per subscribed endpoint of its workspace (not stored). */
|
|
133
|
+
plan(event) {
|
|
134
|
+
return this.options.store
|
|
135
|
+
.endpoints(event.workspaceRef)
|
|
136
|
+
.filter((e) => e.events.includes(event.type))
|
|
137
|
+
.map((e) => ({
|
|
138
|
+
id: deliveryId(event.id, e.id),
|
|
139
|
+
endpointId: e.id,
|
|
140
|
+
event,
|
|
141
|
+
status: "pending",
|
|
142
|
+
attempts: 0,
|
|
143
|
+
nextAttemptAt: this.now()
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
/** Fans an event out to every endpoint of its workspace subscribed to its type. */
|
|
147
|
+
publish(event) {
|
|
148
|
+
const deliveries = this.plan(event);
|
|
149
|
+
deliveries.forEach((d) => this.options.store.putDelivery(d));
|
|
150
|
+
return deliveries;
|
|
151
|
+
}
|
|
152
|
+
/** Attempts every pending delivery that is due; returns how many were attempted. */
|
|
153
|
+
runDue() {
|
|
154
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
155
|
+
const due = this.options.store
|
|
156
|
+
.deliveries()
|
|
157
|
+
.filter((d) => d.status === "pending" && d.nextAttemptAt <= this.now());
|
|
158
|
+
yield Promise.all(due.map((d) => this.attempt(d)));
|
|
159
|
+
return due.length;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/** Sends a delivery again now, with the original event id and a fresh signature. */
|
|
163
|
+
resend(deliveryId) {
|
|
164
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
165
|
+
const d = this.options.store.deliveries().find((x) => x.id === deliveryId);
|
|
166
|
+
if (!d)
|
|
167
|
+
throw new WebhookError(`no delivery ${deliveryId}`);
|
|
168
|
+
const again = Object.assign(Object.assign({}, d), { status: "pending", nextAttemptAt: this.now(), firstAttemptAt: this.now() });
|
|
169
|
+
this.options.store.putDelivery(again);
|
|
170
|
+
return this.attempt(again);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
attempt(d) {
|
|
174
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
175
|
+
var _a, _b, _c, _d, _e, _f;
|
|
176
|
+
const endpoint = this.options.store
|
|
177
|
+
.endpoints()
|
|
178
|
+
.find((e) => e.id === d.endpointId);
|
|
179
|
+
if (!endpoint) {
|
|
180
|
+
const failed = Object.assign(Object.assign({}, d), { status: "failed" });
|
|
181
|
+
this.options.store.putDelivery(failed);
|
|
182
|
+
return failed;
|
|
183
|
+
}
|
|
184
|
+
const now = this.now();
|
|
185
|
+
const secrets = [endpoint.secret];
|
|
186
|
+
if (endpoint.previousSecret && ((_a = endpoint.previousUntil) !== null && _a !== void 0 ? _a : 0) > now)
|
|
187
|
+
secrets.push(endpoint.previousSecret);
|
|
188
|
+
const body = JSON.stringify(d.event);
|
|
189
|
+
let status = 0;
|
|
190
|
+
try {
|
|
191
|
+
// Checked again at delivery: DNS may have changed since the endpoint was created.
|
|
192
|
+
const res = yield (0, net_1.safeFetch)(endpoint.url, {
|
|
193
|
+
method: "POST",
|
|
194
|
+
headers: Object.assign({ "content-type": "application/json" }, (0, signing_1.webhookHeaders)(body, secrets, Math.floor(now / 1000))),
|
|
195
|
+
body,
|
|
196
|
+
timeoutMs: (_b = this.options.attemptTimeoutMs) !== null && _b !== void 0 ? _b : 10000,
|
|
197
|
+
maxRedirects: 0,
|
|
198
|
+
policy: this.policy()
|
|
199
|
+
});
|
|
200
|
+
status = res.status;
|
|
201
|
+
yield res.arrayBuffer().catch(() => undefined);
|
|
202
|
+
}
|
|
203
|
+
catch (_g) {
|
|
204
|
+
status = 0;
|
|
205
|
+
}
|
|
206
|
+
const attempts = d.attempts + 1;
|
|
207
|
+
const firstAttemptAt = (_c = d.firstAttemptAt) !== null && _c !== void 0 ? _c : now;
|
|
208
|
+
let next;
|
|
209
|
+
if (status >= 200 && status < 300) {
|
|
210
|
+
next = Object.assign(Object.assign({}, d), { status: "succeeded", attempts,
|
|
211
|
+
firstAttemptAt, lastAttemptAt: now, lastStatus: status });
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
const delay = Math.min(((_d = this.options.firstDelayMs) !== null && _d !== void 0 ? _d : 10000) * Math.pow(2, (attempts - 1)), (_e = this.options.maxDelayMs) !== null && _e !== void 0 ? _e : 3600000);
|
|
215
|
+
const giveUp = now + delay - firstAttemptAt >
|
|
216
|
+
((_f = this.options.retryPeriodMs) !== null && _f !== void 0 ? _f : 24 * 3600000);
|
|
217
|
+
next = Object.assign(Object.assign({}, d), { status: giveUp ? "failed" : "pending", attempts,
|
|
218
|
+
firstAttemptAt, lastAttemptAt: now, lastStatus: status, nextAttemptAt: now + delay });
|
|
219
|
+
}
|
|
220
|
+
this.options.store.putDelivery(next);
|
|
221
|
+
return next;
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
exports.WebhookDispatcher = WebhookDispatcher;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Delivery } from "./Dispatcher";
|
|
2
|
+
import { WebhookEvent } from "./events";
|
|
3
|
+
export declare const WEBHOOK_EVENTS_SUBJECT = "webhooks.events";
|
|
4
|
+
/** The slice of a NATS connection the bus uses (so `@fonoster/common` needs no NATS client). */
|
|
5
|
+
export type BusConnection = {
|
|
6
|
+
publish(subject: string, data: Uint8Array): void;
|
|
7
|
+
subscribe(subject: string, opts: {
|
|
8
|
+
queue?: string;
|
|
9
|
+
callback: (err: Error | null, msg: {
|
|
10
|
+
data: Uint8Array;
|
|
11
|
+
}) => void;
|
|
12
|
+
}): {
|
|
13
|
+
unsubscribe(): void;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
/** A producer that publishes events on the bus instead of delivering them itself. */
|
|
17
|
+
export declare function busWebhookPublisher(nc: Pick<BusConnection, "publish">): (event: WebhookEvent) => void;
|
|
18
|
+
/** Parses a bus message; undefined for anything that is not a v1 webhook event. */
|
|
19
|
+
export declare function parseBusEvent(data: Uint8Array): WebhookEvent | undefined;
|
|
20
|
+
/** Where Roomr nodes publish a call's events (`roomr.events.<call_ref>`; room events have one more token). */
|
|
21
|
+
export declare const ROOMR_CALL_EVENTS_SUBJECT = "roomr.events.*";
|
|
22
|
+
/**
|
|
23
|
+
* The `call.failed` reasons an unanswered outbound call can end with (the node's `cause`); any
|
|
24
|
+
* other cause is reported as `node_error`.
|
|
25
|
+
*/
|
|
26
|
+
export declare const UNANSWERED_CALL_REASONS: readonly ["busy", "no_answer", "rejected", "removed", "node_error"];
|
|
27
|
+
/** An outbound call the callee never answered, as its node's `call.ended` tells it. */
|
|
28
|
+
export type UnansweredCall = {
|
|
29
|
+
callRef: string;
|
|
30
|
+
appRef: string;
|
|
31
|
+
from: string;
|
|
32
|
+
to: string;
|
|
33
|
+
reason: (typeof UNANSWERED_CALL_REASONS)[number];
|
|
34
|
+
sipStatus?: number;
|
|
35
|
+
nodeRef: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The unanswered outbound call a Roomr node's event on `subject` (`roomr.events.<call_ref>`)
|
|
39
|
+
* reports, or undefined: only a `call.ended` with `answered: false` of a call the control plane
|
|
40
|
+
* placed (its `metadata.app_ref`, passed through by the node) for the subject's own call. The
|
|
41
|
+
* parties come from the metadata the control plane set; the node never chooses the workspace
|
|
42
|
+
* (left for the fan-out to resolve from the app), the event type or extra data (M6).
|
|
43
|
+
*/
|
|
44
|
+
export declare function unansweredOutboundCall(subject: string, event: Record<string, unknown> | undefined): UnansweredCall | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* The `call.failed` event for an unanswered outbound call. Its id depends on the call only, so
|
|
47
|
+
* every replica that sees the node's `call.ended` builds the same event and each endpoint gets one
|
|
48
|
+
* delivery.
|
|
49
|
+
*/
|
|
50
|
+
export declare function unansweredCallFailed(call: UnansweredCall, now?: Date): WebhookEvent;
|
|
51
|
+
export type FanoutOptions = {
|
|
52
|
+
/** Usually `WebhookDispatcher.publish`: stores one pending delivery per subscribed endpoint. */
|
|
53
|
+
publish(event: WebhookEvent): Delivery[];
|
|
54
|
+
/**
|
|
55
|
+
* The workspace of an app, for events whose producer did not know it (empty or `default`
|
|
56
|
+
* workspaceRef with a `data.appRef`).
|
|
57
|
+
*/
|
|
58
|
+
workspaceOfApp?(appRef: string): Promise<string | undefined>;
|
|
59
|
+
/** Called after an event was fanned out (e.g. to attempt the new deliveries at once). */
|
|
60
|
+
onPublished?(event: WebhookEvent, deliveries: Delivery[]): void;
|
|
61
|
+
onError?(e: Error): void;
|
|
62
|
+
/** Queue group: one replica handles each event. */
|
|
63
|
+
queue?: string;
|
|
64
|
+
};
|
|
65
|
+
/** Fills in a missing workspace from the app; undefined when it cannot be known. */
|
|
66
|
+
export declare function resolveWorkspace(event: WebhookEvent, workspaceOfApp?: FanoutOptions["workspaceOfApp"]): Promise<WebhookEvent | undefined>;
|
|
67
|
+
/** Subscribes to the bus and fans every event out; returns the unsubscribe function. */
|
|
68
|
+
export declare function subscribeWebhookFanout(nc: Pick<BusConnection, "subscribe">, options: FanoutOptions): () => void;
|
|
@@ -0,0 +1,178 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.UNANSWERED_CALL_REASONS = exports.ROOMR_CALL_EVENTS_SUBJECT = exports.WEBHOOK_EVENTS_SUBJECT = void 0;
|
|
13
|
+
exports.busWebhookPublisher = busWebhookPublisher;
|
|
14
|
+
exports.parseBusEvent = parseBusEvent;
|
|
15
|
+
exports.unansweredOutboundCall = unansweredOutboundCall;
|
|
16
|
+
exports.unansweredCallFailed = unansweredCallFailed;
|
|
17
|
+
exports.resolveWorkspace = resolveWorkspace;
|
|
18
|
+
exports.subscribeWebhookFanout = subscribeWebhookFanout;
|
|
19
|
+
/**
|
|
20
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
21
|
+
* http://github.com/fonoster/fonoster
|
|
22
|
+
*
|
|
23
|
+
* This file is part of Fonoster
|
|
24
|
+
*
|
|
25
|
+
* Licensed under the MIT License (the "License");
|
|
26
|
+
* you may not use this file except in compliance with
|
|
27
|
+
* the License. You may obtain a copy of the License at
|
|
28
|
+
*
|
|
29
|
+
* https://opensource.org/licenses/MIT
|
|
30
|
+
*
|
|
31
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
32
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
33
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
34
|
+
* See the License for the specific language governing permissions and
|
|
35
|
+
* limitations under the License.
|
|
36
|
+
*/
|
|
37
|
+
const crypto_1 = require("crypto");
|
|
38
|
+
const util_1 = require("util");
|
|
39
|
+
const events_1 = require("./events");
|
|
40
|
+
// Event fan-out from the event bus (add-workspace-webhooks task 2.2, build log LO-B1). Producers
|
|
41
|
+
// that have no database (Roomr nodes, appbridges in bus mode, other regions) publish events on
|
|
42
|
+
// `webhooks.events`; one control-plane replica per event (queue group) fans each event out to the
|
|
43
|
+
// workspace's subscribed endpoints. A producer that does not know the workspace leaves
|
|
44
|
+
// `workspaceRef` empty and the fan-out resolves it from `data.appRef`.
|
|
45
|
+
//
|
|
46
|
+
// Roomr nodes may not publish `webhooks.events` (M6: a node could otherwise forge any event for
|
|
47
|
+
// any workspace). An outbound call that is never answered reaches no AppBridge; the control plane
|
|
48
|
+
// builds its call.failed from the node's own `call.ended` on `roomr.events.<call_ref>`
|
|
49
|
+
// (unansweredOutboundCall), with the workspace taken from the app and nothing else trusted.
|
|
50
|
+
exports.WEBHOOK_EVENTS_SUBJECT = "webhooks.events";
|
|
51
|
+
const encoder = new util_1.TextEncoder();
|
|
52
|
+
const decoder = new util_1.TextDecoder();
|
|
53
|
+
/** A producer that publishes events on the bus instead of delivering them itself. */
|
|
54
|
+
function busWebhookPublisher(nc) {
|
|
55
|
+
return (event) => nc.publish(exports.WEBHOOK_EVENTS_SUBJECT, encoder.encode(JSON.stringify(event)));
|
|
56
|
+
}
|
|
57
|
+
/** Parses a bus message; undefined for anything that is not a v1 webhook event. */
|
|
58
|
+
function parseBusEvent(data) {
|
|
59
|
+
let e;
|
|
60
|
+
try {
|
|
61
|
+
e = JSON.parse(decoder.decode(data));
|
|
62
|
+
}
|
|
63
|
+
catch (_a) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
if (!e ||
|
|
67
|
+
typeof e.id !== "string" ||
|
|
68
|
+
!/^evt_[\w-]+$/.test(e.id) ||
|
|
69
|
+
!events_1.WEBHOOK_EVENT_TYPES.includes(e.type) ||
|
|
70
|
+
typeof e.data !== "object" ||
|
|
71
|
+
e.data === null)
|
|
72
|
+
return undefined;
|
|
73
|
+
return e;
|
|
74
|
+
}
|
|
75
|
+
/** Where Roomr nodes publish a call's events (`roomr.events.<call_ref>`; room events have one more token). */
|
|
76
|
+
exports.ROOMR_CALL_EVENTS_SUBJECT = "roomr.events.*";
|
|
77
|
+
/**
|
|
78
|
+
* The `call.failed` reasons an unanswered outbound call can end with (the node's `cause`); any
|
|
79
|
+
* other cause is reported as `node_error`.
|
|
80
|
+
*/
|
|
81
|
+
exports.UNANSWERED_CALL_REASONS = [
|
|
82
|
+
"busy",
|
|
83
|
+
"no_answer",
|
|
84
|
+
"rejected",
|
|
85
|
+
"removed",
|
|
86
|
+
"node_error"
|
|
87
|
+
];
|
|
88
|
+
const MAX_FIELD = 256;
|
|
89
|
+
const field = (v, required = false) => typeof v === "string" && v.length <= MAX_FIELD && (!required || v.length > 0)
|
|
90
|
+
? v
|
|
91
|
+
: undefined;
|
|
92
|
+
/**
|
|
93
|
+
* The unanswered outbound call a Roomr node's event on `subject` (`roomr.events.<call_ref>`)
|
|
94
|
+
* reports, or undefined: only a `call.ended` with `answered: false` of a call the control plane
|
|
95
|
+
* placed (its `metadata.app_ref`, passed through by the node) for the subject's own call. The
|
|
96
|
+
* parties come from the metadata the control plane set; the node never chooses the workspace
|
|
97
|
+
* (left for the fan-out to resolve from the app), the event type or extra data (M6).
|
|
98
|
+
*/
|
|
99
|
+
function unansweredOutboundCall(subject, event) {
|
|
100
|
+
var _a, _b, _c, _d;
|
|
101
|
+
const tokens = subject.split(".");
|
|
102
|
+
if (tokens.length !== 3 || tokens[0] !== "roomr" || tokens[1] !== "events")
|
|
103
|
+
return undefined;
|
|
104
|
+
if (!event || typeof event !== "object" || event.type !== "call.ended")
|
|
105
|
+
return undefined;
|
|
106
|
+
if (event.answered !== false)
|
|
107
|
+
return undefined;
|
|
108
|
+
const callRef = field(event.call_ref, true);
|
|
109
|
+
if (!callRef || callRef !== tokens[2])
|
|
110
|
+
return undefined;
|
|
111
|
+
const metadata = (event.metadata && typeof event.metadata === "object" ? event.metadata : {});
|
|
112
|
+
const appRef = field(metadata.app_ref, true);
|
|
113
|
+
if (!appRef)
|
|
114
|
+
return undefined;
|
|
115
|
+
const cause = String((_a = event.cause) !== null && _a !== void 0 ? _a : "");
|
|
116
|
+
const reason = exports.UNANSWERED_CALL_REASONS.includes(cause)
|
|
117
|
+
? cause
|
|
118
|
+
: "node_error";
|
|
119
|
+
const sipStatus = Number(event.sip_status);
|
|
120
|
+
return Object.assign(Object.assign({ callRef,
|
|
121
|
+
appRef, from: (_b = field(metadata.from)) !== null && _b !== void 0 ? _b : "", to: (_c = field(metadata.to)) !== null && _c !== void 0 ? _c : "", reason }, (Number.isInteger(sipStatus) && sipStatus > 0 ? { sipStatus } : {})), { nodeRef: (_d = field(event.node_ref)) !== null && _d !== void 0 ? _d : "" });
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The `call.failed` event for an unanswered outbound call. Its id depends on the call only, so
|
|
125
|
+
* every replica that sees the node's `call.ended` builds the same event and each endpoint gets one
|
|
126
|
+
* delivery.
|
|
127
|
+
*/
|
|
128
|
+
function unansweredCallFailed(call, now = new Date()) {
|
|
129
|
+
const event = (0, events_1.callFailed)("", {
|
|
130
|
+
callRef: call.callRef,
|
|
131
|
+
appRef: call.appRef,
|
|
132
|
+
from: call.from,
|
|
133
|
+
to: call.to,
|
|
134
|
+
direction: "outbound",
|
|
135
|
+
reason: call.reason
|
|
136
|
+
});
|
|
137
|
+
const digest = (0, crypto_1.createHash)("sha256")
|
|
138
|
+
.update(`call.failed\n${call.callRef}`)
|
|
139
|
+
.digest("hex")
|
|
140
|
+
.slice(0, 32);
|
|
141
|
+
return Object.assign(Object.assign({}, event), { id: `evt_node_${digest}`, createdAt: now.toISOString() });
|
|
142
|
+
}
|
|
143
|
+
/** Fills in a missing workspace from the app; undefined when it cannot be known. */
|
|
144
|
+
function resolveWorkspace(event, workspaceOfApp) {
|
|
145
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
146
|
+
if (event.workspaceRef && event.workspaceRef !== "default")
|
|
147
|
+
return event;
|
|
148
|
+
const appRef = event.data.appRef;
|
|
149
|
+
if (typeof appRef !== "string" || !appRef || !workspaceOfApp)
|
|
150
|
+
return undefined;
|
|
151
|
+
const workspaceRef = yield workspaceOfApp(appRef);
|
|
152
|
+
return workspaceRef ? Object.assign(Object.assign({}, event), { workspaceRef }) : undefined;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/** Subscribes to the bus and fans every event out; returns the unsubscribe function. */
|
|
156
|
+
function subscribeWebhookFanout(nc, options) {
|
|
157
|
+
var _a;
|
|
158
|
+
const sub = nc.subscribe(exports.WEBHOOK_EVENTS_SUBJECT, {
|
|
159
|
+
queue: (_a = options.queue) !== null && _a !== void 0 ? _a : "webhooks-fanout",
|
|
160
|
+
callback: (err, msg) => {
|
|
161
|
+
if (err)
|
|
162
|
+
return;
|
|
163
|
+
const event = parseBusEvent(msg.data);
|
|
164
|
+
if (!event)
|
|
165
|
+
return;
|
|
166
|
+
resolveWorkspace(event, options.workspaceOfApp)
|
|
167
|
+
.then((resolved) => {
|
|
168
|
+
var _a;
|
|
169
|
+
if (!resolved)
|
|
170
|
+
return;
|
|
171
|
+
const deliveries = options.publish(resolved);
|
|
172
|
+
(_a = options.onPublished) === null || _a === void 0 ? void 0 : _a.call(options, resolved, deliveries);
|
|
173
|
+
})
|
|
174
|
+
.catch((e) => { var _a; return (_a = options.onError) === null || _a === void 0 ? void 0 : _a.call(options, e); });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
return () => sub.unsubscribe();
|
|
178
|
+
}
|