pi-harness-runtime 1.1.376 → 1.1.377
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/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/dist/client.d.ts +56 -35
- package/packages/relay/dist/client.js +278 -71
- package/packages/relay/dist/index.d.ts +12 -5
- package/packages/relay/dist/index.js +12 -4
- package/packages/relay/dist/server.d.ts +58 -20
- package/packages/relay/dist/server.js +344 -49
- package/packages/relay/dist/types.d.ts +137 -1
- package/packages/relay/dist/types.js +21 -2
- package/packages/relay/package.json +1 -1
- package/packages/relay/src/client.ts +375 -81
- package/packages/relay/src/index.ts +42 -5
- package/packages/relay/src/server.ts +452 -54
- package/packages/relay/src/types.ts +174 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
- package/scripts/relay-client.ts +105 -96
- package/scripts/unified-webhook-server.ts +145 -143
- package/scripts/ws-server.ts +81 -0
package/scripts/relay-client.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
* Real-time Callback Relay Client
|
|
3
|
+
* Real-time Callback Relay Client - Two-way streaming
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Uses the relay package's typed streaming protocol:
|
|
6
|
+
* - Snapshot + Live event pattern (t3code style)
|
|
7
|
+
* - Connection state machine with automatic reconnection
|
|
8
|
+
* - Typed message handling
|
|
7
9
|
*
|
|
8
10
|
* Usage:
|
|
9
11
|
* RELAY_WS_URL=wss://webhook.bunchee.online:8444 \
|
|
@@ -11,35 +13,20 @@
|
|
|
11
13
|
* bun run scripts/relay-client.ts
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
|
-
import { WebSocket } from "ws";
|
|
15
16
|
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
16
17
|
import { join } from "path";
|
|
17
18
|
import { homedir } from "os";
|
|
19
|
+
import { RelayClient } from "../packages/relay/src/client";
|
|
20
|
+
import type { CallbackPayload, SnapshotMessage } from "../packages/relay/src/types";
|
|
18
21
|
|
|
19
22
|
// Configuration
|
|
20
23
|
const RELAY_WS_URL = process.env.RELAY_WS_URL || "wss://webhook.bunchee.online:8444";
|
|
21
24
|
const RELAY_AUTH_TOKEN = process.env.RELAY_AUTH_TOKEN || "change-me-in-production";
|
|
22
|
-
const CALLBACK_QUEUE_FILE = join(homedir(), ".pi-harness-runtime", "callback-queue.jsonl");
|
|
23
25
|
const JOB_PIPE_FILE = join(homedir(), ".pi-harness-runtime", "job-commands.jsonl");
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface CallbackPayload {
|
|
31
|
-
type: "telegram_callback" | "line_callback";
|
|
32
|
-
action: string;
|
|
33
|
-
targetId: string;
|
|
34
|
-
userId: number;
|
|
35
|
-
username?: string;
|
|
36
|
-
messageId?: number;
|
|
37
|
-
timestamp: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
let ws: WebSocket | null = null;
|
|
41
|
-
let reconnectDelay = 1000;
|
|
42
|
-
let authenticated = false;
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Telegram Integration
|
|
29
|
+
// ============================================================================
|
|
43
30
|
|
|
44
31
|
function loadKeys() {
|
|
45
32
|
const keysDir = join(homedir(), ".pi-harness-runtime", "keys");
|
|
@@ -52,7 +39,7 @@ function loadKeys() {
|
|
|
52
39
|
function writeToJobPipe(command: { command: string; jobId: string; userId: number; timestamp: string }): void {
|
|
53
40
|
const line = JSON.stringify(command) + "\n";
|
|
54
41
|
writeFileSync(JOB_PIPE_FILE, line, { flag: "a" });
|
|
55
|
-
console.log(`[
|
|
42
|
+
console.log(`[Client] Wrote to job pipe: ${command.command} ${command.jobId}`);
|
|
56
43
|
}
|
|
57
44
|
|
|
58
45
|
async function sendTelegramMessage(chatId: number, text: string): Promise<void> {
|
|
@@ -64,8 +51,12 @@ async function sendTelegramMessage(chatId: number, text: string): Promise<void>
|
|
|
64
51
|
});
|
|
65
52
|
}
|
|
66
53
|
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// Callback Processing
|
|
56
|
+
// ============================================================================
|
|
57
|
+
|
|
67
58
|
async function processCallback(callback: CallbackPayload): Promise<void> {
|
|
68
|
-
console.log(`[
|
|
59
|
+
console.log(`[Client] Processing: ${callback.type} - ${callback.action} ${callback.targetId}`);
|
|
69
60
|
|
|
70
61
|
switch (callback.action) {
|
|
71
62
|
case "approve":
|
|
@@ -75,7 +66,7 @@ async function processCallback(callback: CallbackPayload): Promise<void> {
|
|
|
75
66
|
userId: callback.userId,
|
|
76
67
|
timestamp: new Date().toISOString(),
|
|
77
68
|
});
|
|
78
|
-
await sendTelegramMessage(callback.userId,
|
|
69
|
+
await sendTelegramMessage(callback.userId, `Approved! Job ${callback.targetId} will resume.`);
|
|
79
70
|
break;
|
|
80
71
|
|
|
81
72
|
case "reject":
|
|
@@ -85,91 +76,109 @@ async function processCallback(callback: CallbackPayload): Promise<void> {
|
|
|
85
76
|
userId: callback.userId,
|
|
86
77
|
timestamp: new Date().toISOString(),
|
|
87
78
|
});
|
|
88
|
-
await sendTelegramMessage(callback.userId,
|
|
79
|
+
await sendTelegramMessage(callback.userId, `Rejected. Job ${callback.targetId} cancelled.`);
|
|
89
80
|
break;
|
|
90
81
|
|
|
91
82
|
case "sayhi":
|
|
92
83
|
await sendTelegramMessage(
|
|
93
84
|
callback.userId,
|
|
94
|
-
"
|
|
85
|
+
"Hi! Two-way streaming communication working!"
|
|
95
86
|
);
|
|
96
87
|
break;
|
|
97
88
|
|
|
98
89
|
default:
|
|
99
|
-
console.log(`[
|
|
90
|
+
console.log(`[Client] Unknown action: ${callback.action}`);
|
|
100
91
|
}
|
|
101
92
|
}
|
|
102
93
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// Main
|
|
96
|
+
// ============================================================================
|
|
97
|
+
|
|
98
|
+
async function main() {
|
|
99
|
+
console.log("===========================================");
|
|
100
|
+
console.log("Real-time Callback Relay Client - Streaming v2");
|
|
101
|
+
console.log("===========================================");
|
|
102
|
+
console.log(`Server: ${RELAY_WS_URL}`);
|
|
103
|
+
console.log("===========================================");
|
|
104
|
+
|
|
105
|
+
// Create client with typed callbacks
|
|
106
|
+
const client = new RelayClient(
|
|
107
|
+
{
|
|
108
|
+
serverUrl: RELAY_WS_URL,
|
|
109
|
+
token: RELAY_AUTH_TOKEN,
|
|
110
|
+
keepAlive: 30000,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
onConnected: (sessionId) => {
|
|
114
|
+
console.log(`[Client] Connected! Session: ${sessionId}`);
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
onDisconnected: () => {
|
|
118
|
+
console.log("[Client] Disconnected");
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
onSnapshot: (snapshot: SnapshotMessage["payload"]) => {
|
|
122
|
+
console.log(`[Client] Snapshot received:`);
|
|
123
|
+
console.log(` - Session: ${snapshot.sessionId}`);
|
|
124
|
+
console.log(` - Version: ${snapshot.serverVersion}`);
|
|
125
|
+
console.log(` - Connected clients: ${snapshot.connectedClients}`);
|
|
126
|
+
console.log(` - Recent callbacks: ${snapshot.recentCallbacks.length}`);
|
|
127
|
+
|
|
128
|
+
// Process any recent callbacks from snapshot
|
|
129
|
+
for (const callback of snapshot.recentCallbacks) {
|
|
130
|
+
processCallback(callback).catch(console.error);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
onSynchronized: () => {
|
|
135
|
+
console.log("[Client] Synchronized - receiving live events");
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
onCallback: (callback: CallbackPayload) => {
|
|
139
|
+
console.log(`[Client] Live callback: ${callback.action} ${callback.targetId}`);
|
|
140
|
+
processCallback(callback).catch(console.error);
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
onStatus: (status) => {
|
|
144
|
+
console.log(`[Client] Status [${status.kind}]: ${status.code} - ${status.message}`);
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
onError: (error) => {
|
|
148
|
+
console.error("[Client] Error:", error.message);
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
onStateChange: (change) => {
|
|
152
|
+
const prev = change.previous.state;
|
|
153
|
+
const curr = change.current.state;
|
|
154
|
+
if (prev !== curr) {
|
|
155
|
+
console.log(`[Client] State: ${prev} → ${curr}`);
|
|
156
|
+
}
|
|
157
|
+
},
|
|
147
158
|
}
|
|
148
|
-
|
|
159
|
+
);
|
|
149
160
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
console.log(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
161
|
+
// Handle graceful shutdown
|
|
162
|
+
const shutdown = () => {
|
|
163
|
+
console.log("\nShutting down...");
|
|
164
|
+
client.disconnect();
|
|
165
|
+
process.exit(0);
|
|
166
|
+
};
|
|
156
167
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
});
|
|
160
|
-
}
|
|
168
|
+
process.on("SIGINT", shutdown);
|
|
169
|
+
process.on("SIGTERM", shutdown);
|
|
161
170
|
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
171
|
+
// Connect
|
|
172
|
+
try {
|
|
173
|
+
await client.connect();
|
|
174
|
+
console.log("[Client] Connection established!");
|
|
175
|
+
|
|
176
|
+
// Keep running
|
|
177
|
+
await new Promise(() => {});
|
|
178
|
+
} catch (err) {
|
|
179
|
+
console.error("[Client] Failed to connect:", err);
|
|
180
|
+
process.exit(1);
|
|
166
181
|
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
console.log("===========================================");
|
|
170
|
-
console.log("Real-time Callback Relay Client");
|
|
171
|
-
console.log("===========================================");
|
|
172
|
-
console.log(`Server: ${RELAY_WS_URL}`);
|
|
173
|
-
console.log("===========================================");
|
|
182
|
+
}
|
|
174
183
|
|
|
175
|
-
|
|
184
|
+
main();
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
3
|
* Unified Webhook Server — Handles Telegram, LINE, and WebSocket relay
|
|
4
|
+
*
|
|
5
|
+
* Two-way streaming implementation using relay package:
|
|
6
|
+
* - Snapshot + Live event pattern (t3code style)
|
|
7
|
+
* - Typed message protocol
|
|
8
|
+
* - Connection state machine
|
|
9
|
+
* - Stream coalescing for high-frequency events
|
|
4
10
|
*
|
|
5
11
|
* Routes:
|
|
6
12
|
* /telegram → Telegram webhook handler
|
|
@@ -11,12 +17,14 @@
|
|
|
11
17
|
* bun run scripts/unified-webhook-server.ts
|
|
12
18
|
*/
|
|
13
19
|
|
|
14
|
-
import { existsSync, readFileSync
|
|
20
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
15
21
|
import { join } from "node:path";
|
|
16
22
|
import { homedir } from "os";
|
|
17
23
|
import { createServer, IncomingMessage, ServerResponse } from "node:http";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
24
|
+
import { createRelayServer } from "../packages/relay/src/server";
|
|
25
|
+
import { generateMessageId } from "../packages/relay/src/types";
|
|
26
|
+
import type { CallbackPayload } from "../packages/relay/src/types";
|
|
27
|
+
import type { RelayServerOptions } from "../packages/relay/src/server";
|
|
20
28
|
|
|
21
29
|
// --- Configuration ---
|
|
22
30
|
interface Config {
|
|
@@ -33,7 +41,8 @@ interface Config {
|
|
|
33
41
|
|
|
34
42
|
const PORT = parseInt(process.env.PORT || "8443", 10);
|
|
35
43
|
const WS_PORT = parseInt(process.env.WS_PORT || "8444", 10);
|
|
36
|
-
const
|
|
44
|
+
const AUTH_TOKEN = process.env.RELAY_AUTH_TOKEN || "change-me-in-production";
|
|
45
|
+
const SERVER_VERSION = process.env.SERVER_VERSION || "1.0.0";
|
|
37
46
|
|
|
38
47
|
// --- Keys Loader ---
|
|
39
48
|
function loadConfig(): Config {
|
|
@@ -76,17 +85,6 @@ function loadConfig(): Config {
|
|
|
76
85
|
return config;
|
|
77
86
|
}
|
|
78
87
|
|
|
79
|
-
// --- Telegram HMAC Verification (for future use with secret hash) ---
|
|
80
|
-
async function _verifyTelegramHmac(data: string, secret: string, hash: string): Promise<boolean> {
|
|
81
|
-
const encoder = new TextEncoder();
|
|
82
|
-
const key = await crypto.subtle.importKey(
|
|
83
|
-
"raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]
|
|
84
|
-
);
|
|
85
|
-
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(data));
|
|
86
|
-
const expected = Array.from(new Uint8Array(signature)).map(b => b.toString(16).padStart(2, "0")).join("");
|
|
87
|
-
return expected === hash;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
88
|
// --- LINE Verification ---
|
|
91
89
|
async function verifyLINE(body: string, channelSecret: string, xLineSignature: string): Promise<boolean> {
|
|
92
90
|
const encoder = new TextEncoder();
|
|
@@ -98,58 +96,49 @@ async function verifyLINE(body: string, channelSecret: string, xLineSignature: s
|
|
|
98
96
|
return expected === xLineSignature;
|
|
99
97
|
}
|
|
100
98
|
|
|
101
|
-
// --- Callback
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
count++;
|
|
117
|
-
}
|
|
99
|
+
// --- Parse Callback Data ---
|
|
100
|
+
function parseCallbackData(data: string): { action: string; targetId: string } {
|
|
101
|
+
const colonIdx = data.indexOf(":");
|
|
102
|
+
const underscoreIdx = data.indexOf("_");
|
|
103
|
+
|
|
104
|
+
if (colonIdx !== -1) {
|
|
105
|
+
return {
|
|
106
|
+
action: data.slice(0, colonIdx),
|
|
107
|
+
targetId: data.slice(colonIdx + 1).replace(/_+$/, ""),
|
|
108
|
+
};
|
|
109
|
+
} else if (underscoreIdx !== -1) {
|
|
110
|
+
return {
|
|
111
|
+
action: data.slice(0, underscoreIdx),
|
|
112
|
+
targetId: data.slice(underscoreIdx + 1).replace(/_+$/, ""),
|
|
113
|
+
};
|
|
118
114
|
}
|
|
119
|
-
return count;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function startWebSocketServer() {
|
|
123
|
-
const wss = new WebSocketServer({ port: WS_PORT });
|
|
124
115
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
wsClients.set(clientId, ws);
|
|
128
|
-
console.log(`[WS] Client connected: ${clientId}`);
|
|
129
|
-
|
|
130
|
-
ws.on("message", (data) => {
|
|
131
|
-
try {
|
|
132
|
-
const msg = JSON.parse(data.toString());
|
|
133
|
-
if (msg.type === "auth" && msg.payload === RELAY_AUTH_TOKEN) {
|
|
134
|
-
ws.send(JSON.stringify({ type: "auth", payload: { success: true } }));
|
|
135
|
-
console.log(`[WS] Client authenticated: ${clientId}`);
|
|
136
|
-
}
|
|
137
|
-
} catch (err) {
|
|
138
|
-
console.error(`[WS] Invalid message from ${clientId}:`, err);
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
ws.on("close", () => {
|
|
143
|
-
wsClients.delete(clientId);
|
|
144
|
-
console.log(`[WS] Client disconnected: ${clientId}`);
|
|
145
|
-
});
|
|
146
|
-
});
|
|
116
|
+
return { action: data, targetId: "" };
|
|
117
|
+
}
|
|
147
118
|
|
|
148
|
-
|
|
119
|
+
// --- Create Callback Payload ---
|
|
120
|
+
function createCallbackPayload(
|
|
121
|
+
type: "telegram_callback" | "line_callback",
|
|
122
|
+
action: string,
|
|
123
|
+
targetId: string,
|
|
124
|
+
userId: number | string,
|
|
125
|
+
username?: string,
|
|
126
|
+
messageId?: number
|
|
127
|
+
): CallbackPayload {
|
|
128
|
+
return {
|
|
129
|
+
id: generateMessageId(),
|
|
130
|
+
type,
|
|
131
|
+
action,
|
|
132
|
+
targetId,
|
|
133
|
+
userId: typeof userId === "string" ? parseInt(userId, 10) || 0 : userId,
|
|
134
|
+
username,
|
|
135
|
+
messageId,
|
|
136
|
+
timestamp: new Date().toISOString(),
|
|
137
|
+
};
|
|
149
138
|
}
|
|
150
139
|
|
|
151
140
|
// --- Telegram Handler ---
|
|
152
|
-
async function handleTelegram(req: IncomingMessage, res: ServerResponse, config: Config) {
|
|
141
|
+
async function handleTelegram(req: IncomingMessage, res: ServerResponse, config: Config, relayServer: ReturnType<typeof createRelayServer>) {
|
|
153
142
|
if (!config.telegram) {
|
|
154
143
|
res.writeHead(404);
|
|
155
144
|
res.end("Telegram not configured");
|
|
@@ -185,37 +174,21 @@ async function handleTelegram(req: IncomingMessage, res: ServerResponse, config:
|
|
|
185
174
|
}
|
|
186
175
|
|
|
187
176
|
// Parse callback data
|
|
188
|
-
const
|
|
189
|
-
const colonIdx = data.indexOf(":");
|
|
190
|
-
const underscoreIdx = data.indexOf("_");
|
|
191
|
-
let action: string, targetId: string;
|
|
192
|
-
if (colonIdx !== -1) {
|
|
193
|
-
action = data.slice(0, colonIdx);
|
|
194
|
-
targetId = data.slice(colonIdx + 1).replace(/_+$/, "");
|
|
195
|
-
} else if (underscoreIdx !== -1) {
|
|
196
|
-
action = data.slice(0, underscoreIdx);
|
|
197
|
-
targetId = data.slice(underscoreIdx + 1).replace(/_+$/, "");
|
|
198
|
-
} else {
|
|
199
|
-
action = data;
|
|
200
|
-
targetId = "";
|
|
201
|
-
}
|
|
177
|
+
const { action, targetId } = parseCallbackData(query.data || "");
|
|
202
178
|
|
|
203
|
-
|
|
204
|
-
|
|
179
|
+
// Create typed callback payload
|
|
180
|
+
const callback = createCallbackPayload(
|
|
181
|
+
"telegram_callback",
|
|
205
182
|
action,
|
|
206
183
|
targetId,
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
// Broadcast via
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
// Fallback to file queue
|
|
216
|
-
queueCallback(callback);
|
|
217
|
-
}
|
|
218
|
-
console.log(`[Telegram] Callback: ${action} ${targetId} from @${from.username || from.id} (WS clients: ${wsCount})`);
|
|
184
|
+
from.id,
|
|
185
|
+
from.username,
|
|
186
|
+
query.message?.message_id
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
// Broadcast via typed streaming relay
|
|
190
|
+
relayServer.broadcastCallback(callback);
|
|
191
|
+
console.log(`[Telegram] Callback: ${action} ${targetId} from @${from.username || from.id}`);
|
|
219
192
|
|
|
220
193
|
// Acknowledge immediately
|
|
221
194
|
await fetch(`https://api.telegram.org/bot${config.telegram.botToken}/answerCallbackQuery`, {
|
|
@@ -235,7 +208,7 @@ async function handleTelegram(req: IncomingMessage, res: ServerResponse, config:
|
|
|
235
208
|
}
|
|
236
209
|
|
|
237
210
|
// --- LINE Handler ---
|
|
238
|
-
async function handleLINE(req: IncomingMessage, res: ServerResponse, config: Config) {
|
|
211
|
+
async function handleLINE(req: IncomingMessage, res: ServerResponse, config: Config, relayServer: ReturnType<typeof createRelayServer>) {
|
|
239
212
|
if (!config.line) {
|
|
240
213
|
res.writeHead(404);
|
|
241
214
|
res.end("LINE not configured");
|
|
@@ -269,24 +242,19 @@ async function handleLINE(req: IncomingMessage, res: ServerResponse, config: Con
|
|
|
269
242
|
for (const event of events) {
|
|
270
243
|
if (event.type === "postback") {
|
|
271
244
|
const data = event.postback?.data || "";
|
|
272
|
-
const
|
|
273
|
-
let action = data.slice(0, colonIdx !== -1 ? colonIdx : undefined);
|
|
274
|
-
let targetId = colonIdx !== -1 ? data.slice(colonIdx + 1) : "";
|
|
245
|
+
const { action, targetId } = parseCallbackData(data);
|
|
275
246
|
|
|
276
|
-
|
|
277
|
-
|
|
247
|
+
// Create typed callback payload
|
|
248
|
+
const callback = createCallbackPayload(
|
|
249
|
+
"line_callback",
|
|
278
250
|
action,
|
|
279
251
|
targetId,
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
if (wsCount === 0) {
|
|
287
|
-
queueCallback(callback);
|
|
288
|
-
}
|
|
289
|
-
console.log(`[LINE] Postback: ${action} ${targetId} (WS clients: ${wsCount})`);
|
|
252
|
+
event.source?.userId || "unknown"
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
// Broadcast via typed streaming relay
|
|
256
|
+
relayServer.broadcastCallback(callback);
|
|
257
|
+
console.log(`[LINE] Postback: ${action} ${targetId}`);
|
|
290
258
|
}
|
|
291
259
|
}
|
|
292
260
|
|
|
@@ -300,45 +268,79 @@ async function handleLINE(req: IncomingMessage, res: ServerResponse, config: Con
|
|
|
300
268
|
}
|
|
301
269
|
|
|
302
270
|
// --- Main ---
|
|
303
|
-
|
|
271
|
+
async function main() {
|
|
272
|
+
const config = loadConfig();
|
|
273
|
+
|
|
274
|
+
console.log("===========================================");
|
|
275
|
+
console.log("Unified Webhook Server - Streaming v2");
|
|
276
|
+
console.log("===========================================");
|
|
277
|
+
if (config.telegram) console.log(" /telegram - Telegram webhook");
|
|
278
|
+
if (config.line) console.log(" /line - LINE webhook");
|
|
279
|
+
console.log(` HTTP: http://0.0.0.0:${PORT}`);
|
|
280
|
+
console.log(` WebSocket: ws://0.0.0.0:${WS_PORT}`);
|
|
281
|
+
console.log("===========================================");
|
|
282
|
+
|
|
283
|
+
// Create relay server with typed streaming
|
|
284
|
+
const relayOptions: RelayServerOptions = {
|
|
285
|
+
port: WS_PORT,
|
|
286
|
+
host: "0.0.0.0",
|
|
287
|
+
authToken: AUTH_TOKEN,
|
|
288
|
+
serverVersion: SERVER_VERSION,
|
|
289
|
+
onStateChange: (change: { previous: { state: string }; current: { state: string }; timestamp: string }) => {
|
|
290
|
+
console.log(`[Relay] State: ${change.previous.state} → ${change.current.state}`);
|
|
291
|
+
},
|
|
292
|
+
};
|
|
304
293
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
294
|
+
const relayServer = createRelayServer(relayOptions);
|
|
295
|
+
|
|
296
|
+
// Start WebSocket relay
|
|
297
|
+
await relayServer.start();
|
|
298
|
+
console.log("[Relay] WebSocket server started on port", WS_PORT);
|
|
299
|
+
|
|
300
|
+
// HTTP webhook server
|
|
301
|
+
const server = createServer(async (req, res) => {
|
|
302
|
+
const url = new URL(req.url || "/", `http://localhost:${PORT}`);
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
if (url.pathname === "/telegram" && req.method === "POST") {
|
|
306
|
+
await handleTelegram(req, res, config, relayServer);
|
|
307
|
+
} else if (url.pathname === "/line" && req.method === "POST") {
|
|
308
|
+
await handleLINE(req, res, config, relayServer);
|
|
309
|
+
} else if (url.pathname === "/health") {
|
|
310
|
+
res.writeHead(200);
|
|
311
|
+
res.end(JSON.stringify({
|
|
312
|
+
status: "ok",
|
|
313
|
+
clients: relayServer.getClientCount(),
|
|
314
|
+
running: relayServer.isRunning(),
|
|
315
|
+
}));
|
|
316
|
+
} else {
|
|
317
|
+
res.writeHead(404);
|
|
318
|
+
res.end("Not Found");
|
|
319
|
+
}
|
|
320
|
+
} catch (err) {
|
|
321
|
+
console.error("[Server] Error:", err);
|
|
322
|
+
res.writeHead(500);
|
|
323
|
+
res.end("Error");
|
|
324
|
+
}
|
|
325
|
+
});
|
|
311
326
|
|
|
312
|
-
|
|
313
|
-
|
|
327
|
+
server.listen(PORT, () => {
|
|
328
|
+
console.log(`HTTP server running on http://0.0.0.0:${PORT}`);
|
|
329
|
+
console.log("Configure webhooks:");
|
|
330
|
+
if (config.telegram) console.log(` Telegram: https://webhook.bunchee.online/telegram`);
|
|
331
|
+
if (config.line) console.log(` LINE: https://webhook.bunchee.online/line`);
|
|
332
|
+
});
|
|
314
333
|
|
|
315
|
-
//
|
|
316
|
-
const
|
|
317
|
-
|
|
334
|
+
// Graceful shutdown
|
|
335
|
+
const shutdown = async () => {
|
|
336
|
+
console.log("\nShutting down...");
|
|
337
|
+
await relayServer.stop();
|
|
338
|
+
server.close();
|
|
339
|
+
process.exit(0);
|
|
340
|
+
};
|
|
318
341
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
} else if (url.pathname === "/health") {
|
|
325
|
-
res.writeHead(200);
|
|
326
|
-
res.end("OK");
|
|
327
|
-
} else {
|
|
328
|
-
res.writeHead(404);
|
|
329
|
-
res.end("Not Found");
|
|
330
|
-
}
|
|
331
|
-
} catch (err) {
|
|
332
|
-
console.error("[Server] Error:", err);
|
|
333
|
-
res.writeHead(500);
|
|
334
|
-
res.end("Error");
|
|
335
|
-
}
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
server.listen(PORT, () => {
|
|
339
|
-
console.log(`Server running on http://0.0.0.0:${PORT}`);
|
|
340
|
-
console.log(`WebSocket relay on ws://0.0.0.0:${WS_PORT}`);
|
|
341
|
-
console.log("Configure webhooks:");
|
|
342
|
-
if (config.telegram) console.log(` Telegram: https://webhook.bunchee.online/telegram`);
|
|
343
|
-
if (config.line) console.log(` LINE: https://webhook.bunchee.online/line`);
|
|
344
|
-
});
|
|
342
|
+
process.on("SIGINT", shutdown);
|
|
343
|
+
process.on("SIGTERM", shutdown);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
main().catch(console.error);
|