@fonoster/common 0.26.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.
- package/dist/assistants/index.d.ts +2 -6
- package/dist/assistants/index.js +2 -6
- package/dist/assistants/sessionAssistantSchema.d.ts +741 -0
- package/dist/assistants/sessionAssistantSchema.js +112 -0
- package/dist/assistants/testCasesSchema.d.ts +4 -4
- 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/{utils/findIntegrationsCredentials.d.ts → config/cli/types.d.ts} +6 -5
- package/dist/config/cli/types.js +7 -0
- package/dist/config/configHints.d.ts +39 -0
- package/dist/config/configHints.js +61 -0
- package/dist/{voice → config}/index.d.ts +12 -13
- package/dist/config/index.js +46 -0
- package/dist/config/integrationsSchema.d.ts +32 -0
- package/dist/config/integrationsSchema.js +56 -0
- package/dist/config/interpolateEnv.d.ts +34 -0
- package/dist/config/interpolateEnv.js +70 -0
- package/dist/config/loadServiceConfig.d.ts +33 -0
- package/dist/config/loadServiceConfig.js +121 -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 -4
- package/dist/constants.js +4 -5
- package/dist/envs.d.ts +2 -1
- package/dist/envs.js +13 -11
- 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 +22 -6
- 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 +11 -1
- package/dist/index.js +14 -2
- package/dist/net/assertPublicDestination.d.ts +52 -0
- package/dist/net/assertPublicDestination.js +342 -0
- package/dist/{voice/PlayDtmf.d.ts → net/index.d.ts} +1 -5
- package/dist/{voice → net}/index.js +1 -13
- package/dist/notifications/createSendEmail.js +3 -1
- package/dist/protos/applications.proto +124 -3
- package/dist/protos/authz.proto +23 -4
- package/dist/protos/capabilities.proto +94 -0
- package/dist/protos/identity.proto +26 -1
- package/dist/protos/providers.proto +135 -0
- package/dist/protos/webhooks.proto +140 -0
- package/dist/{voice/StopSay.d.ts → regions/hub/index.d.ts} +1 -6
- 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 +79 -0
- package/dist/registry/Registry.js +418 -0
- package/dist/registry/cli.d.ts +20 -0
- package/dist/registry/cli.js +424 -0
- package/dist/{assistants/audioFiltersSchema.js → registry/features.d.ts} +13 -14
- package/dist/registry/features.js +36 -0
- package/dist/{assistants/LanguageModelProvider.d.ts → registry/index.d.ts} +6 -8
- package/dist/registry/index.js +40 -0
- package/dist/registry/prisma.d.ts +67 -0
- package/dist/registry/prisma.js +391 -0
- package/dist/registry/stores.d.ts +21 -0
- package/dist/{assistants/eventsHookSchema.js → registry/stores.js} +55 -18
- package/dist/registry/types.d.ts +98 -0
- package/dist/{voice/Say.d.ts → registry/types.js} +4 -10
- package/dist/registry/workspaceKeys.d.ts +41 -0
- package/dist/registry/workspaceKeys.js +64 -0
- package/dist/session/index.d.ts +19 -0
- package/dist/session/index.js +35 -0
- package/dist/session/schema.d.ts +1951 -0
- package/dist/session/schema.js +397 -0
- package/dist/sessions/index.d.ts +93 -0
- package/dist/sessions/index.js +194 -0
- package/dist/telemetry.d.ts +76 -0
- package/dist/telemetry.js +360 -0
- package/dist/types.d.ts +3 -5
- package/dist/utils/{findIntegrationsCredentials.js → WriteBehind.d.ts} +13 -10
- package/dist/utils/WriteBehind.js +64 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/{voice/Play.d.ts → utils/logUnhandledRejections.d.ts} +8 -12
- package/dist/utils/logUnhandledRejections.js +37 -0
- package/dist/validators/calls.d.ts +2 -2
- package/dist/validators/calls.js +5 -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 +49 -0
- package/dist/webhooks/bus.js +170 -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 -12
- package/schema/session.v1.json +791 -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/assistants/languageModelConfigSchema.js +0 -91
- package/dist/identity/tokenHasAccessKeyId.d.ts +0 -2
- package/dist/protos/voice.proto +0 -557
- package/dist/voice/Dial.js +0 -20
- package/dist/voice/Gather.d.ts +0 -37
- 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.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.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
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Delivery, WebhookEndpoint, WebhookStore } from "./Dispatcher";
|
|
2
|
+
type PrismaLike = any;
|
|
3
|
+
export type PrismaWebhookStoreOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* Take over pending deliveries this long overdue (their owner died or stalled). Off by default;
|
|
6
|
+
* turn it on in processes that run `runDue()` on a timer. Default 60 s when set to true.
|
|
7
|
+
*/
|
|
8
|
+
adoptOverdue?: boolean | number;
|
|
9
|
+
now?: () => number;
|
|
10
|
+
};
|
|
11
|
+
export declare class PrismaWebhookStore implements WebhookStore {
|
|
12
|
+
private readonly prisma;
|
|
13
|
+
private readonly options;
|
|
14
|
+
private e;
|
|
15
|
+
private d;
|
|
16
|
+
private readonly writes;
|
|
17
|
+
private constructor();
|
|
18
|
+
static open(prisma: PrismaLike, options?: PrismaWebhookStoreOptions): Promise<PrismaWebhookStore>;
|
|
19
|
+
private now;
|
|
20
|
+
/**
|
|
21
|
+
* Reloads endpoints, forgets settled deliveries (they stay in the log) and, with
|
|
22
|
+
* `adoptOverdue`, claims pending deliveries whose owner stopped retrying them.
|
|
23
|
+
*/
|
|
24
|
+
refresh(): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Reloads one workspace's endpoints now. The fan-out calls it for each event, so an endpoint
|
|
27
|
+
* created, changed or deleted a moment ago through another replica counts at once instead of
|
|
28
|
+
* after the next `refresh()` (ST-2).
|
|
29
|
+
*/
|
|
30
|
+
refreshEndpoints(workspaceRef: string): Promise<void>;
|
|
31
|
+
endpoints(workspaceRef?: string): WebhookEndpoint[];
|
|
32
|
+
putEndpoint(x: WebhookEndpoint): void;
|
|
33
|
+
deleteEndpoint(id: string): void;
|
|
34
|
+
/** Deliveries this process knows: the ones it created, adopted or loaded. */
|
|
35
|
+
deliveries(endpointId?: string): Delivery[];
|
|
36
|
+
/**
|
|
37
|
+
* Stores new deliveries before returning (the fan-out acknowledges the bus message only then).
|
|
38
|
+
* Returns the ones it stored; a delivery that already exists (the same event fanned out again)
|
|
39
|
+
* is left as it is, and whoever owns it keeps it (ST-2).
|
|
40
|
+
*/
|
|
41
|
+
insertDeliveries(xs: Delivery[]): Promise<Delivery[]>;
|
|
42
|
+
putDelivery(x: Delivery): void;
|
|
43
|
+
/**
|
|
44
|
+
* The delivery log, newest first (W6). Loaded deliveries become known to this store, so
|
|
45
|
+
* `WebhookDispatcher.resend` can find them.
|
|
46
|
+
*/
|
|
47
|
+
loadDeliveries(filter?: {
|
|
48
|
+
endpointId?: string;
|
|
49
|
+
workspaceRef?: string;
|
|
50
|
+
id?: string;
|
|
51
|
+
}, limit?: number): Promise<Delivery[]>;
|
|
52
|
+
/** Waits for every queued write; throws the first failure. */
|
|
53
|
+
flush(): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,242 @@
|
|
|
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.PrismaWebhookStore = void 0;
|
|
13
|
+
/**
|
|
14
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
15
|
+
* http://github.com/fonoster/fonoster
|
|
16
|
+
*
|
|
17
|
+
* This file is part of Fonoster
|
|
18
|
+
*
|
|
19
|
+
* Licensed under the MIT License (the "License");
|
|
20
|
+
* you may not use this file except in compliance with
|
|
21
|
+
* the License. You may obtain a copy of the License at
|
|
22
|
+
*
|
|
23
|
+
* https://opensource.org/licenses/MIT
|
|
24
|
+
*
|
|
25
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
26
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
27
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
28
|
+
* See the License for the specific language governing permissions and
|
|
29
|
+
* limitations under the License.
|
|
30
|
+
*/
|
|
31
|
+
const WriteBehind_1 = require("../utils/WriteBehind");
|
|
32
|
+
const ms = (d) => (d ? d.getTime() : undefined);
|
|
33
|
+
const date = (n) => (n === undefined ? null : new Date(n));
|
|
34
|
+
function toEndpoint(r) {
|
|
35
|
+
const e = {
|
|
36
|
+
id: r.ref,
|
|
37
|
+
workspaceRef: r.accessKeyId,
|
|
38
|
+
url: r.url,
|
|
39
|
+
events: r.events,
|
|
40
|
+
secret: r.secret
|
|
41
|
+
};
|
|
42
|
+
if (r.previousSecret) {
|
|
43
|
+
e.previousSecret = r.previousSecret;
|
|
44
|
+
e.previousUntil = ms(r.previousUntil);
|
|
45
|
+
}
|
|
46
|
+
return e;
|
|
47
|
+
}
|
|
48
|
+
function toDelivery(r) {
|
|
49
|
+
const d = {
|
|
50
|
+
id: r.ref,
|
|
51
|
+
endpointId: r.endpointRef,
|
|
52
|
+
event: r.event,
|
|
53
|
+
status: r.status,
|
|
54
|
+
attempts: r.attempts,
|
|
55
|
+
nextAttemptAt: ms(r.nextAttemptAt)
|
|
56
|
+
};
|
|
57
|
+
if (r.firstAttemptAt)
|
|
58
|
+
d.firstAttemptAt = ms(r.firstAttemptAt);
|
|
59
|
+
if (r.lastAttemptAt)
|
|
60
|
+
d.lastAttemptAt = ms(r.lastAttemptAt);
|
|
61
|
+
if (r.lastStatus !== null && r.lastStatus !== undefined)
|
|
62
|
+
d.lastStatus = r.lastStatus;
|
|
63
|
+
return d;
|
|
64
|
+
}
|
|
65
|
+
/** A delivery's columns (without its ref). */
|
|
66
|
+
function deliveryData(x) {
|
|
67
|
+
var _a;
|
|
68
|
+
return {
|
|
69
|
+
endpointRef: x.endpointId,
|
|
70
|
+
accessKeyId: x.event.workspaceRef,
|
|
71
|
+
eventId: x.event.id,
|
|
72
|
+
eventType: x.event.type,
|
|
73
|
+
event: x.event,
|
|
74
|
+
status: x.status,
|
|
75
|
+
attempts: x.attempts,
|
|
76
|
+
firstAttemptAt: date(x.firstAttemptAt),
|
|
77
|
+
lastAttemptAt: date(x.lastAttemptAt),
|
|
78
|
+
lastStatus: (_a = x.lastStatus) !== null && _a !== void 0 ? _a : null,
|
|
79
|
+
nextAttemptAt: new Date(x.nextAttemptAt)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
class PrismaWebhookStore {
|
|
83
|
+
constructor(prisma, options) {
|
|
84
|
+
this.prisma = prisma;
|
|
85
|
+
this.options = options;
|
|
86
|
+
this.e = new Map();
|
|
87
|
+
this.d = new Map();
|
|
88
|
+
this.writes = new WriteBehind_1.WriteBehind();
|
|
89
|
+
}
|
|
90
|
+
static open(prisma_1) {
|
|
91
|
+
return __awaiter(this, arguments, void 0, function* (prisma, options = {}) {
|
|
92
|
+
const store = new PrismaWebhookStore(prisma, options);
|
|
93
|
+
yield store.refresh();
|
|
94
|
+
return store;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
now() {
|
|
98
|
+
var _a, _b, _c;
|
|
99
|
+
return (_c = (_b = (_a = this.options).now) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : Date.now();
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Reloads endpoints, forgets settled deliveries (they stay in the log) and, with
|
|
103
|
+
* `adoptOverdue`, claims pending deliveries whose owner stopped retrying them.
|
|
104
|
+
*/
|
|
105
|
+
refresh() {
|
|
106
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
107
|
+
yield this.writes.flush();
|
|
108
|
+
const rows = yield this.prisma.webhookEndpoint.findMany();
|
|
109
|
+
this.e = new Map(rows.map((r) => [r.ref, toEndpoint(r)]));
|
|
110
|
+
for (const [id, d] of this.d)
|
|
111
|
+
if (d.status !== "pending")
|
|
112
|
+
this.d.delete(id);
|
|
113
|
+
const adopt = this.options.adoptOverdue;
|
|
114
|
+
if (!adopt)
|
|
115
|
+
return;
|
|
116
|
+
const after = adopt === true ? 60000 : adopt;
|
|
117
|
+
const overdue = yield this.prisma.webhookDelivery.findMany({
|
|
118
|
+
where: {
|
|
119
|
+
status: "pending",
|
|
120
|
+
nextAttemptAt: { lt: new Date(this.now() - after) }
|
|
121
|
+
},
|
|
122
|
+
take: 500
|
|
123
|
+
});
|
|
124
|
+
for (const r of overdue) {
|
|
125
|
+
if (this.d.has(r.ref))
|
|
126
|
+
continue;
|
|
127
|
+
// Claim it: only one process moves nextAttemptAt from the value it read.
|
|
128
|
+
const { count } = yield this.prisma.webhookDelivery.updateMany({
|
|
129
|
+
where: { ref: r.ref, nextAttemptAt: r.nextAttemptAt },
|
|
130
|
+
data: { nextAttemptAt: new Date(this.now()) }
|
|
131
|
+
});
|
|
132
|
+
if (count === 1)
|
|
133
|
+
this.d.set(r.ref, Object.assign(Object.assign({}, toDelivery(r)), { nextAttemptAt: this.now() }));
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Reloads one workspace's endpoints now. The fan-out calls it for each event, so an endpoint
|
|
139
|
+
* created, changed or deleted a moment ago through another replica counts at once instead of
|
|
140
|
+
* after the next `refresh()` (ST-2).
|
|
141
|
+
*/
|
|
142
|
+
refreshEndpoints(workspaceRef) {
|
|
143
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
144
|
+
yield this.writes.flush();
|
|
145
|
+
const rows = yield this.prisma.webhookEndpoint.findMany({
|
|
146
|
+
where: { accessKeyId: workspaceRef }
|
|
147
|
+
});
|
|
148
|
+
for (const [id, e] of this.e)
|
|
149
|
+
if (e.workspaceRef === workspaceRef)
|
|
150
|
+
this.e.delete(id);
|
|
151
|
+
for (const r of rows)
|
|
152
|
+
this.e.set(r.ref, toEndpoint(r));
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
endpoints(workspaceRef) {
|
|
156
|
+
return [...this.e.values()].filter((x) => !workspaceRef || x.workspaceRef === workspaceRef);
|
|
157
|
+
}
|
|
158
|
+
putEndpoint(x) {
|
|
159
|
+
var _a;
|
|
160
|
+
this.e.set(x.id, x);
|
|
161
|
+
const data = {
|
|
162
|
+
accessKeyId: x.workspaceRef,
|
|
163
|
+
url: x.url,
|
|
164
|
+
events: x.events,
|
|
165
|
+
secret: x.secret,
|
|
166
|
+
previousSecret: (_a = x.previousSecret) !== null && _a !== void 0 ? _a : null,
|
|
167
|
+
previousUntil: date(x.previousUntil)
|
|
168
|
+
};
|
|
169
|
+
this.writes.push(() => this.prisma.webhookEndpoint.upsert({
|
|
170
|
+
where: { ref: x.id },
|
|
171
|
+
create: Object.assign({ ref: x.id }, data),
|
|
172
|
+
update: data
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
deleteEndpoint(id) {
|
|
176
|
+
this.e.delete(id);
|
|
177
|
+
this.writes.push(() => this.prisma.webhookEndpoint.deleteMany({ where: { ref: id } }));
|
|
178
|
+
}
|
|
179
|
+
/** Deliveries this process knows: the ones it created, adopted or loaded. */
|
|
180
|
+
deliveries(endpointId) {
|
|
181
|
+
return [...this.d.values()].filter((x) => !endpointId || x.endpointId === endpointId);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Stores new deliveries before returning (the fan-out acknowledges the bus message only then).
|
|
185
|
+
* Returns the ones it stored; a delivery that already exists (the same event fanned out again)
|
|
186
|
+
* is left as it is, and whoever owns it keeps it (ST-2).
|
|
187
|
+
*/
|
|
188
|
+
insertDeliveries(xs) {
|
|
189
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
190
|
+
yield this.writes.flush();
|
|
191
|
+
const created = [];
|
|
192
|
+
for (const x of xs) {
|
|
193
|
+
try {
|
|
194
|
+
yield this.prisma.webhookDelivery.create({
|
|
195
|
+
data: Object.assign({ ref: x.id }, deliveryData(x))
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
if (e.code === "P2002")
|
|
200
|
+
continue;
|
|
201
|
+
throw e;
|
|
202
|
+
}
|
|
203
|
+
this.d.set(x.id, x);
|
|
204
|
+
created.push(x);
|
|
205
|
+
}
|
|
206
|
+
return created;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
putDelivery(x) {
|
|
210
|
+
this.d.set(x.id, x);
|
|
211
|
+
const data = deliveryData(x);
|
|
212
|
+
this.writes.push(() => this.prisma.webhookDelivery.upsert({
|
|
213
|
+
where: { ref: x.id },
|
|
214
|
+
create: Object.assign({ ref: x.id }, data),
|
|
215
|
+
update: data
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The delivery log, newest first (W6). Loaded deliveries become known to this store, so
|
|
220
|
+
* `WebhookDispatcher.resend` can find them.
|
|
221
|
+
*/
|
|
222
|
+
loadDeliveries() {
|
|
223
|
+
return __awaiter(this, arguments, void 0, function* (filter = {}, limit = 100) {
|
|
224
|
+
yield this.writes.flush();
|
|
225
|
+
const rows = yield this.prisma.webhookDelivery.findMany({
|
|
226
|
+
where: Object.assign(Object.assign(Object.assign({}, (filter.id ? { ref: filter.id } : {})), (filter.endpointId ? { endpointRef: filter.endpointId } : {})), (filter.workspaceRef ? { accessKeyId: filter.workspaceRef } : {})),
|
|
227
|
+
orderBy: { createdAt: "desc" },
|
|
228
|
+
take: limit
|
|
229
|
+
});
|
|
230
|
+
const out = rows.map(toDelivery);
|
|
231
|
+
for (const d of out)
|
|
232
|
+
if (!this.d.has(d.id))
|
|
233
|
+
this.d.set(d.id, d);
|
|
234
|
+
return out;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/** Waits for every queued write; throws the first failure. */
|
|
238
|
+
flush() {
|
|
239
|
+
return this.writes.flush();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
exports.PrismaWebhookStore = PrismaWebhookStore;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const WEBHOOK_SIGNATURE_HEADER = "X-Fonoster-Signature";
|
|
2
|
+
export declare const WEBHOOK_TIMESTAMP_HEADER = "X-Fonoster-Timestamp";
|
|
3
|
+
export declare function webhookHeaders(body: string, secrets: string[], nowSeconds?: number): {
|
|
4
|
+
"X-Fonoster-Timestamp": string;
|
|
5
|
+
"X-Fonoster-Signature": string;
|
|
6
|
+
};
|
|
7
|
+
/** For receivers: true when one of the signatures matches and the timestamp is fresh (±300 s). */
|
|
8
|
+
export declare function verifyWebhook(body: string, headers: Record<string, string | string[] | undefined>, secret: string, nowSeconds?: number): boolean;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WEBHOOK_TIMESTAMP_HEADER = exports.WEBHOOK_SIGNATURE_HEADER = void 0;
|
|
4
|
+
exports.webhookHeaders = webhookHeaders;
|
|
5
|
+
exports.verifyWebhook = verifyWebhook;
|
|
6
|
+
/**
|
|
7
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
8
|
+
* http://github.com/fonoster/fonoster
|
|
9
|
+
*
|
|
10
|
+
* This file is part of Fonoster
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the MIT License (the "License");
|
|
13
|
+
* you may not use this file except in compliance with
|
|
14
|
+
* the License. You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* https://opensource.org/licenses/MIT
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
const crypto_1 = require("crypto");
|
|
25
|
+
// add-workspace-webhooks, "Deliveries are signed": X-Fonoster-Timestamp (Unix seconds) and
|
|
26
|
+
// X-Fonoster-Signature = hex HMAC-SHA256 of "<timestamp>.<raw body>", one per valid secret,
|
|
27
|
+
// comma-separated during a rotation window.
|
|
28
|
+
exports.WEBHOOK_SIGNATURE_HEADER = "X-Fonoster-Signature";
|
|
29
|
+
exports.WEBHOOK_TIMESTAMP_HEADER = "X-Fonoster-Timestamp";
|
|
30
|
+
const hmac = (secret, timestamp, body) => (0, crypto_1.createHmac)("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
|
|
31
|
+
function webhookHeaders(body, secrets, nowSeconds = Math.floor(Date.now() / 1000)) {
|
|
32
|
+
return {
|
|
33
|
+
[exports.WEBHOOK_TIMESTAMP_HEADER]: String(nowSeconds),
|
|
34
|
+
[exports.WEBHOOK_SIGNATURE_HEADER]: secrets
|
|
35
|
+
.map((s) => hmac(s, nowSeconds, body))
|
|
36
|
+
.join(",")
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** For receivers: true when one of the signatures matches and the timestamp is fresh (±300 s). */
|
|
40
|
+
function verifyWebhook(body, headers, secret, nowSeconds = Math.floor(Date.now() / 1000)) {
|
|
41
|
+
const lower = Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v]));
|
|
42
|
+
const timestamp = Number(lower[exports.WEBHOOK_TIMESTAMP_HEADER.toLowerCase()]);
|
|
43
|
+
const header = lower[exports.WEBHOOK_SIGNATURE_HEADER.toLowerCase()];
|
|
44
|
+
if (!Number.isFinite(timestamp) || typeof header !== "string")
|
|
45
|
+
return false;
|
|
46
|
+
if (Math.abs(nowSeconds - timestamp) > 300)
|
|
47
|
+
return false;
|
|
48
|
+
const expected = Buffer.from(hmac(secret, timestamp, body), "hex");
|
|
49
|
+
return header.split(",").some((sig) => {
|
|
50
|
+
const given = Buffer.from(sig.trim(), "hex");
|
|
51
|
+
return given.length === expected.length && (0, crypto_1.timingSafeEqual)(given, expected);
|
|
52
|
+
});
|
|
53
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fonoster/common",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"description": "Common library for Fonoster projects",
|
|
5
5
|
"author": "Pedro Sanders <psanders@fonoster.com>",
|
|
6
6
|
"homepage": "https://github.com/fonoster/fonoster#readme",
|
|
@@ -15,27 +15,38 @@
|
|
|
15
15
|
"prebuild": "rimraf ./dist tsconfig.tsbuildinfo",
|
|
16
16
|
"build": "tsc -b tsconfig.json",
|
|
17
17
|
"postbuild": "cp -a src/protos dist",
|
|
18
|
-
"clean": "rimraf ./dist node_modules tsconfig.tsbuildinfo"
|
|
18
|
+
"clean": "rimraf ./dist node_modules tsconfig.tsbuildinfo",
|
|
19
|
+
"generate:session-schema": "tsx scripts/export-session-schema.ts"
|
|
19
20
|
},
|
|
20
21
|
"dependencies": {
|
|
21
|
-
"@fonoster/logger": "^0.
|
|
22
|
-
"@fonoster/types": "^0.
|
|
23
|
-
"@grpc/grpc-js": "~1.
|
|
22
|
+
"@fonoster/logger": "^0.30.0",
|
|
23
|
+
"@fonoster/types": "^0.30.0",
|
|
24
|
+
"@grpc/grpc-js": "~1.14.6",
|
|
24
25
|
"@grpc/proto-loader": "^0.7.15",
|
|
25
26
|
"@influxdata/influxdb-client": "^1.35.0",
|
|
26
|
-
"
|
|
27
|
+
"@opentelemetry/api": "^1.9.0",
|
|
28
|
+
"@opentelemetry/resources": "^2.12.0",
|
|
29
|
+
"@opentelemetry/sdk-trace-base": "^2.12.0",
|
|
30
|
+
"@opentelemetry/sdk-trace-node": "^2.12.0",
|
|
31
|
+
"ajv": "^8.17.1",
|
|
27
32
|
"grpc-health-check": "^2.0.2",
|
|
28
33
|
"handlebars": "^4.7.8",
|
|
34
|
+
"json-schema-to-ts": "^3.1.1",
|
|
35
|
+
"jsonc-parser": "^3.2.0",
|
|
29
36
|
"jsonwebtoken": "^9.0.2",
|
|
30
37
|
"jwt-decode": "^4.0.0",
|
|
31
|
-
"
|
|
38
|
+
"nkeys.js": "^1.1.0",
|
|
39
|
+
"nodemailer": "^10.1.2",
|
|
32
40
|
"pb-util": "^1.0.3",
|
|
33
41
|
"twilio": "^5.11.1",
|
|
34
42
|
"validator": "^13.12.0",
|
|
43
|
+
"yaml": "^2.9.0",
|
|
44
|
+
"zod": "^3.25.76",
|
|
35
45
|
"zod-validation-error": "^3.5.4"
|
|
36
46
|
},
|
|
37
47
|
"files": [
|
|
38
|
-
"dist"
|
|
48
|
+
"dist",
|
|
49
|
+
"schema"
|
|
39
50
|
],
|
|
40
51
|
"publishConfig": {
|
|
41
52
|
"access": "public"
|
|
@@ -47,8 +58,10 @@
|
|
|
47
58
|
"bugs": {
|
|
48
59
|
"url": "https://github.com/fonoster/fonoster/issues"
|
|
49
60
|
},
|
|
50
|
-
"devDependencies": {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
"devDependencies": {},
|
|
62
|
+
"gitHead": "08c9cfd7932675b6b16111daa4d03f14b4e287d1",
|
|
63
|
+
"bin": {
|
|
64
|
+
"fonoster-registry": "dist/registry/cli.js",
|
|
65
|
+
"fonoster-config": "dist/config/cli/index.js"
|
|
66
|
+
}
|
|
54
67
|
}
|