@syengup/friday-channel-next 0.0.37 → 0.0.39
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/index.js +10 -4
- package/index.ts +10 -4
- package/install.js +132 -257
- package/package.json +1 -1
- package/dist/attachments/0768c9b1-53b0-44df-83e8-be15c4ea188f.jpg +0 -0
- package/dist/attachments/0a379d01-116b-4da1-bf15-77cb2cbb0093.jpg +0 -0
- package/dist/attachments/181caab2-64a7-4004-a057-225a144f949e.mp3 +0 -0
- package/dist/attachments/19662331-e527-47d2-bc0e-0e19a7a91419.jpg +0 -0
- package/dist/attachments/26a23b2b-52df-4572-a5e1-15b34fb87e44.jpg +0 -0
- package/dist/attachments/2f9282c5-8db4-4c4a-a060-e65104f6f9ff.jpg +0 -0
- package/dist/attachments/3929ec3d-ea15-4de6-96bc-97e8b0b658a7.jpg +0 -0
- package/dist/attachments/403c0cbc-4e3c-4146-a3be-ff3746ee7cda.jpg +0 -0
- package/dist/attachments/441977f5-0f7b-4aa2-841a-1d63e787ea53.jpg +0 -0
- package/dist/attachments/453e8aa2-76e3-498d-8d6f-d7b96d6bf45b.jpg +0 -0
- package/dist/attachments/538cde71-d26e-4d3d-b901-e8dd905e668c.mp3 +0 -0
- package/dist/attachments/55c7f628-4ba2-4252-aa4b-4f3eb6045a8a.mp3 +0 -0
- package/dist/attachments/5f7683f5-8194-4698-b077-31d209525379.jpg +0 -0
- package/dist/attachments/60614a35-8f44-4197-b783-2f58f5a72ac8.jpeg +0 -0
- package/dist/attachments/62830489-8814-48b1-851c-3845e514f35e.mp3 +0 -0
- package/dist/attachments/66f4a62d-1531-4f38-a531-7456f9edf221.png +0 -0
- package/dist/attachments/6735d749-769e-483a-9b84-43b9338a720b.png +0 -0
- package/dist/attachments/6d1766b1-05e4-4b04-b3c8-1c25e9d182a1.png +0 -0
- package/dist/attachments/782b077b-06e3-484b-baf5-33e7160234ed.png +0 -0
- package/dist/attachments/7ad638b2-1f56-4d93-9ad8-b40346e0650f.jpg +0 -0
- package/dist/attachments/89f6fb15-e652-4111-a60c-baa414659052.png +0 -0
- package/dist/attachments/8a88b14f-442f-45fb-b01d-e51bab8f800d.mp3 +0 -0
- package/dist/attachments/92292034-9cf6-4f26-8d77-fddca3deb638.png +0 -0
- package/dist/attachments/92c2b414-d33d-4d93-bcb6-013da7bec9a4.jpg +0 -0
- package/dist/attachments/9664f69e-3c05-45ca-9a52-f2d0b9f9bf7e.jpg +0 -0
- package/dist/attachments/977d28c1-43c0-40e0-95e3-defe0f41afe8.jpg +0 -0
- package/dist/attachments/9df40f1a-c6e1-4177-8a03-06757a30b19e.png +0 -0
- package/dist/attachments/a68e6815-6163-4421-a70f-34493aa9a217.jpg +0 -0
- package/dist/attachments/aab32fea-6d99-47ec-ab1f-2340f31312eb.jpg +0 -0
- package/dist/attachments/ab403224-2fb1-49c1-8738-ea194ab65d44.png +0 -0
- package/dist/attachments/ac3da190-d6ee-4038-a673-8b893035a687.png +0 -0
- package/dist/attachments/af02be9c-87f7-4c5a-9969-7db32039bb58.png +0 -0
- package/dist/attachments/b011d42a-00e5-4f77-86bc-08da6112e6e1.mp3 +0 -0
- package/dist/attachments/b7d7df40-c627-4b1f-9b09-167b88545c25.mp3 +0 -0
- package/dist/attachments/c5e9bf09-a718-422c-bcb3-94c173e3755b.mp3 +0 -0
- package/dist/attachments/d5449e13-1995-44ba-9392-ecbfe5f9876f.jpg +0 -0
- package/dist/attachments/ea0069f5-01cf-4ea1-985e-3a1e426399c3.png +0 -0
- package/dist/attachments/f3989ff2-7b70-4a80-a896-74a6b197f7d8.png +0 -0
- package/dist/attachments/f64a4a14-e3aa-4eed-a8d9-1603f04baa5b.jpg +0 -0
- package/dist/src/http/handlers/device-token.d.ts +0 -2
- package/dist/src/http/handlers/device-token.js +0 -43
- package/dist/src/http/handlers/import.d.ts +0 -7
- package/dist/src/http/handlers/import.js +0 -69
- package/dist/src/http/handlers/info.d.ts +0 -2
- package/dist/src/http/handlers/info.js +0 -13
- package/dist/src/http/handlers/messages-list.d.ts +0 -7
- package/dist/src/http/handlers/messages-list.js +0 -44
- package/dist/src/http/handlers/pair.d.ts +0 -2
- package/dist/src/http/handlers/pair.js +0 -39
- package/dist/src/http/handlers/sessions-list.d.ts +0 -8
- package/dist/src/http/handlers/sessions-list.js +0 -24
- package/dist/src/http/handlers/sessions-messages-get.d.ts +0 -2
- package/dist/src/http/handlers/sessions-messages-get.js +0 -55
- package/dist/src/http/handlers/sessions-messages-post.d.ts +0 -2
- package/dist/src/http/handlers/sessions-messages-post.js +0 -92
- package/dist/src/http/handlers/sessions-messages.d.ts +0 -2
- package/dist/src/http/handlers/sessions-messages.js +0 -135
- package/dist/src/http/handlers/sync.d.ts +0 -7
- package/dist/src/http/handlers/sync.js +0 -56
- package/dist/src/push/apns.d.ts +0 -15
- package/dist/src/push/apns.js +0 -56
- package/dist/src/push/device-tokens.d.ts +0 -3
- package/dist/src/push/device-tokens.js +0 -39
- package/dist/src/sync/account-identity.d.ts +0 -14
- package/dist/src/sync/account-identity.js +0 -101
- package/dist/src/sync/archive.d.ts +0 -9
- package/dist/src/sync/archive.js +0 -25
- package/dist/src/sync/database.d.ts +0 -66
- package/dist/src/sync/database.js +0 -364
- package/dist/src/sync/init.d.ts +0 -3
- package/dist/src/sync/init.js +0 -14
- package/dist/src/sync/installation-id.d.ts +0 -1
- package/dist/src/sync/installation-id.js +0 -41
- package/dist/src/sync/message-accumulator.d.ts +0 -29
- package/dist/src/sync/message-accumulator.js +0 -188
- package/dist/src/sync/message-store.d.ts +0 -68
- package/dist/src/sync/message-store.js +0 -262
- package/dist/src/sync/push-store.d.ts +0 -5
- package/dist/src/sync/push-store.js +0 -54
- package/dist/src/sync/session-key.d.ts +0 -12
- package/dist/src/sync/session-key.js +0 -47
- package/dist/src/sync/sync-state.d.ts +0 -5
- package/dist/src/sync/sync-state.js +0 -54
- package/dist/src/sync/transcript-archive.d.ts +0 -13
- package/dist/src/sync/transcript-archive.js +0 -37
- package/dist/src/sync/transcript-store.d.ts +0 -35
- package/dist/src/sync/transcript-store.js +0 -221
- package/dist/src/sync/translate.d.ts +0 -42
- package/dist/src/sync/translate.js +0 -171
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { extractBearerToken } from "../middleware/auth.js";
|
|
2
|
-
import { readJsonBody } from "../middleware/body.js";
|
|
3
|
-
import { saveToken } from "../../push/device-tokens.js";
|
|
4
|
-
export async function handleDeviceToken(req, res) {
|
|
5
|
-
if (req.method !== "POST") {
|
|
6
|
-
res.statusCode = 405;
|
|
7
|
-
res.setHeader("Content-Type", "application/json");
|
|
8
|
-
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
9
|
-
return true;
|
|
10
|
-
}
|
|
11
|
-
if (!extractBearerToken(req)) {
|
|
12
|
-
res.statusCode = 401;
|
|
13
|
-
res.setHeader("Content-Type", "application/json");
|
|
14
|
-
res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
|
|
15
|
-
return true;
|
|
16
|
-
}
|
|
17
|
-
let body;
|
|
18
|
-
try {
|
|
19
|
-
body = await readJsonBody(req);
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
body = null;
|
|
23
|
-
}
|
|
24
|
-
if (!body) {
|
|
25
|
-
res.statusCode = 400;
|
|
26
|
-
res.setHeader("Content-Type", "application/json");
|
|
27
|
-
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
const deviceId = typeof body.deviceId === "string" && body.deviceId.trim() ? body.deviceId.trim() : "";
|
|
31
|
-
const token = typeof body.token === "string" && body.token.trim() ? body.token.trim() : "";
|
|
32
|
-
if (!deviceId || !token) {
|
|
33
|
-
res.statusCode = 400;
|
|
34
|
-
res.setHeader("Content-Type", "application/json");
|
|
35
|
-
res.end(JSON.stringify({ error: "deviceId and token are required" }));
|
|
36
|
-
return true;
|
|
37
|
-
}
|
|
38
|
-
saveToken(deviceId, token);
|
|
39
|
-
res.statusCode = 200;
|
|
40
|
-
res.setHeader("Content-Type", "application/json");
|
|
41
|
-
res.end(JSON.stringify({ ok: true }));
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* POST /friday-next/sessions/{sessionKey}/import
|
|
3
|
-
*
|
|
4
|
-
* First-time bulk import. Rejects if session already has data (409 Conflict).
|
|
5
|
-
*/
|
|
6
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
7
|
-
export declare function handleImport(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { extractBearerToken } from "../middleware/auth.js";
|
|
2
|
-
import { readJsonBody } from "../middleware/body.js";
|
|
3
|
-
import { importMessages, ConflictError } from "../../sync/message-store.js";
|
|
4
|
-
import { toSessionStoreKey } from "../../session/session-manager.js";
|
|
5
|
-
export async function handleImport(req, res) {
|
|
6
|
-
if (req.method !== "POST") {
|
|
7
|
-
res.statusCode = 405;
|
|
8
|
-
res.setHeader("Content-Type", "application/json");
|
|
9
|
-
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
10
|
-
return true;
|
|
11
|
-
}
|
|
12
|
-
const token = extractBearerToken(req);
|
|
13
|
-
if (!token) {
|
|
14
|
-
res.statusCode = 401;
|
|
15
|
-
res.setHeader("Content-Type", "application/json");
|
|
16
|
-
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
17
|
-
return true;
|
|
18
|
-
}
|
|
19
|
-
const url = new URL(req.url ?? "/", "http://localhost");
|
|
20
|
-
const pathname = url.pathname;
|
|
21
|
-
const prefix = "/friday-next/sessions/";
|
|
22
|
-
const suffix = "/import";
|
|
23
|
-
if (!pathname.startsWith(prefix) || !pathname.endsWith(suffix)) {
|
|
24
|
-
res.statusCode = 400;
|
|
25
|
-
res.setHeader("Content-Type", "application/json");
|
|
26
|
-
res.end(JSON.stringify({ error: "Missing sessionKey in URL path" }));
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
const rawKey = pathname.slice(prefix.length, pathname.length - suffix.length);
|
|
30
|
-
const sessionKey = toSessionStoreKey(decodeURIComponent(rawKey));
|
|
31
|
-
const body = await readJsonBody(req, 50 * 1024 * 1024); // 50 MB for bulk import
|
|
32
|
-
if (!body) {
|
|
33
|
-
res.statusCode = 400;
|
|
34
|
-
res.setHeader("Content-Type", "application/json");
|
|
35
|
-
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
36
|
-
return true;
|
|
37
|
-
}
|
|
38
|
-
const deviceId = typeof body.deviceId === "string" ? body.deviceId.trim() : "";
|
|
39
|
-
if (!deviceId) {
|
|
40
|
-
res.statusCode = 400;
|
|
41
|
-
res.setHeader("Content-Type", "application/json");
|
|
42
|
-
res.end(JSON.stringify({ error: "Missing required field: deviceId" }));
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
const messages = Array.isArray(body.messages) ? body.messages : [];
|
|
46
|
-
if (messages.length === 0) {
|
|
47
|
-
res.statusCode = 400;
|
|
48
|
-
res.setHeader("Content-Type", "application/json");
|
|
49
|
-
res.end(JSON.stringify({ error: "Missing required field: messages (non-empty array)" }));
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
const result = importMessages({ sessionKey, deviceId, messages });
|
|
54
|
-
res.statusCode = 200;
|
|
55
|
-
res.setHeader("Content-Type", "application/json");
|
|
56
|
-
res.end(JSON.stringify(result));
|
|
57
|
-
}
|
|
58
|
-
catch (err) {
|
|
59
|
-
if (err instanceof ConflictError) {
|
|
60
|
-
res.statusCode = 409;
|
|
61
|
-
res.setHeader("Content-Type", "application/json");
|
|
62
|
-
res.end(JSON.stringify({ error: err.message }));
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
throw err;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { getInstallationId } from "../../sync/installation-id.js";
|
|
2
|
-
export async function handleInfo(req, res) {
|
|
3
|
-
if (req.method !== "GET") {
|
|
4
|
-
res.statusCode = 405;
|
|
5
|
-
res.setHeader("Content-Type", "application/json");
|
|
6
|
-
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
7
|
-
return true;
|
|
8
|
-
}
|
|
9
|
-
res.statusCode = 200;
|
|
10
|
-
res.setHeader("Content-Type", "application/json");
|
|
11
|
-
res.end(JSON.stringify({ ok: true, channel: "friday-next", version: "v2", installationId: getInstallationId() }));
|
|
12
|
-
return true;
|
|
13
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GET /friday-next/sessions/{sessionKey}/messages?before=&limit=
|
|
3
|
-
*
|
|
4
|
-
* Paginated pull of historical messages for a session.
|
|
5
|
-
*/
|
|
6
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
7
|
-
export declare function handleMessagesList(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { extractBearerToken } from "../middleware/auth.js";
|
|
2
|
-
import { getMessagesBefore } from "../../sync/message-store.js";
|
|
3
|
-
import { toSessionStoreKey } from "../../session/session-manager.js";
|
|
4
|
-
export async function handleMessagesList(req, res) {
|
|
5
|
-
if (req.method !== "GET") {
|
|
6
|
-
res.statusCode = 405;
|
|
7
|
-
res.setHeader("Content-Type", "application/json");
|
|
8
|
-
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
9
|
-
return true;
|
|
10
|
-
}
|
|
11
|
-
const token = extractBearerToken(req);
|
|
12
|
-
if (!token) {
|
|
13
|
-
res.statusCode = 401;
|
|
14
|
-
res.setHeader("Content-Type", "application/json");
|
|
15
|
-
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
16
|
-
return true;
|
|
17
|
-
}
|
|
18
|
-
const url = new URL(req.url ?? "/", "http://localhost");
|
|
19
|
-
const pathname = url.pathname;
|
|
20
|
-
const prefix = "/friday-next/sessions/";
|
|
21
|
-
const suffix = "/messages";
|
|
22
|
-
if (!pathname.startsWith(prefix) || !pathname.endsWith(suffix)) {
|
|
23
|
-
res.statusCode = 400;
|
|
24
|
-
res.setHeader("Content-Type", "application/json");
|
|
25
|
-
res.end(JSON.stringify({ error: "Missing sessionKey in URL path" }));
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
const rawKey = pathname.slice(prefix.length, pathname.length - suffix.length);
|
|
29
|
-
const sessionKey = toSessionStoreKey(decodeURIComponent(rawKey));
|
|
30
|
-
const before = url.searchParams.get("before") ?? undefined;
|
|
31
|
-
const limitRaw = url.searchParams.get("limit");
|
|
32
|
-
const limit = limitRaw ? parseInt(limitRaw, 10) : 25;
|
|
33
|
-
if (limitRaw && (isNaN(limit) || limit < 1 || limit > 50)) {
|
|
34
|
-
res.statusCode = 400;
|
|
35
|
-
res.setHeader("Content-Type", "application/json");
|
|
36
|
-
res.end(JSON.stringify({ error: "Invalid limit (1-50)" }));
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
const result = getMessagesBefore(sessionKey, before, limit);
|
|
40
|
-
res.statusCode = 200;
|
|
41
|
-
res.setHeader("Content-Type", "application/json");
|
|
42
|
-
res.end(JSON.stringify(result));
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { getOrCreateAccountId } from "../../sync/account-identity.js";
|
|
2
|
-
import { getInstallationId } from "../../sync/installation-id.js";
|
|
3
|
-
import { extractBearerToken } from "../middleware/auth.js";
|
|
4
|
-
import { readJsonBody } from "../middleware/body.js";
|
|
5
|
-
export async function handlePair(req, res) {
|
|
6
|
-
if (req.method !== "POST") {
|
|
7
|
-
res.statusCode = 405;
|
|
8
|
-
res.setHeader("Content-Type", "application/json");
|
|
9
|
-
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
10
|
-
return true;
|
|
11
|
-
}
|
|
12
|
-
if (!extractBearerToken(req)) {
|
|
13
|
-
res.statusCode = 401;
|
|
14
|
-
res.setHeader("Content-Type", "application/json");
|
|
15
|
-
res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
|
|
16
|
-
return true;
|
|
17
|
-
}
|
|
18
|
-
const body = await readJsonBody(req);
|
|
19
|
-
const deviceId = typeof body?.deviceId === "string" ? body.deviceId.trim() : "";
|
|
20
|
-
if (!deviceId) {
|
|
21
|
-
res.statusCode = 400;
|
|
22
|
-
res.setHeader("Content-Type", "application/json");
|
|
23
|
-
res.end(JSON.stringify({ error: "Missing required field: deviceId" }));
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
const claimAccountId = typeof body?.claimAccountId === "string" ? body.claimAccountId.trim() : undefined;
|
|
27
|
-
const claim = body?.claim && typeof body.claim === "object" && !Array.isArray(body.claim)
|
|
28
|
-
? body.claim
|
|
29
|
-
: {};
|
|
30
|
-
const result = getOrCreateAccountId({
|
|
31
|
-
deviceId,
|
|
32
|
-
claimAccountId,
|
|
33
|
-
claimConfirmed: claim.confirmed === true,
|
|
34
|
-
});
|
|
35
|
-
res.statusCode = 200;
|
|
36
|
-
res.setHeader("Content-Type", "application/json");
|
|
37
|
-
res.end(JSON.stringify({ ok: true, installationId: getInstallationId(), ...result }));
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GET /friday-next/sessions?deviceId=...
|
|
3
|
-
*
|
|
4
|
-
* List sessions. If deviceId is provided, filters by device.
|
|
5
|
-
* If deviceId is omitted, returns all sessions (for app reinstall recovery when deviceId changed).
|
|
6
|
-
*/
|
|
7
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
8
|
-
export declare function handleSessionsList(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { extractBearerToken } from "../middleware/auth.js";
|
|
2
|
-
import { listSessions, listAllSessionsList } from "../../sync/message-store.js";
|
|
3
|
-
export async function handleSessionsList(req, res) {
|
|
4
|
-
if (req.method !== "GET") {
|
|
5
|
-
res.statusCode = 405;
|
|
6
|
-
res.setHeader("Content-Type", "application/json");
|
|
7
|
-
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
8
|
-
return true;
|
|
9
|
-
}
|
|
10
|
-
const token = extractBearerToken(req);
|
|
11
|
-
if (!token) {
|
|
12
|
-
res.statusCode = 401;
|
|
13
|
-
res.setHeader("Content-Type", "application/json");
|
|
14
|
-
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
15
|
-
return true;
|
|
16
|
-
}
|
|
17
|
-
const url = new URL(req.url ?? "/", "http://localhost");
|
|
18
|
-
const deviceId = url.searchParams.get("deviceId")?.trim();
|
|
19
|
-
const sessions = deviceId ? listSessions(deviceId) : listAllSessionsList();
|
|
20
|
-
res.statusCode = 200;
|
|
21
|
-
res.setHeader("Content-Type", "application/json");
|
|
22
|
-
res.end(JSON.stringify({ sessions }));
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { resolveAccountIdFromRequest } from "../../sync/account-identity.js";
|
|
2
|
-
import { getTranscriptStore } from "../../sync/transcript-store.js";
|
|
3
|
-
import { extractBearerToken } from "../middleware/auth.js";
|
|
4
|
-
function sessionKeyFromPath(pathname) {
|
|
5
|
-
const prefix = "/friday-next/sessions/";
|
|
6
|
-
const suffix = "/messages";
|
|
7
|
-
if (!pathname.startsWith(prefix) || !pathname.endsWith(suffix))
|
|
8
|
-
return "";
|
|
9
|
-
return decodeURIComponent(pathname.slice(prefix.length, -suffix.length));
|
|
10
|
-
}
|
|
11
|
-
export async function handleSessionMessagesGet(req, res) {
|
|
12
|
-
if (req.method !== "GET") {
|
|
13
|
-
res.statusCode = 405;
|
|
14
|
-
res.setHeader("Content-Type", "application/json");
|
|
15
|
-
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
16
|
-
return true;
|
|
17
|
-
}
|
|
18
|
-
if (!extractBearerToken(req)) {
|
|
19
|
-
res.statusCode = 401;
|
|
20
|
-
res.setHeader("Content-Type", "application/json");
|
|
21
|
-
res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
const accountId = resolveAccountIdFromRequest(req);
|
|
25
|
-
if (!accountId) {
|
|
26
|
-
res.statusCode = 403;
|
|
27
|
-
res.setHeader("Content-Type", "application/json");
|
|
28
|
-
res.end(JSON.stringify({ error: "Unknown or missing X-Account-Id" }));
|
|
29
|
-
return true;
|
|
30
|
-
}
|
|
31
|
-
const url = new URL(req.url ?? "/", "http://localhost");
|
|
32
|
-
const sessionKey = sessionKeyFromPath(url.pathname);
|
|
33
|
-
if (!sessionKey) {
|
|
34
|
-
res.statusCode = 400;
|
|
35
|
-
res.setHeader("Content-Type", "application/json");
|
|
36
|
-
res.end(JSON.stringify({ error: "Missing session key in path" }));
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
if (!sessionKey.includes(`friday:account:${accountId}:`) && !sessionKey.includes(`friday%3Aaccount%3A${accountId}%3A`)) {
|
|
40
|
-
const legacyAllowed = sessionKey.startsWith("friday:direct:") || sessionKey.startsWith("agent:main:friday:direct:");
|
|
41
|
-
if (!legacyAllowed) {
|
|
42
|
-
res.statusCode = 403;
|
|
43
|
-
res.setHeader("Content-Type", "application/json");
|
|
44
|
-
res.end(JSON.stringify({ error: "Session does not belong to account" }));
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
const since = Number.parseInt(url.searchParams.get("since") ?? "0", 10);
|
|
49
|
-
const limit = Number.parseInt(url.searchParams.get("limit") ?? "200", 10);
|
|
50
|
-
const events = getTranscriptStore().readSince(accountId, sessionKey, Number.isFinite(since) ? since : 0, limit);
|
|
51
|
-
res.statusCode = 200;
|
|
52
|
-
res.setHeader("Content-Type", "application/json");
|
|
53
|
-
res.end(JSON.stringify({ ok: true, events, nextSince: events.at(-1)?.seq ?? since }));
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { resolveAccountIdFromRequest } from "../../sync/account-identity.js";
|
|
2
|
-
import { getTranscriptStore } from "../../sync/transcript-store.js";
|
|
3
|
-
import { extractBearerToken } from "../middleware/auth.js";
|
|
4
|
-
import { readJsonBody } from "../middleware/body.js";
|
|
5
|
-
const allowedKinds = new Set([
|
|
6
|
-
"user_message",
|
|
7
|
-
"assistant_message",
|
|
8
|
-
"thought_event",
|
|
9
|
-
"tool_event",
|
|
10
|
-
"outbound_text",
|
|
11
|
-
"outbound_media",
|
|
12
|
-
"sync_upsert",
|
|
13
|
-
]);
|
|
14
|
-
function sessionKeyFromPath(pathname) {
|
|
15
|
-
const prefix = "/friday-next/sessions/";
|
|
16
|
-
const suffix = "/messages";
|
|
17
|
-
if (!pathname.startsWith(prefix) || !pathname.endsWith(suffix))
|
|
18
|
-
return "";
|
|
19
|
-
return decodeURIComponent(pathname.slice(prefix.length, -suffix.length));
|
|
20
|
-
}
|
|
21
|
-
function parseEvent(raw, accountId) {
|
|
22
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
23
|
-
return null;
|
|
24
|
-
const obj = raw;
|
|
25
|
-
const kind = typeof obj.kind === "string" && allowedKinds.has(obj.kind)
|
|
26
|
-
? obj.kind
|
|
27
|
-
: null;
|
|
28
|
-
if (!kind)
|
|
29
|
-
return null;
|
|
30
|
-
const payload = obj.payload && typeof obj.payload === "object" && !Array.isArray(obj.payload)
|
|
31
|
-
? obj.payload
|
|
32
|
-
: null;
|
|
33
|
-
if (!payload)
|
|
34
|
-
return null;
|
|
35
|
-
return {
|
|
36
|
-
eventId: typeof obj.eventId === "string" ? obj.eventId.trim() : undefined,
|
|
37
|
-
clientId: typeof obj.clientId === "string" && obj.clientId.trim() ? obj.clientId.trim() : accountId,
|
|
38
|
-
kind,
|
|
39
|
-
payload,
|
|
40
|
-
ts: typeof obj.ts === "number" ? obj.ts : undefined,
|
|
41
|
-
lamportTs: typeof obj.lamportTs === "number" ? obj.lamportTs : undefined,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
export async function handleSessionMessagesPost(req, res) {
|
|
45
|
-
if (req.method !== "POST") {
|
|
46
|
-
res.statusCode = 405;
|
|
47
|
-
res.setHeader("Content-Type", "application/json");
|
|
48
|
-
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
if (!extractBearerToken(req)) {
|
|
52
|
-
res.statusCode = 401;
|
|
53
|
-
res.setHeader("Content-Type", "application/json");
|
|
54
|
-
res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
const accountId = resolveAccountIdFromRequest(req);
|
|
58
|
-
if (!accountId) {
|
|
59
|
-
res.statusCode = 403;
|
|
60
|
-
res.setHeader("Content-Type", "application/json");
|
|
61
|
-
res.end(JSON.stringify({ error: "Unknown or missing X-Account-Id" }));
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
const url = new URL(req.url ?? "/", "http://localhost");
|
|
65
|
-
const sessionKey = sessionKeyFromPath(url.pathname);
|
|
66
|
-
if (!sessionKey) {
|
|
67
|
-
res.statusCode = 400;
|
|
68
|
-
res.setHeader("Content-Type", "application/json");
|
|
69
|
-
res.end(JSON.stringify({ error: "Missing session key in path" }));
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
if (!sessionKey.includes(`friday:account:${accountId}:`) && !sessionKey.startsWith("friday:direct:")) {
|
|
73
|
-
res.statusCode = 403;
|
|
74
|
-
res.setHeader("Content-Type", "application/json");
|
|
75
|
-
res.end(JSON.stringify({ error: "Session does not belong to account" }));
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
const body = await readJsonBody(req, 8 * 1024 * 1024);
|
|
79
|
-
const rawEvents = Array.isArray(body?.events) ? body.events : [];
|
|
80
|
-
const events = rawEvents.map((e) => parseEvent(e, accountId)).filter((e) => e !== null);
|
|
81
|
-
if (events.length !== rawEvents.length) {
|
|
82
|
-
res.statusCode = 400;
|
|
83
|
-
res.setHeader("Content-Type", "application/json");
|
|
84
|
-
res.end(JSON.stringify({ error: "Invalid transcript event batch" }));
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
const appended = getTranscriptStore().appendEvents(accountId, sessionKey, events);
|
|
88
|
-
res.statusCode = 200;
|
|
89
|
-
res.setHeader("Content-Type", "application/json");
|
|
90
|
-
res.end(JSON.stringify({ ok: true, appended: appended.length, events: appended }));
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { resolveFridayNextConfig } from "../../config.js";
|
|
2
|
-
import { getHostOpenClawConfigSnapshot } from "../../host-config.js";
|
|
3
|
-
import { getFridayNextRuntime } from "../../runtime.js";
|
|
4
|
-
import { accountExists, requireAccountId } from "../../sync/account-identity.js";
|
|
5
|
-
import { sessionBelongsToAccount } from "../../sync/session-key.js";
|
|
6
|
-
import { readSince, upsertEvents } from "../../sync/transcript-store.js";
|
|
7
|
-
import { extractBearerToken } from "../middleware/auth.js";
|
|
8
|
-
import { readJsonBody } from "../middleware/body.js";
|
|
9
|
-
const VALID_KINDS = new Set([
|
|
10
|
-
"user_message",
|
|
11
|
-
"assistant_message",
|
|
12
|
-
"thought_event",
|
|
13
|
-
"tool_event",
|
|
14
|
-
"outbound_text",
|
|
15
|
-
"outbound_media",
|
|
16
|
-
]);
|
|
17
|
-
function decodeSessionKeyFromPath(pathname) {
|
|
18
|
-
const prefix = "/friday-next/sessions/";
|
|
19
|
-
if (!pathname.startsWith(prefix))
|
|
20
|
-
return null;
|
|
21
|
-
const rest = pathname.slice(prefix.length);
|
|
22
|
-
const messagesIdx = rest.indexOf("/messages");
|
|
23
|
-
if (messagesIdx < 0)
|
|
24
|
-
return null;
|
|
25
|
-
const encoded = rest.slice(0, messagesIdx);
|
|
26
|
-
if (!encoded)
|
|
27
|
-
return null;
|
|
28
|
-
try {
|
|
29
|
-
return decodeURIComponent(encoded);
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
export async function handleSessionsMessages(req, res) {
|
|
36
|
-
const url = new URL(req.url ?? "/", "http://localhost");
|
|
37
|
-
const sessionKey = decodeSessionKeyFromPath(url.pathname);
|
|
38
|
-
if (!sessionKey)
|
|
39
|
-
return false;
|
|
40
|
-
if (!extractBearerToken(req)) {
|
|
41
|
-
res.statusCode = 401;
|
|
42
|
-
res.setHeader("Content-Type", "application/json");
|
|
43
|
-
res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
const accountId = requireAccountId(req);
|
|
47
|
-
if (!accountId) {
|
|
48
|
-
res.statusCode = 400;
|
|
49
|
-
res.setHeader("Content-Type", "application/json");
|
|
50
|
-
res.end(JSON.stringify({ error: "Missing or invalid X-Account-Id header" }));
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
const cfg = resolveFridayNextConfig(getHostOpenClawConfigSnapshot(getFridayNextRuntime().config));
|
|
54
|
-
if (!accountExists(cfg.historyDir, accountId)) {
|
|
55
|
-
res.statusCode = 403;
|
|
56
|
-
res.setHeader("Content-Type", "application/json");
|
|
57
|
-
res.end(JSON.stringify({ error: "Unknown account" }));
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
if (!sessionBelongsToAccount(sessionKey, accountId)) {
|
|
61
|
-
res.statusCode = 403;
|
|
62
|
-
res.setHeader("Content-Type", "application/json");
|
|
63
|
-
res.end(JSON.stringify({ error: "Session does not belong to account" }));
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
if (req.method === "GET") {
|
|
67
|
-
const since = Math.max(0, Number.parseInt(url.searchParams.get("since") ?? "0", 10) || 0);
|
|
68
|
-
const limit = Math.min(cfg.syncMaxBatchSize, Math.max(1, Number.parseInt(url.searchParams.get("limit") ?? String(cfg.syncMaxBatchSize), 10) || cfg.syncMaxBatchSize));
|
|
69
|
-
const direction = url.searchParams.get("direction") === "desc" ? "desc" : "asc";
|
|
70
|
-
const events = readSince({
|
|
71
|
-
accountId,
|
|
72
|
-
sessionKey,
|
|
73
|
-
sinceSeq: since,
|
|
74
|
-
limit,
|
|
75
|
-
direction,
|
|
76
|
-
historyDir: cfg.historyDir,
|
|
77
|
-
});
|
|
78
|
-
res.statusCode = 200;
|
|
79
|
-
res.setHeader("Content-Type", "application/json");
|
|
80
|
-
res.end(JSON.stringify({ ok: true, accountId, sessionKey, events }));
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
if (req.method === "POST") {
|
|
84
|
-
const body = (await readJsonBody(req));
|
|
85
|
-
const rawEvents = Array.isArray(body?.events) ? body.events : [];
|
|
86
|
-
if (rawEvents.length > cfg.syncMaxBatchSize) {
|
|
87
|
-
res.statusCode = 400;
|
|
88
|
-
res.setHeader("Content-Type", "application/json");
|
|
89
|
-
res.end(JSON.stringify({ error: `Too many events (max ${cfg.syncMaxBatchSize})` }));
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
const parsed = rawEvents
|
|
93
|
-
.map((item) => {
|
|
94
|
-
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
95
|
-
return null;
|
|
96
|
-
const o = item;
|
|
97
|
-
const kind = typeof o.kind === "string" ? o.kind : "";
|
|
98
|
-
if (!VALID_KINDS.has(kind))
|
|
99
|
-
return null;
|
|
100
|
-
const clientId = typeof o.clientId === "string" ? o.clientId : accountId;
|
|
101
|
-
const payload = o.payload && typeof o.payload === "object" && !Array.isArray(o.payload)
|
|
102
|
-
? o.payload
|
|
103
|
-
: {};
|
|
104
|
-
return {
|
|
105
|
-
kind: kind,
|
|
106
|
-
clientId,
|
|
107
|
-
payload,
|
|
108
|
-
messageId: typeof o.messageId === "string" ? o.messageId : undefined,
|
|
109
|
-
lamport: typeof o.lamport === "number" ? o.lamport : undefined,
|
|
110
|
-
ts: typeof o.ts === "number" ? o.ts : undefined,
|
|
111
|
-
};
|
|
112
|
-
})
|
|
113
|
-
.filter((x) => x !== null);
|
|
114
|
-
const result = upsertEvents({
|
|
115
|
-
accountId,
|
|
116
|
-
sessionKey,
|
|
117
|
-
events: parsed,
|
|
118
|
-
historyDir: cfg.historyDir,
|
|
119
|
-
});
|
|
120
|
-
res.statusCode = 200;
|
|
121
|
-
res.setHeader("Content-Type", "application/json");
|
|
122
|
-
res.end(JSON.stringify({
|
|
123
|
-
ok: true,
|
|
124
|
-
accountId,
|
|
125
|
-
sessionKey,
|
|
126
|
-
applied: result.applied,
|
|
127
|
-
events: result.events,
|
|
128
|
-
}));
|
|
129
|
-
return true;
|
|
130
|
-
}
|
|
131
|
-
res.statusCode = 405;
|
|
132
|
-
res.setHeader("Content-Type", "application/json");
|
|
133
|
-
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
134
|
-
return true;
|
|
135
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* POST /friday-next/sessions/{sessionKey}/sync
|
|
3
|
-
*
|
|
4
|
-
* Bidirectional incremental sync: pull remote messages (primary) + push local messages (offline catch-up).
|
|
5
|
-
*/
|
|
6
|
-
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
7
|
-
export declare function handleSync(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
|