agent-pager 0.1.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.
@@ -0,0 +1,45 @@
1
+ import { createHash, generateKeyPairSync, randomBytes, sign, verify } from "node:crypto";
2
+ export function newId(prefix) {
3
+ return `${prefix}_${randomBytes(12).toString("hex")}`;
4
+ }
5
+ export function nowIso() {
6
+ return new Date().toISOString();
7
+ }
8
+ export function sha256(value) {
9
+ return createHash("sha256").update(value).digest("hex");
10
+ }
11
+ export function createDeviceKeyPair() {
12
+ const { publicKey, privateKey } = generateKeyPairSync("ed25519");
13
+ return {
14
+ publicKeyPem: publicKey.export({ type: "spki", format: "pem" }).toString(),
15
+ privateKeyPem: privateKey.export({ type: "pkcs8", format: "pem" }).toString()
16
+ };
17
+ }
18
+ export function signText(privateKeyPem, text) {
19
+ return sign(null, Buffer.from(text), privateKeyPem).toString("base64");
20
+ }
21
+ export function verifyText(publicKeyPem, text, signature) {
22
+ try {
23
+ return verify(null, Buffer.from(text), publicKeyPem, Buffer.from(signature, "base64"));
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }
29
+ export function canonicalRequest(method, pathAndQuery, timestamp, body) {
30
+ return [method.toUpperCase(), pathAndQuery, timestamp, sha256(body)].join("\n");
31
+ }
32
+ export function stableJson(value) {
33
+ return JSON.stringify(sortKeys(value));
34
+ }
35
+ function sortKeys(value) {
36
+ if (Array.isArray(value)) {
37
+ return value.map(sortKeys);
38
+ }
39
+ if (value && typeof value === "object") {
40
+ return Object.fromEntries(Object.entries(value)
41
+ .sort(([a], [b]) => a.localeCompare(b))
42
+ .map(([key, child]) => [key, sortKeys(child)]));
43
+ }
44
+ return value;
45
+ }
@@ -0,0 +1,51 @@
1
+ import { stableJson, verifyText } from "./crypto.js";
2
+ import { apiRequest } from "./http-client.js";
3
+ import { deliverPageLocally } from "./session-adapters.js";
4
+ export async function deliverPendingHostedPages(config, seenDeliveryIds) {
5
+ const result = await apiRequest("/api/deliveries?status=pending", { config });
6
+ let delivered = 0;
7
+ for (const delivery of result.deliveries) {
8
+ if (await deliverHostedDelivery(delivery, config, seenDeliveryIds))
9
+ delivered += 1;
10
+ }
11
+ return delivered;
12
+ }
13
+ export async function deliverHostedDelivery(delivery, config, seenDeliveryIds) {
14
+ if (delivery.deviceId && delivery.deviceId !== config.deviceId)
15
+ return false;
16
+ if (seenDeliveryIds?.has(delivery.id)) {
17
+ await apiRequest(`/api/deliveries/${delivery.id}/ack`, { method: "POST", body: {}, config });
18
+ return false;
19
+ }
20
+ if (!verifyText(config.serverPublicKeyPem, stableJson(delivery.envelope), delivery.serverSignature)) {
21
+ console.error("Dropped page with invalid server signature.");
22
+ return false;
23
+ }
24
+ seenDeliveryIds?.add(delivery.id);
25
+ await deliverPageLocally(pageFromHostedEnvelope(delivery.envelope));
26
+ await apiRequest(`/api/deliveries/${delivery.id}/ack`, { method: "POST", body: {}, config });
27
+ return true;
28
+ }
29
+ export async function deliverLegacyEnvelope(envelope, config) {
30
+ const unsigned = { page: envelope.page, serverTime: envelope.serverTime, nonce: envelope.nonce };
31
+ if (!verifyText(config.serverPublicKeyPem, stableJson(unsigned), envelope.signature)) {
32
+ console.error("Dropped page with invalid server signature.");
33
+ return false;
34
+ }
35
+ await deliverPageLocally(envelope.page);
36
+ await apiRequest(`/api/pages/${envelope.page.id}/ack`, { method: "POST", body: {}, config });
37
+ return true;
38
+ }
39
+ function pageFromHostedEnvelope(envelope) {
40
+ return {
41
+ id: envelope.page.id,
42
+ fromUserId: envelope.page.from_user_id,
43
+ toUserId: envelope.page.to_user_id,
44
+ fromUsername: envelope.page.fromUsername,
45
+ toUsername: envelope.page.toUsername,
46
+ message: envelope.page.message,
47
+ urgency: envelope.page.urgency,
48
+ replyToPageId: envelope.page.reply_to_page_id || undefined,
49
+ createdAt: envelope.page.created_at
50
+ };
51
+ }