@emseepea/feedback 0.2.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/LICENSE +21 -0
- package/README.md +220 -0
- package/dist/deadline.d.ts +4 -0
- package/dist/deadline.d.ts.map +1 -0
- package/dist/deadline.js +41 -0
- package/dist/deadline.js.map +1 -0
- package/dist/firestore.d.ts +48 -0
- package/dist/firestore.d.ts.map +1 -0
- package/dist/firestore.js +296 -0
- package/dist/firestore.js.map +1 -0
- package/dist/github.d.ts +12 -0
- package/dist/github.d.ts.map +1 -0
- package/dist/github.js +275 -0
- package/dist/github.js.map +1 -0
- package/dist/http.d.ts +8 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +59 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +328 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +255 -0
- package/dist/index.js.map +1 -0
- package/dist/postgres.d.ts +32 -0
- package/dist/postgres.d.ts.map +1 -0
- package/dist/postgres.js +352 -0
- package/dist/postgres.js.map +1 -0
- package/dist/testing-server.d.ts +2 -0
- package/dist/testing-server.d.ts.map +1 -0
- package/dist/testing-server.js +46 -0
- package/dist/testing-server.js.map +1 -0
- package/dist/webhooks.d.ts +22 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/dist/webhooks.js +142 -0
- package/dist/webhooks.js.map +1 -0
- package/dist/zendesk.d.ts +12 -0
- package/dist/zendesk.d.ts.map +1 -0
- package/dist/zendesk.js +286 -0
- package/dist/zendesk.js.map +1 -0
- package/package.json +71 -0
package/dist/postgres.js
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { feedbackConversationSchema } from "./index.js";
|
|
4
|
+
import { beforeDeadline } from "./deadline.js";
|
|
5
|
+
export const postgresFeedbackSchema = `
|
|
6
|
+
CREATE TABLE IF NOT EXISTS emseepea_feedback_threads (
|
|
7
|
+
scope text NOT NULL,
|
|
8
|
+
id text NOT NULL,
|
|
9
|
+
subject text NOT NULL,
|
|
10
|
+
status text NOT NULL,
|
|
11
|
+
created_at timestamptz NOT NULL,
|
|
12
|
+
updated_at timestamptz NOT NULL,
|
|
13
|
+
PRIMARY KEY (scope, id)
|
|
14
|
+
);
|
|
15
|
+
CREATE TABLE IF NOT EXISTS emseepea_feedback_messages (
|
|
16
|
+
scope text NOT NULL,
|
|
17
|
+
thread_id text NOT NULL,
|
|
18
|
+
id text NOT NULL,
|
|
19
|
+
sequence integer NOT NULL CHECK (sequence > 0),
|
|
20
|
+
author text NOT NULL CHECK (author IN ('user', 'team', 'system')),
|
|
21
|
+
kind text NOT NULL CHECK (kind IN ('comment', 'question', 'status')),
|
|
22
|
+
body text NOT NULL,
|
|
23
|
+
created_at timestamptz NOT NULL,
|
|
24
|
+
offered_to_client_at timestamptz,
|
|
25
|
+
PRIMARY KEY (scope, id),
|
|
26
|
+
UNIQUE (scope, thread_id, sequence),
|
|
27
|
+
FOREIGN KEY (scope, thread_id) REFERENCES emseepea_feedback_threads(scope, id)
|
|
28
|
+
);
|
|
29
|
+
CREATE INDEX IF NOT EXISTS emseepea_feedback_threads_page
|
|
30
|
+
ON emseepea_feedback_threads(scope, updated_at DESC, id DESC);
|
|
31
|
+
CREATE TABLE IF NOT EXISTS emseepea_feedback_submissions (
|
|
32
|
+
scope text NOT NULL,
|
|
33
|
+
id text NOT NULL,
|
|
34
|
+
observation text NOT NULL,
|
|
35
|
+
detail text NOT NULL,
|
|
36
|
+
context jsonb,
|
|
37
|
+
recorded_at timestamptz NOT NULL,
|
|
38
|
+
PRIMARY KEY (scope, id)
|
|
39
|
+
);
|
|
40
|
+
CREATE TABLE IF NOT EXISTS emseepea_feedback_outbox (
|
|
41
|
+
event_id text PRIMARY KEY,
|
|
42
|
+
event_type text NOT NULL,
|
|
43
|
+
occurred_at timestamptz NOT NULL,
|
|
44
|
+
scope text NOT NULL,
|
|
45
|
+
thread_id text NOT NULL,
|
|
46
|
+
message_id text,
|
|
47
|
+
author text,
|
|
48
|
+
dispatched_at timestamptz
|
|
49
|
+
);`;
|
|
50
|
+
export async function migratePostgresFeedback(pool) {
|
|
51
|
+
await pool.query(postgresFeedbackSchema);
|
|
52
|
+
}
|
|
53
|
+
export function createPostgresFeedbackSubmissionBackend(options) {
|
|
54
|
+
return {
|
|
55
|
+
submit: (command, context) => transaction(options.pool, async (client) => {
|
|
56
|
+
const id = randomUUID();
|
|
57
|
+
const result = await client.query(`
|
|
58
|
+
INSERT INTO emseepea_feedback_submissions
|
|
59
|
+
(scope, id, observation, detail, context, recorded_at)
|
|
60
|
+
VALUES ($1, $2, $3, $4, $5::jsonb, clock_timestamp())
|
|
61
|
+
RETURNING recorded_at
|
|
62
|
+
`, [context.scope, id, command.observation, command.detail, JSON.stringify(command.context)]);
|
|
63
|
+
const recordedAt = iso(result.rows[0]?.recorded_at);
|
|
64
|
+
const events = [event("feedback.message.added", id, id, "user", recordedAt)];
|
|
65
|
+
if (options.outbox)
|
|
66
|
+
await writeOutbox(client, context.scope, events);
|
|
67
|
+
return { id, recordedAt, events };
|
|
68
|
+
}, context),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export function createPostgresFeedbackBackend(options) {
|
|
72
|
+
return {
|
|
73
|
+
createThread: (command, context) => transaction(options.pool, async (client) => {
|
|
74
|
+
const threadId = randomUUID();
|
|
75
|
+
const messageId = randomUUID();
|
|
76
|
+
const inserted = await client.query(`
|
|
77
|
+
INSERT INTO emseepea_feedback_threads
|
|
78
|
+
(scope, id, subject, status, created_at, updated_at)
|
|
79
|
+
VALUES ($1, $2, $3, 'open', clock_timestamp(), clock_timestamp())
|
|
80
|
+
RETURNING *
|
|
81
|
+
`, [context.scope, threadId, command.subject]);
|
|
82
|
+
const message = await insertMessage(client, context.scope, threadId, messageId, 1, "user", "comment", command.message);
|
|
83
|
+
const thread = threadRow.parse(inserted.rows[0]);
|
|
84
|
+
const events = [
|
|
85
|
+
event("feedback.thread.created", threadId, undefined, "user", iso(thread.created_at)),
|
|
86
|
+
event("feedback.message.added", threadId, messageId, "user", iso(message.created_at)),
|
|
87
|
+
];
|
|
88
|
+
if (options.outbox)
|
|
89
|
+
await writeOutbox(client, context.scope, events);
|
|
90
|
+
return { conversation: conversation(thread, [message]), events };
|
|
91
|
+
}, context),
|
|
92
|
+
appendMessage: (command, context) => transaction(options.pool, async (client) => {
|
|
93
|
+
const thread = await lockThread(client, context.scope, command.threadId);
|
|
94
|
+
const sequenceResult = await client.query(`
|
|
95
|
+
SELECT COALESCE(MAX(sequence), 0) + 1 AS sequence
|
|
96
|
+
FROM emseepea_feedback_messages
|
|
97
|
+
WHERE scope = $1 AND thread_id = $2
|
|
98
|
+
`, [context.scope, command.threadId]);
|
|
99
|
+
const sequence = z.coerce.number().int().positive().parse(sequenceResult.rows[0]?.sequence);
|
|
100
|
+
const message = await insertMessage(client, context.scope, command.threadId, randomUUID(), sequence, "user", "comment", command.message);
|
|
101
|
+
await client.query(`
|
|
102
|
+
UPDATE emseepea_feedback_threads SET updated_at = $3
|
|
103
|
+
WHERE scope = $1 AND id = $2
|
|
104
|
+
`, [context.scope, command.threadId, message.created_at]);
|
|
105
|
+
const mapped = messageValue(message);
|
|
106
|
+
const events = [event("feedback.message.added", command.threadId, mapped.id, "user", mapped.createdAt)];
|
|
107
|
+
if (options.outbox)
|
|
108
|
+
await writeOutbox(client, context.scope, events);
|
|
109
|
+
void thread;
|
|
110
|
+
return { message: mapped, events };
|
|
111
|
+
}, context),
|
|
112
|
+
listThreads: ({ cursor, limit }, context) => transaction(options.pool, async (client) => {
|
|
113
|
+
const position = decodeCursor(cursor);
|
|
114
|
+
const values = [context.scope, limit + 1];
|
|
115
|
+
const condition = position
|
|
116
|
+
? "AND (updated_at, id) < ($3::timestamptz, $4)"
|
|
117
|
+
: "";
|
|
118
|
+
if (position)
|
|
119
|
+
values.push(position.updatedAt, position.id);
|
|
120
|
+
const result = await client.query(`
|
|
121
|
+
SELECT * FROM emseepea_feedback_threads
|
|
122
|
+
WHERE scope = $1 ${condition}
|
|
123
|
+
ORDER BY updated_at DESC, id DESC
|
|
124
|
+
LIMIT $2
|
|
125
|
+
`, values);
|
|
126
|
+
const rows = result.rows.map((row) => threadRow.parse(row));
|
|
127
|
+
const visible = rows.slice(0, limit);
|
|
128
|
+
const last = visible.at(-1);
|
|
129
|
+
return {
|
|
130
|
+
page: {
|
|
131
|
+
threads: visible.map(threadValue),
|
|
132
|
+
nextCursor: rows.length > limit && last
|
|
133
|
+
? encodeCursor(iso(last.updated_at), last.id)
|
|
134
|
+
: undefined,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}, context),
|
|
138
|
+
async getThread(query, context) {
|
|
139
|
+
const base = await transaction(options.pool, async (client) => {
|
|
140
|
+
const thread = await lockThread(client, context.scope, query.threadId);
|
|
141
|
+
const messages = await client.query(`
|
|
142
|
+
SELECT * FROM emseepea_feedback_messages
|
|
143
|
+
WHERE scope = $1 AND thread_id = $2
|
|
144
|
+
ORDER BY sequence
|
|
145
|
+
LIMIT 200
|
|
146
|
+
`, [context.scope, query.threadId]);
|
|
147
|
+
return conversation(thread, messages.rows.map((row) => messageRow.parse(row)));
|
|
148
|
+
}, context);
|
|
149
|
+
feedbackConversationSchema.parse(base);
|
|
150
|
+
try {
|
|
151
|
+
const offered = await transaction(options.pool, async (client) => {
|
|
152
|
+
const result = await client.query(`
|
|
153
|
+
UPDATE emseepea_feedback_messages
|
|
154
|
+
SET offered_to_client_at = clock_timestamp()
|
|
155
|
+
WHERE scope = $1 AND thread_id = $2
|
|
156
|
+
AND author = 'team' AND offered_to_client_at IS NULL
|
|
157
|
+
RETURNING *
|
|
158
|
+
`, [context.scope, query.threadId]);
|
|
159
|
+
const rows = result.rows.map((row) => messageRow.parse(row));
|
|
160
|
+
const events = rows.map((message) => event("feedback.message.offered-to-client", query.threadId, message.id, "team", iso(message.offered_to_client_at)));
|
|
161
|
+
if (options.outbox)
|
|
162
|
+
await writeOutbox(client, context.scope, events);
|
|
163
|
+
return { rows, events };
|
|
164
|
+
}, context);
|
|
165
|
+
const receipts = new Map(offered.rows.map((message) => [message.id, iso(message.offered_to_client_at)]));
|
|
166
|
+
return {
|
|
167
|
+
conversation: {
|
|
168
|
+
...base,
|
|
169
|
+
messages: base.messages.map((message) => receipts.has(message.id)
|
|
170
|
+
? { ...message, offeredToClientAt: receipts.get(message.id) }
|
|
171
|
+
: message),
|
|
172
|
+
},
|
|
173
|
+
events: offered.events,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return { conversation: base, events: [] };
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
export async function appendPostgresTeamReply(options, command, context) {
|
|
183
|
+
const scope = z.string().min(1).max(240).parse(command.scope);
|
|
184
|
+
const messageBody = z.string().min(1).max(4_000).parse(command.message);
|
|
185
|
+
await transaction(options.pool, async (client) => {
|
|
186
|
+
await lockThread(client, scope, command.threadId);
|
|
187
|
+
const next = await client.query(`
|
|
188
|
+
SELECT COALESCE(MAX(sequence), 0) + 1 AS sequence
|
|
189
|
+
FROM emseepea_feedback_messages
|
|
190
|
+
WHERE scope = $1 AND thread_id = $2
|
|
191
|
+
`, [scope, command.threadId]);
|
|
192
|
+
const message = await insertMessage(client, scope, command.threadId, randomUUID(), z.coerce.number().int().positive().parse(next.rows[0]?.sequence), "team", "comment", messageBody);
|
|
193
|
+
const status = command.status === undefined
|
|
194
|
+
? undefined
|
|
195
|
+
: z.string().min(1).max(80).parse(command.status);
|
|
196
|
+
await client.query(`
|
|
197
|
+
UPDATE emseepea_feedback_threads
|
|
198
|
+
SET updated_at = $3, status = COALESCE($4, status)
|
|
199
|
+
WHERE scope = $1 AND id = $2
|
|
200
|
+
`, [scope, command.threadId, message.created_at, status]);
|
|
201
|
+
if (options.outbox) {
|
|
202
|
+
const events = [event("feedback.message.added", command.threadId, message.id, "team", iso(message.created_at))];
|
|
203
|
+
if (status)
|
|
204
|
+
events.push(event("feedback.status.changed", command.threadId, undefined, "team", iso(message.created_at)));
|
|
205
|
+
await writeOutbox(client, scope, events);
|
|
206
|
+
}
|
|
207
|
+
}, { ...context, scope });
|
|
208
|
+
}
|
|
209
|
+
const threadRow = z.object({
|
|
210
|
+
id: z.string(),
|
|
211
|
+
subject: z.string(),
|
|
212
|
+
status: z.string(),
|
|
213
|
+
created_at: z.union([z.date(), z.string()]),
|
|
214
|
+
updated_at: z.union([z.date(), z.string()]),
|
|
215
|
+
});
|
|
216
|
+
const messageRow = z.object({
|
|
217
|
+
id: z.string(),
|
|
218
|
+
thread_id: z.string(),
|
|
219
|
+
sequence: z.coerce.number().int().positive(),
|
|
220
|
+
author: z.enum(["user", "team", "system"]),
|
|
221
|
+
kind: z.enum(["comment", "question", "status"]),
|
|
222
|
+
body: z.string(),
|
|
223
|
+
created_at: z.union([z.date(), z.string()]),
|
|
224
|
+
offered_to_client_at: z.union([z.date(), z.string()]).nullable(),
|
|
225
|
+
});
|
|
226
|
+
async function transaction(pool, work, context) {
|
|
227
|
+
context.signal.throwIfAborted();
|
|
228
|
+
let abandoned = false;
|
|
229
|
+
const pendingClient = pool.connect();
|
|
230
|
+
pendingClient.then((client) => {
|
|
231
|
+
if (abandoned)
|
|
232
|
+
client.release();
|
|
233
|
+
}).catch(() => undefined);
|
|
234
|
+
let client;
|
|
235
|
+
try {
|
|
236
|
+
client = await beforeDeadline(pendingClient, context);
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
abandoned = true;
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
await client.query("BEGIN");
|
|
244
|
+
const remainingMs = Math.max(1, context.deadlineMs - Date.now());
|
|
245
|
+
await client.query("SELECT set_config('statement_timeout', $1, true)", [String(remainingMs)]);
|
|
246
|
+
context.signal.throwIfAborted();
|
|
247
|
+
const result = await work(client);
|
|
248
|
+
context.signal.throwIfAborted();
|
|
249
|
+
await client.query("COMMIT");
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
await client.query("ROLLBACK").catch(() => undefined);
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
client.release();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async function lockThread(client, scope, id) {
|
|
261
|
+
const result = await client.query(`
|
|
262
|
+
SELECT * FROM emseepea_feedback_threads
|
|
263
|
+
WHERE scope = $1 AND id = $2
|
|
264
|
+
FOR UPDATE
|
|
265
|
+
`, [scope, id]);
|
|
266
|
+
if (result.rows.length !== 1)
|
|
267
|
+
throw new Error("Feedback thread not found");
|
|
268
|
+
return threadRow.parse(result.rows[0]);
|
|
269
|
+
}
|
|
270
|
+
async function insertMessage(client, scope, threadId, id, sequence, author, kind, value) {
|
|
271
|
+
const result = await client.query(`
|
|
272
|
+
INSERT INTO emseepea_feedback_messages
|
|
273
|
+
(scope, thread_id, id, sequence, author, kind, body, created_at)
|
|
274
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, clock_timestamp())
|
|
275
|
+
RETURNING *
|
|
276
|
+
`, [scope, threadId, id, sequence, author, kind, value]);
|
|
277
|
+
return messageRow.parse(result.rows[0]);
|
|
278
|
+
}
|
|
279
|
+
async function writeOutbox(client, scope, events) {
|
|
280
|
+
if (events.length === 0)
|
|
281
|
+
return;
|
|
282
|
+
await client.query(`
|
|
283
|
+
INSERT INTO emseepea_feedback_outbox
|
|
284
|
+
(event_id, event_type, occurred_at, scope, thread_id, message_id, author)
|
|
285
|
+
SELECT id, type, "occurredAt"::timestamptz, $2, "threadId", "messageId", author
|
|
286
|
+
FROM jsonb_to_recordset($1::jsonb)
|
|
287
|
+
AS event(id text, type text, "occurredAt" text, "threadId" text, "messageId" text, author text)
|
|
288
|
+
ON CONFLICT (event_id) DO NOTHING
|
|
289
|
+
`, [JSON.stringify(events), scope]);
|
|
290
|
+
}
|
|
291
|
+
function conversation(thread, messages) {
|
|
292
|
+
return {
|
|
293
|
+
...threadValue(thread),
|
|
294
|
+
messages: messages.map(messageValue),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function threadValue(thread) {
|
|
298
|
+
return {
|
|
299
|
+
id: thread.id,
|
|
300
|
+
subject: thread.subject,
|
|
301
|
+
status: thread.status,
|
|
302
|
+
createdAt: iso(thread.created_at),
|
|
303
|
+
updatedAt: iso(thread.updated_at),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function messageValue(message) {
|
|
307
|
+
return {
|
|
308
|
+
id: message.id,
|
|
309
|
+
threadId: message.thread_id,
|
|
310
|
+
sequence: message.sequence,
|
|
311
|
+
author: message.author,
|
|
312
|
+
kind: message.kind,
|
|
313
|
+
body: message.body,
|
|
314
|
+
createdAt: iso(message.created_at),
|
|
315
|
+
offeredToClientAt: message.offered_to_client_at
|
|
316
|
+
? iso(message.offered_to_client_at)
|
|
317
|
+
: undefined,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function iso(value) {
|
|
321
|
+
if (value === null || value === undefined)
|
|
322
|
+
throw new Error("PostgreSQL feedback timestamp missing");
|
|
323
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
324
|
+
if (Number.isNaN(date.valueOf()))
|
|
325
|
+
throw new Error("PostgreSQL feedback timestamp invalid");
|
|
326
|
+
return date.toISOString();
|
|
327
|
+
}
|
|
328
|
+
function event(type, threadId, messageId, author, occurredAt) {
|
|
329
|
+
return {
|
|
330
|
+
id: messageId ? `${type}:${messageId}` : `${type}:${threadId}:${occurredAt}`,
|
|
331
|
+
type,
|
|
332
|
+
occurredAt,
|
|
333
|
+
threadId,
|
|
334
|
+
messageId,
|
|
335
|
+
author,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
function encodeCursor(updatedAt, id) {
|
|
339
|
+
return Buffer.from(JSON.stringify({ updatedAt, id })).toString("base64url");
|
|
340
|
+
}
|
|
341
|
+
function decodeCursor(cursor) {
|
|
342
|
+
if (cursor === undefined)
|
|
343
|
+
return undefined;
|
|
344
|
+
try {
|
|
345
|
+
return z.strictObject({ updatedAt: z.iso.datetime(), id: z.string().min(1).max(240) })
|
|
346
|
+
.parse(JSON.parse(Buffer.from(cursor, "base64url").toString("utf8")));
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
throw new Error("Invalid PostgreSQL feedback cursor");
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
//# sourceMappingURL=postgres.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres.js","sourceRoot":"","sources":["../src/postgres.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing-server.d.ts","sourceRoot":"","sources":["../src/testing-server.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { serveEmseepea } from "@emseepea/server";
|
|
3
|
+
import { defineFeedbackSubmission } from "./index.js";
|
|
4
|
+
const appModule = process.env.EMSEEPEA_EVAL_APP_MODULE;
|
|
5
|
+
const factoryName = process.env.EMSEEPEA_EVAL_APP_FACTORY;
|
|
6
|
+
const kind = process.env.EMSEEPEA_EVAL_APP_KIND ?? "options";
|
|
7
|
+
if (!appModule || !factoryName)
|
|
8
|
+
throw new Error("Example eval app factory is required");
|
|
9
|
+
const factory = (await import(appModule))[factoryName];
|
|
10
|
+
if (typeof factory !== "function")
|
|
11
|
+
throw new Error("Example eval app factory was not exported");
|
|
12
|
+
const feedback = defineFeedbackSubmission({
|
|
13
|
+
access: "public",
|
|
14
|
+
backend: {
|
|
15
|
+
submit: () => ({ id: randomUUID(), recordedAt: new Date().toISOString() }),
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
const extensions = { additionalTools: [feedback] };
|
|
19
|
+
const created = kind === "postgres"
|
|
20
|
+
? await factory({ databaseUrl: process.env.DATABASE_URL, ...extensions })
|
|
21
|
+
: kind === "mongodb"
|
|
22
|
+
? await factory({ uri: process.env.MONGODB_URL, ...extensions })
|
|
23
|
+
: kind === "soap"
|
|
24
|
+
? await factory(process.env.PEA_SOAP_URL, extensions)
|
|
25
|
+
: await factory(extensions);
|
|
26
|
+
if (!created || typeof created !== "object")
|
|
27
|
+
throw new Error("Example eval app factory returned no app");
|
|
28
|
+
const app = "app" in created ? created.app : created;
|
|
29
|
+
const closeProvider = "closeProvider" in created && typeof created.closeProvider === "function"
|
|
30
|
+
? created.closeProvider.bind(created)
|
|
31
|
+
: undefined;
|
|
32
|
+
const running = await serveEmseepea(app, {
|
|
33
|
+
port: Number.parseInt(process.env.PORT ?? "3000", 10),
|
|
34
|
+
});
|
|
35
|
+
console.log(`Feedback-enabled example eval server listening at ${running.url}`);
|
|
36
|
+
async function shutdown() {
|
|
37
|
+
try {
|
|
38
|
+
await running.close();
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
await closeProvider?.();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
process.once("SIGINT", () => void shutdown());
|
|
45
|
+
process.once("SIGTERM", () => void shutdown());
|
|
46
|
+
//# sourceMappingURL=testing-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing-server.js","sourceRoot":"","sources":["../src/testing-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { FeedbackAdapterContext, FeedbackEvent } from "./index.js";
|
|
2
|
+
export interface FeedbackWebhookStore {
|
|
3
|
+
claim(eventId: string, context: FeedbackAdapterContext): boolean | Promise<boolean>;
|
|
4
|
+
resolveScope(scopeHash: string, context: FeedbackAdapterContext): string | undefined | Promise<string | undefined>;
|
|
5
|
+
}
|
|
6
|
+
interface WebhookInput {
|
|
7
|
+
readonly body: Uint8Array;
|
|
8
|
+
readonly headers: Readonly<Record<string, string | undefined>>;
|
|
9
|
+
}
|
|
10
|
+
export declare function ingestGitHubFeedbackWebhook(input: WebhookInput, options: Readonly<{
|
|
11
|
+
secret: string;
|
|
12
|
+
owner: string;
|
|
13
|
+
repository: string;
|
|
14
|
+
store: FeedbackWebhookStore;
|
|
15
|
+
}>, context: FeedbackAdapterContext): Promise<readonly FeedbackEvent[]>;
|
|
16
|
+
export declare function ingestZendeskFeedbackWebhook(input: WebhookInput, options: Readonly<{
|
|
17
|
+
secret: string;
|
|
18
|
+
subdomain: string;
|
|
19
|
+
store: FeedbackWebhookStore;
|
|
20
|
+
}>, context: FeedbackAdapterContext): Promise<readonly FeedbackEvent[]>;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=webhooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAkCxE,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpF,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACpH;AAED,UAAU,YAAY;IACpB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;CAChE;AAED,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,QAAQ,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,oBAAoB,CAAC;CAC7B,CAAC,EACF,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC,CAgBnC;AAED,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,QAAQ,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,oBAAoB,CAAC;CAC7B,CAAC,EACF,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC,CA2BnC"}
|
package/dist/webhooks.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { beforeDeadline } from "./deadline.js";
|
|
4
|
+
import { feedbackEventSchema } from "./index.js";
|
|
5
|
+
const maximumWebhookBytes = 64 * 1024;
|
|
6
|
+
const timestamp = z.iso.datetime({ offset: true });
|
|
7
|
+
const githubPayloadSchema = z.object({
|
|
8
|
+
action: z.string().min(1),
|
|
9
|
+
repository: z.object({ full_name: z.string().min(3) }),
|
|
10
|
+
issue: z.object({
|
|
11
|
+
number: z.number().int().positive(),
|
|
12
|
+
updated_at: timestamp,
|
|
13
|
+
labels: z.array(z.object({ name: z.string() })),
|
|
14
|
+
}),
|
|
15
|
+
comment: z.object({
|
|
16
|
+
id: z.number().int().positive(),
|
|
17
|
+
body: z.string(),
|
|
18
|
+
created_at: timestamp,
|
|
19
|
+
}).optional(),
|
|
20
|
+
});
|
|
21
|
+
const zendeskPayloadSchema = z.strictObject({
|
|
22
|
+
eventId: z.string().min(1).max(240),
|
|
23
|
+
subdomain: z.string().min(1).max(63),
|
|
24
|
+
ticketId: z.number().int().positive(),
|
|
25
|
+
scopeTag: z.string().regex(/^emseepea_[a-f0-9]{24}$/),
|
|
26
|
+
occurredAt: timestamp,
|
|
27
|
+
change: z.discriminatedUnion("type", [
|
|
28
|
+
z.strictObject({
|
|
29
|
+
type: z.literal("public_comment"),
|
|
30
|
+
commentId: z.number().int().positive(),
|
|
31
|
+
author: z.enum(["user", "team"]),
|
|
32
|
+
}),
|
|
33
|
+
z.strictObject({ type: z.literal("status_changed") }),
|
|
34
|
+
]),
|
|
35
|
+
});
|
|
36
|
+
export async function ingestGitHubFeedbackWebhook(input, options, context) {
|
|
37
|
+
const body = checkedBody(input.body);
|
|
38
|
+
verifyHexSignature(input.headers["x-hub-signature-256"], checkedSecret(options.secret), body);
|
|
39
|
+
const delivery = requiredHeader(input.headers, "x-github-delivery");
|
|
40
|
+
const kind = requiredHeader(input.headers, "x-github-event");
|
|
41
|
+
const payload = githubPayloadSchema.parse(JSON.parse(new TextDecoder().decode(body)));
|
|
42
|
+
if (payload.repository.full_name !== `${options.owner}/${options.repository}`) {
|
|
43
|
+
throw new Error("GitHub feedback webhook targeted the wrong repository");
|
|
44
|
+
}
|
|
45
|
+
const labels = payload.issue.labels.filter(({ name }) => name.startsWith("emseepea-"));
|
|
46
|
+
if (labels.length !== 1)
|
|
47
|
+
throw new Error("GitHub feedback webhook must have exactly one feedback scope");
|
|
48
|
+
const label = labels[0];
|
|
49
|
+
const scope = await resolveScope(options.store, label.name.slice("emseepea-".length), context);
|
|
50
|
+
const event = githubEvent(kind, delivery, payload, scope);
|
|
51
|
+
if (!await beforeDeadline(Promise.resolve(options.store.claim(`github:${delivery}`, context)), context))
|
|
52
|
+
return [];
|
|
53
|
+
return Object.freeze([Object.freeze(feedbackEventSchema.parse(event))]);
|
|
54
|
+
}
|
|
55
|
+
export async function ingestZendeskFeedbackWebhook(input, options, context) {
|
|
56
|
+
const body = checkedBody(input.body);
|
|
57
|
+
const sentAt = requiredHeader(input.headers, "x-zendesk-webhook-signature-timestamp");
|
|
58
|
+
verifyBase64Signature(input.headers["x-zendesk-webhook-signature"], checkedSecret(options.secret), new TextEncoder().encode(`${sentAt}${new TextDecoder().decode(body)}`));
|
|
59
|
+
const payload = zendeskPayloadSchema.parse(JSON.parse(new TextDecoder().decode(body)));
|
|
60
|
+
if (payload.subdomain !== options.subdomain) {
|
|
61
|
+
throw new Error("Zendesk feedback webhook targeted the wrong account");
|
|
62
|
+
}
|
|
63
|
+
const scope = await resolveScope(options.store, payload.scopeTag.slice("emseepea_".length), context);
|
|
64
|
+
if (!await beforeDeadline(Promise.resolve(options.store.claim(`zendesk:${payload.eventId}`, context)), context))
|
|
65
|
+
return [];
|
|
66
|
+
const threadId = `zendesk-ticket-${payload.ticketId}`;
|
|
67
|
+
const change = payload.change;
|
|
68
|
+
return Object.freeze([Object.freeze(feedbackEventSchema.parse({
|
|
69
|
+
id: `zendesk:${payload.eventId}`,
|
|
70
|
+
type: change.type === "status_changed" ? "feedback.status.changed" : "feedback.message.added",
|
|
71
|
+
occurredAt: payload.occurredAt,
|
|
72
|
+
scope,
|
|
73
|
+
threadId,
|
|
74
|
+
...(change.type === "public_comment" ? {
|
|
75
|
+
messageId: `zendesk-comment-${change.commentId}`,
|
|
76
|
+
author: change.author,
|
|
77
|
+
} : {}),
|
|
78
|
+
}))]);
|
|
79
|
+
}
|
|
80
|
+
function githubEvent(kind, delivery, payload, scope) {
|
|
81
|
+
const threadId = `github-issue-${payload.issue.number}`;
|
|
82
|
+
if (kind === "issue_comment" && payload.action === "created" && payload.comment) {
|
|
83
|
+
return Object.freeze({
|
|
84
|
+
id: `github:${delivery}`,
|
|
85
|
+
type: "feedback.message.added",
|
|
86
|
+
occurredAt: payload.comment.created_at,
|
|
87
|
+
scope,
|
|
88
|
+
threadId,
|
|
89
|
+
messageId: `github-comment-${payload.comment.id}`,
|
|
90
|
+
author: payload.comment.body.startsWith("<!-- emseepea-feedback:user -->") ? "user" : "team",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (kind === "issues" && ["assigned", "closed", "labeled", "milestoned", "reopened", "unassigned", "unlabeled"].includes(payload.action)) {
|
|
94
|
+
return Object.freeze({
|
|
95
|
+
id: `github:${delivery}`,
|
|
96
|
+
type: "feedback.status.changed",
|
|
97
|
+
occurredAt: payload.issue.updated_at,
|
|
98
|
+
scope,
|
|
99
|
+
threadId,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
throw new Error("Unsupported GitHub feedback webhook event");
|
|
103
|
+
}
|
|
104
|
+
async function resolveScope(store, hash, context) {
|
|
105
|
+
const scope = await beforeDeadline(Promise.resolve(store.resolveScope(hash, context)), context);
|
|
106
|
+
return z.string().min(1).max(240).parse(scope);
|
|
107
|
+
}
|
|
108
|
+
function checkedBody(body) {
|
|
109
|
+
if (body.byteLength === 0 || body.byteLength > maximumWebhookBytes) {
|
|
110
|
+
throw new Error("Feedback webhook body is empty or exceeded 64 KiB");
|
|
111
|
+
}
|
|
112
|
+
return body;
|
|
113
|
+
}
|
|
114
|
+
function checkedSecret(secret) {
|
|
115
|
+
if (Buffer.byteLength(secret, "utf8") < 32) {
|
|
116
|
+
throw new Error("Feedback webhook secret must be at least 32 bytes");
|
|
117
|
+
}
|
|
118
|
+
return secret;
|
|
119
|
+
}
|
|
120
|
+
function requiredHeader(headers, name) {
|
|
121
|
+
const value = headers[name];
|
|
122
|
+
if (!value)
|
|
123
|
+
throw new Error(`Feedback webhook is missing ${name}`);
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
function verifyHexSignature(value, secret, body) {
|
|
127
|
+
const expected = `sha256=${createHmac("sha256", secret).update(body).digest("hex")}`;
|
|
128
|
+
verifySignature(value, expected);
|
|
129
|
+
}
|
|
130
|
+
function verifyBase64Signature(value, secret, body) {
|
|
131
|
+
verifySignature(value, createHmac("sha256", secret).update(body).digest("base64"));
|
|
132
|
+
}
|
|
133
|
+
function verifySignature(value, expected) {
|
|
134
|
+
if (!value)
|
|
135
|
+
throw new Error("Feedback webhook signature is missing");
|
|
136
|
+
const actualBytes = Buffer.from(value);
|
|
137
|
+
const expectedBytes = Buffer.from(expected);
|
|
138
|
+
if (actualBytes.length !== expectedBytes.length || !timingSafeEqual(actualBytes, expectedBytes)) {
|
|
139
|
+
throw new Error("Feedback webhook signature is invalid");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=webhooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FeedbackConversationBackend, FeedbackSubmissionBackend } from "./index.js";
|
|
2
|
+
export interface ZendeskFeedbackOptions {
|
|
3
|
+
readonly subdomain?: string;
|
|
4
|
+
readonly baseUrl?: URL;
|
|
5
|
+
readonly token: string | (() => string | Promise<string>);
|
|
6
|
+
readonly requesterId?: (scope: string) => number | Promise<number>;
|
|
7
|
+
readonly tags?: readonly string[];
|
|
8
|
+
readonly fetch?: typeof fetch;
|
|
9
|
+
}
|
|
10
|
+
export declare function createZendeskFeedbackBackend(options: ZendeskFeedbackOptions): FeedbackConversationBackend;
|
|
11
|
+
export declare function createZendeskFeedbackSubmissionBackend(options: ZendeskFeedbackOptions): FeedbackSubmissionBackend;
|
|
12
|
+
//# sourceMappingURL=zendesk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zendesk.d.ts","sourceRoot":"","sources":["../src/zendesk.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAGV,2BAA2B,EAE3B,yBAAyB,EAE1B,MAAM,YAAY,CAAC;AAyBpB,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1D,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnE,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CAC/B;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,sBAAsB,GAC9B,2BAA2B,CAsJ7B;AAED,wBAAgB,sCAAsC,CACpD,OAAO,EAAE,sBAAsB,GAC9B,yBAAyB,CAe3B"}
|