@zhin.js/adapter-sandbox 3.0.3 → 3.0.5

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.
@@ -1,101 +0,0 @@
1
- /**
2
- * 按 session 隔离的 Sandbox SSE 推送(Edge / Vercel 等无 WebSocket 入站时使用)。
3
- * SSE `data` 字段为与 WebSocket 相同的 JSON 字符串。
4
- */
5
- const MAX_REPLAY = 100;
6
- const sessions = new Map();
7
- function getSession(sessionId) {
8
- let s = sessions.get(sessionId);
9
- if (!s) {
10
- s = { subscribers: new Map(), history: [], nextSubId: 0, nextEventId: 0 };
11
- sessions.set(sessionId, s);
12
- }
13
- return s;
14
- }
15
- function formatSse(data, id) {
16
- const lines = [];
17
- if (id)
18
- lines.push(`id: ${id}`);
19
- lines.push(`data: ${data}`);
20
- lines.push("");
21
- return lines.join("\n") + "\n";
22
- }
23
- export function broadcastSandboxSse(sessionId, jsonPayload) {
24
- const session = getSession(sessionId);
25
- const stored = {
26
- id: String(++session.nextEventId),
27
- data: jsonPayload,
28
- };
29
- session.history.push(stored);
30
- if (session.history.length > MAX_REPLAY)
31
- session.history.shift();
32
- const chunk = formatSse(stored.data, stored.id);
33
- for (const sub of session.subscribers.values()) {
34
- sub.enqueue(chunk);
35
- }
36
- }
37
- export function subscribeSandboxSse(sessionId, lastEventId) {
38
- const session = getSession(sessionId);
39
- const encoder = new TextEncoder();
40
- const replayFrom = lastEventId ? Number.parseInt(lastEventId, 10) : 0;
41
- const subId = `sse-${++session.nextSubId}`;
42
- return new ReadableStream({
43
- start(controller) {
44
- for (const ev of session.history) {
45
- if (Number(ev.id) > replayFrom) {
46
- controller.enqueue(encoder.encode(formatSse(ev.data, ev.id)));
47
- }
48
- }
49
- const interval = setInterval(() => {
50
- try {
51
- controller.enqueue(encoder.encode(": heartbeat\n\n"));
52
- }
53
- catch {
54
- clearInterval(interval);
55
- }
56
- }, 15000);
57
- session.subscribers.set(subId, {
58
- id: subId,
59
- enqueue: (chunk) => {
60
- try {
61
- controller.enqueue(encoder.encode(chunk));
62
- }
63
- catch {
64
- /* closed */
65
- }
66
- },
67
- close: () => {
68
- clearInterval(interval);
69
- try {
70
- controller.close();
71
- }
72
- catch {
73
- /* */
74
- }
75
- },
76
- });
77
- },
78
- cancel() {
79
- const s = sessions.get(sessionId);
80
- const sub = s?.subscribers.get(subId);
81
- sub?.close();
82
- s?.subscribers.delete(subId);
83
- },
84
- });
85
- }
86
- export function closeSandboxSseSession(sessionId) {
87
- const session = sessions.get(sessionId);
88
- if (!session)
89
- return;
90
- for (const sub of session.subscribers.values())
91
- sub.close();
92
- session.subscribers.clear();
93
- sessions.delete(sessionId);
94
- }
95
- /** @internal */
96
- export function resetSandboxSseHubForTests() {
97
- for (const id of [...sessions.keys()])
98
- closeSandboxSseSession(id);
99
- sessions.clear();
100
- }
101
- //# sourceMappingURL=sandbox-sse-hub.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sandbox-sse-hub.js","sourceRoot":"","sources":["../src/sandbox-sse-hub.ts"],"names":[],"mappings":"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"}
package/src/fetch-sse.ts DELETED
@@ -1,87 +0,0 @@
1
- import { registerFetchRoute, type RouteTable } from "@zhin.js/http-host/edge";
2
- import type { RouterContext } from "@zhin.js/http-host/edge";
3
- import type { SandboxWsHostAdapter } from "./sandbox-ws.js";
4
- import { subscribeSandboxSse } from "./sandbox-sse-hub.js";
5
-
6
- export type RegisterSandboxSseOptions = {
7
- eventsPath?: string;
8
- messagePath?: string;
9
- };
10
-
11
- function resolveSessionId(ctx: RouterContext): string | undefined {
12
- const q = ctx.query.session?.trim();
13
- if (q) return q;
14
- const h = ctx.get("x-sandbox-session")?.trim();
15
- if (h) return h;
16
- const body = ctx.request.body as { session?: string } | undefined;
17
- if (body && typeof body.session === "string" && body.session.trim()) {
18
- return body.session.trim();
19
- }
20
- return undefined;
21
- }
22
-
23
- /**
24
- * Edge / Vercel 等:Sandbox 使用 HTTP POST 上行 + SSE 下行(无 WebSocket upgrade)。
25
- */
26
- export function registerSandboxSseRoutes(
27
- table: RouteTable,
28
- getAdapter: () => SandboxWsHostAdapter,
29
- options: RegisterSandboxSseOptions = {},
30
- ): void {
31
- const eventsPath = options.eventsPath ?? "/sandbox/events";
32
- const messagePath = options.messagePath ?? "/sandbox/message";
33
-
34
- registerFetchRoute(table, "GET", eventsPath, (ctx: RouterContext) => {
35
- const sessionId = resolveSessionId(ctx);
36
- if (!sessionId) {
37
- ctx.status = 400;
38
- ctx.body = { success: false, error: "Missing session (query ?session= or X-Sandbox-Session)" };
39
- return;
40
- }
41
- const adapter = getAdapter();
42
- if (!adapter.hasSseSession(sessionId)) {
43
- adapter.acceptSseSession(sessionId);
44
- }
45
- const lastEventId = ctx.query["last-event-id"] ?? ctx.query.lastEventId;
46
- const stream = subscribeSandboxSse(sessionId, lastEventId);
47
- ctx.status = 200;
48
- ctx.set("Content-Type", "text/event-stream; charset=utf-8");
49
- ctx.set("Cache-Control", "no-cache, no-transform");
50
- ctx.set("Connection", "keep-alive");
51
- ctx.set("X-Accel-Buffering", "no");
52
- ctx.body = stream;
53
- });
54
-
55
- registerFetchRoute(table, "POST", messagePath, async (ctx: RouterContext) => {
56
- const sessionId = resolveSessionId(ctx);
57
- if (!sessionId) {
58
- ctx.status = 400;
59
- ctx.body = { success: false, error: "Missing session (X-Sandbox-Session or body.session)" };
60
- return;
61
- }
62
- const adapter = getAdapter();
63
- if (!adapter.hasSseSession(sessionId)) {
64
- ctx.status = 404;
65
- ctx.body = { success: false, error: "No sandbox session; open GET /sandbox/events first" };
66
- return;
67
- }
68
- const body = ctx.request.body;
69
- const raw = typeof body === "string"
70
- ? body
71
- : body === undefined || body === null
72
- ? ""
73
- : JSON.stringify(body);
74
- if (!raw) {
75
- ctx.status = 400;
76
- ctx.body = { success: false, error: "Empty message body" };
77
- return;
78
- }
79
- try {
80
- adapter.ingestSseClientMessage(sessionId, raw);
81
- ctx.body = { success: true };
82
- } catch (err) {
83
- ctx.status = 500;
84
- ctx.body = { success: false, error: (err as Error).message };
85
- }
86
- });
87
- }
@@ -1,118 +0,0 @@
1
- /**
2
- * 按 session 隔离的 Sandbox SSE 推送(Edge / Vercel 等无 WebSocket 入站时使用)。
3
- * SSE `data` 字段为与 WebSocket 相同的 JSON 字符串。
4
- */
5
-
6
- type Subscriber = {
7
- id: string;
8
- enqueue: (chunk: string) => void;
9
- close: () => void;
10
- };
11
-
12
- type StoredEvent = { id: string; data: string };
13
-
14
- type SessionState = {
15
- subscribers: Map<string, Subscriber>;
16
- history: StoredEvent[];
17
- nextSubId: number;
18
- nextEventId: number;
19
- };
20
-
21
- const MAX_REPLAY = 100;
22
- const sessions = new Map<string, SessionState>();
23
-
24
- function getSession(sessionId: string): SessionState {
25
- let s = sessions.get(sessionId);
26
- if (!s) {
27
- s = { subscribers: new Map(), history: [], nextSubId: 0, nextEventId: 0 };
28
- sessions.set(sessionId, s);
29
- }
30
- return s;
31
- }
32
-
33
- function formatSse(data: string, id?: string): string {
34
- const lines: string[] = [];
35
- if (id) lines.push(`id: ${id}`);
36
- lines.push(`data: ${data}`);
37
- lines.push("");
38
- return lines.join("\n") + "\n";
39
- }
40
-
41
- export function broadcastSandboxSse(sessionId: string, jsonPayload: string): void {
42
- const session = getSession(sessionId);
43
- const stored: StoredEvent = {
44
- id: String(++session.nextEventId),
45
- data: jsonPayload,
46
- };
47
- session.history.push(stored);
48
- if (session.history.length > MAX_REPLAY) session.history.shift();
49
- const chunk = formatSse(stored.data, stored.id);
50
- for (const sub of session.subscribers.values()) {
51
- sub.enqueue(chunk);
52
- }
53
- }
54
-
55
- export function subscribeSandboxSse(
56
- sessionId: string,
57
- lastEventId?: string,
58
- ): ReadableStream<Uint8Array> {
59
- const session = getSession(sessionId);
60
- const encoder = new TextEncoder();
61
- const replayFrom = lastEventId ? Number.parseInt(lastEventId, 10) : 0;
62
- const subId = `sse-${++session.nextSubId}`;
63
-
64
- return new ReadableStream<Uint8Array>({
65
- start(controller) {
66
- for (const ev of session.history) {
67
- if (Number(ev.id) > replayFrom) {
68
- controller.enqueue(encoder.encode(formatSse(ev.data, ev.id)));
69
- }
70
- }
71
- const interval = setInterval(() => {
72
- try {
73
- controller.enqueue(encoder.encode(": heartbeat\n\n"));
74
- } catch {
75
- clearInterval(interval);
76
- }
77
- }, 15000);
78
- session.subscribers.set(subId, {
79
- id: subId,
80
- enqueue: (chunk) => {
81
- try {
82
- controller.enqueue(encoder.encode(chunk));
83
- } catch {
84
- /* closed */
85
- }
86
- },
87
- close: () => {
88
- clearInterval(interval);
89
- try {
90
- controller.close();
91
- } catch {
92
- /* */
93
- }
94
- },
95
- });
96
- },
97
- cancel() {
98
- const s = sessions.get(sessionId);
99
- const sub = s?.subscribers.get(subId);
100
- sub?.close();
101
- s?.subscribers.delete(subId);
102
- },
103
- });
104
- }
105
-
106
- export function closeSandboxSseSession(sessionId: string): void {
107
- const session = sessions.get(sessionId);
108
- if (!session) return;
109
- for (const sub of session.subscribers.values()) sub.close();
110
- session.subscribers.clear();
111
- sessions.delete(sessionId);
112
- }
113
-
114
- /** @internal */
115
- export function resetSandboxSseHubForTests(): void {
116
- for (const id of [...sessions.keys()]) closeSandboxSseSession(id);
117
- sessions.clear();
118
- }