@teneo-protocol/sdk 1.0.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/.dockerignore +14 -0
- package/.env.test.example +14 -0
- package/.eslintrc.json +26 -0
- package/.github/workflows/claude-code-review.yml +78 -0
- package/.github/workflows/claude-reviewer.yml +64 -0
- package/.github/workflows/publish-npm.yml +38 -0
- package/.github/workflows/push-to-main.yml +23 -0
- package/.node-version +1 -0
- package/.prettierrc +11 -0
- package/Dockerfile +25 -0
- package/LICENCE +661 -0
- package/README.md +709 -0
- package/dist/constants.d.ts +42 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +45 -0
- package/dist/constants.js.map +1 -0
- package/dist/core/websocket-client.d.ts +261 -0
- package/dist/core/websocket-client.d.ts.map +1 -0
- package/dist/core/websocket-client.js +875 -0
- package/dist/core/websocket-client.js.map +1 -0
- package/dist/formatters/response-formatter.d.ts +354 -0
- package/dist/formatters/response-formatter.d.ts.map +1 -0
- package/dist/formatters/response-formatter.js +575 -0
- package/dist/formatters/response-formatter.js.map +1 -0
- package/dist/handlers/message-handler-registry.d.ts +155 -0
- package/dist/handlers/message-handler-registry.d.ts.map +1 -0
- package/dist/handlers/message-handler-registry.js +216 -0
- package/dist/handlers/message-handler-registry.js.map +1 -0
- package/dist/handlers/message-handlers/agent-selected-handler.d.ts +112 -0
- package/dist/handlers/message-handlers/agent-selected-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/agent-selected-handler.js +40 -0
- package/dist/handlers/message-handlers/agent-selected-handler.js.map +1 -0
- package/dist/handlers/message-handlers/agents-list-handler.d.ts +14 -0
- package/dist/handlers/message-handlers/agents-list-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/agents-list-handler.js +25 -0
- package/dist/handlers/message-handlers/agents-list-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-error-handler.d.ts +71 -0
- package/dist/handlers/message-handlers/auth-error-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-error-handler.js +30 -0
- package/dist/handlers/message-handlers/auth-error-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-message-handler.d.ts +18 -0
- package/dist/handlers/message-handlers/auth-message-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-message-handler.js +60 -0
- package/dist/handlers/message-handlers/auth-message-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-required-handler.d.ts +76 -0
- package/dist/handlers/message-handlers/auth-required-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-required-handler.js +23 -0
- package/dist/handlers/message-handlers/auth-required-handler.js.map +1 -0
- package/dist/handlers/message-handlers/auth-success-handler.d.ts +18 -0
- package/dist/handlers/message-handlers/auth-success-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/auth-success-handler.js +51 -0
- package/dist/handlers/message-handlers/auth-success-handler.js.map +1 -0
- package/dist/handlers/message-handlers/base-handler.d.ts +55 -0
- package/dist/handlers/message-handlers/base-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/base-handler.js +83 -0
- package/dist/handlers/message-handlers/base-handler.js.map +1 -0
- package/dist/handlers/message-handlers/challenge-handler.d.ts +73 -0
- package/dist/handlers/message-handlers/challenge-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/challenge-handler.js +47 -0
- package/dist/handlers/message-handlers/challenge-handler.js.map +1 -0
- package/dist/handlers/message-handlers/error-message-handler.d.ts +76 -0
- package/dist/handlers/message-handlers/error-message-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/error-message-handler.js +29 -0
- package/dist/handlers/message-handlers/error-message-handler.js.map +1 -0
- package/dist/handlers/message-handlers/index.d.ts +28 -0
- package/dist/handlers/message-handlers/index.d.ts.map +1 -0
- package/dist/handlers/message-handlers/index.js +100 -0
- package/dist/handlers/message-handlers/index.js.map +1 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts +122 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.js +30 -0
- package/dist/handlers/message-handlers/list-rooms-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/ping-pong-handler.d.ts +104 -0
- package/dist/handlers/message-handlers/ping-pong-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/ping-pong-handler.js +36 -0
- package/dist/handlers/message-handlers/ping-pong-handler.js.map +1 -0
- package/dist/handlers/message-handlers/regular-message-handler.d.ts +56 -0
- package/dist/handlers/message-handlers/regular-message-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/regular-message-handler.js +59 -0
- package/dist/handlers/message-handlers/regular-message-handler.js.map +1 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.d.ts +81 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.js +48 -0
- package/dist/handlers/message-handlers/subscribe-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/task-response-handler.d.ts +14 -0
- package/dist/handlers/message-handlers/task-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/task-response-handler.js +44 -0
- package/dist/handlers/message-handlers/task-response-handler.js.map +1 -0
- package/dist/handlers/message-handlers/types.d.ts +51 -0
- package/dist/handlers/message-handlers/types.d.ts.map +1 -0
- package/dist/handlers/message-handlers/types.js +7 -0
- package/dist/handlers/message-handlers/types.js.map +1 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts +81 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.d.ts.map +1 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.js +48 -0
- package/dist/handlers/message-handlers/unsubscribe-response-handler.js.map +1 -0
- package/dist/handlers/webhook-handler.d.ts +202 -0
- package/dist/handlers/webhook-handler.d.ts.map +1 -0
- package/dist/handlers/webhook-handler.js +511 -0
- package/dist/handlers/webhook-handler.js.map +1 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +217 -0
- package/dist/index.js.map +1 -0
- package/dist/managers/agent-registry.d.ts +173 -0
- package/dist/managers/agent-registry.d.ts.map +1 -0
- package/dist/managers/agent-registry.js +310 -0
- package/dist/managers/agent-registry.js.map +1 -0
- package/dist/managers/connection-manager.d.ts +134 -0
- package/dist/managers/connection-manager.d.ts.map +1 -0
- package/dist/managers/connection-manager.js +176 -0
- package/dist/managers/connection-manager.js.map +1 -0
- package/dist/managers/index.d.ts +9 -0
- package/dist/managers/index.d.ts.map +1 -0
- package/dist/managers/index.js +16 -0
- package/dist/managers/index.js.map +1 -0
- package/dist/managers/message-router.d.ts +112 -0
- package/dist/managers/message-router.d.ts.map +1 -0
- package/dist/managers/message-router.js +260 -0
- package/dist/managers/message-router.js.map +1 -0
- package/dist/managers/room-manager.d.ts +165 -0
- package/dist/managers/room-manager.d.ts.map +1 -0
- package/dist/managers/room-manager.js +227 -0
- package/dist/managers/room-manager.js.map +1 -0
- package/dist/teneo-sdk.d.ts +703 -0
- package/dist/teneo-sdk.d.ts.map +1 -0
- package/dist/teneo-sdk.js +907 -0
- package/dist/teneo-sdk.js.map +1 -0
- package/dist/types/config.d.ts +1047 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +720 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/error-codes.d.ts +29 -0
- package/dist/types/error-codes.d.ts.map +1 -0
- package/dist/types/error-codes.js +41 -0
- package/dist/types/error-codes.js.map +1 -0
- package/dist/types/events.d.ts +616 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +261 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/health.d.ts +40 -0
- package/dist/types/health.d.ts.map +1 -0
- package/dist/types/health.js +6 -0
- package/dist/types/health.js.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +123 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/messages.d.ts +3734 -0
- package/dist/types/messages.d.ts.map +1 -0
- package/dist/types/messages.js +482 -0
- package/dist/types/messages.js.map +1 -0
- package/dist/types/validation.d.ts +81 -0
- package/dist/types/validation.d.ts.map +1 -0
- package/dist/types/validation.js +115 -0
- package/dist/types/validation.js.map +1 -0
- package/dist/utils/bounded-queue.d.ts +127 -0
- package/dist/utils/bounded-queue.d.ts.map +1 -0
- package/dist/utils/bounded-queue.js +181 -0
- package/dist/utils/bounded-queue.js.map +1 -0
- package/dist/utils/circuit-breaker.d.ts +141 -0
- package/dist/utils/circuit-breaker.d.ts.map +1 -0
- package/dist/utils/circuit-breaker.js +215 -0
- package/dist/utils/circuit-breaker.js.map +1 -0
- package/dist/utils/deduplication-cache.d.ts +110 -0
- package/dist/utils/deduplication-cache.d.ts.map +1 -0
- package/dist/utils/deduplication-cache.js +177 -0
- package/dist/utils/deduplication-cache.js.map +1 -0
- package/dist/utils/event-waiter.d.ts +101 -0
- package/dist/utils/event-waiter.d.ts.map +1 -0
- package/dist/utils/event-waiter.js +118 -0
- package/dist/utils/event-waiter.js.map +1 -0
- package/dist/utils/index.d.ts +51 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +72 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +22 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +91 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +122 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +190 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/dist/utils/retry-policy.d.ts +191 -0
- package/dist/utils/retry-policy.d.ts.map +1 -0
- package/dist/utils/retry-policy.js +225 -0
- package/dist/utils/retry-policy.js.map +1 -0
- package/dist/utils/secure-private-key.d.ts +113 -0
- package/dist/utils/secure-private-key.d.ts.map +1 -0
- package/dist/utils/secure-private-key.js +188 -0
- package/dist/utils/secure-private-key.js.map +1 -0
- package/dist/utils/signature-verifier.d.ts +143 -0
- package/dist/utils/signature-verifier.d.ts.map +1 -0
- package/dist/utils/signature-verifier.js +238 -0
- package/dist/utils/signature-verifier.js.map +1 -0
- package/dist/utils/ssrf-validator.d.ts +36 -0
- package/dist/utils/ssrf-validator.d.ts.map +1 -0
- package/dist/utils/ssrf-validator.js +195 -0
- package/dist/utils/ssrf-validator.js.map +1 -0
- package/examples/.env.example +17 -0
- package/examples/basic-usage.ts +211 -0
- package/examples/production-dashboard/.env.example +153 -0
- package/examples/production-dashboard/package.json +39 -0
- package/examples/production-dashboard/public/dashboard.html +642 -0
- package/examples/production-dashboard/server.ts +753 -0
- package/examples/webhook-integration.ts +239 -0
- package/examples/x-influencer-battle-redesign.html +1065 -0
- package/examples/x-influencer-battle-server.ts +217 -0
- package/examples/x-influencer-battle.html +787 -0
- package/package.json +65 -0
- package/src/constants.ts +43 -0
- package/src/core/websocket-client.test.ts +512 -0
- package/src/core/websocket-client.ts +1056 -0
- package/src/formatters/response-formatter.test.ts +571 -0
- package/src/formatters/response-formatter.ts +677 -0
- package/src/handlers/message-handler-registry.ts +239 -0
- package/src/handlers/message-handlers/agent-selected-handler.ts +40 -0
- package/src/handlers/message-handlers/agents-list-handler.ts +26 -0
- package/src/handlers/message-handlers/auth-error-handler.ts +31 -0
- package/src/handlers/message-handlers/auth-message-handler.ts +66 -0
- package/src/handlers/message-handlers/auth-required-handler.ts +23 -0
- package/src/handlers/message-handlers/auth-success-handler.ts +57 -0
- package/src/handlers/message-handlers/base-handler.ts +101 -0
- package/src/handlers/message-handlers/challenge-handler.ts +57 -0
- package/src/handlers/message-handlers/error-message-handler.ts +27 -0
- package/src/handlers/message-handlers/index.ts +77 -0
- package/src/handlers/message-handlers/list-rooms-response-handler.ts +28 -0
- package/src/handlers/message-handlers/ping-pong-handler.ts +30 -0
- package/src/handlers/message-handlers/regular-message-handler.ts +65 -0
- package/src/handlers/message-handlers/subscribe-response-handler.ts +47 -0
- package/src/handlers/message-handlers/task-response-handler.ts +45 -0
- package/src/handlers/message-handlers/types.ts +77 -0
- package/src/handlers/message-handlers/unsubscribe-response-handler.ts +47 -0
- package/src/handlers/webhook-handler.test.ts +789 -0
- package/src/handlers/webhook-handler.ts +576 -0
- package/src/index.ts +269 -0
- package/src/managers/agent-registry.test.ts +466 -0
- package/src/managers/agent-registry.ts +347 -0
- package/src/managers/connection-manager.ts +195 -0
- package/src/managers/index.ts +9 -0
- package/src/managers/message-router.ts +349 -0
- package/src/managers/room-manager.ts +248 -0
- package/src/teneo-sdk.ts +1022 -0
- package/src/types/config.test.ts +325 -0
- package/src/types/config.ts +799 -0
- package/src/types/error-codes.ts +44 -0
- package/src/types/events.test.ts +302 -0
- package/src/types/events.ts +382 -0
- package/src/types/health.ts +46 -0
- package/src/types/index.ts +199 -0
- package/src/types/messages.test.ts +660 -0
- package/src/types/messages.ts +570 -0
- package/src/types/validation.ts +123 -0
- package/src/utils/bounded-queue.test.ts +356 -0
- package/src/utils/bounded-queue.ts +205 -0
- package/src/utils/circuit-breaker.test.ts +394 -0
- package/src/utils/circuit-breaker.ts +262 -0
- package/src/utils/deduplication-cache.test.ts +380 -0
- package/src/utils/deduplication-cache.ts +198 -0
- package/src/utils/event-waiter.test.ts +381 -0
- package/src/utils/event-waiter.ts +172 -0
- package/src/utils/index.ts +74 -0
- package/src/utils/logger.ts +87 -0
- package/src/utils/rate-limiter.test.ts +341 -0
- package/src/utils/rate-limiter.ts +211 -0
- package/src/utils/retry-policy.test.ts +558 -0
- package/src/utils/retry-policy.ts +272 -0
- package/src/utils/secure-private-key.test.ts +356 -0
- package/src/utils/secure-private-key.ts +205 -0
- package/src/utils/signature-verifier.test.ts +464 -0
- package/src/utils/signature-verifier.ts +298 -0
- package/src/utils/ssrf-validator.test.ts +372 -0
- package/src/utils/ssrf-validator.ts +224 -0
- package/tests/integration/real-server.test.ts +740 -0
- package/tests/integration/websocket.test.ts +381 -0
- package/tests/integration-setup.ts +16 -0
- package/tests/setup.ts +34 -0
- package/tsconfig.json +32 -0
- package/vitest.config.ts +42 -0
- package/vitest.integration.config.ts +23 -0
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Production Dashboard Example - Hono + Bun
|
|
3
|
+
*
|
|
4
|
+
* A comprehensive example demonstrating ALL Teneo Protocol SDK features:
|
|
5
|
+
* - WebSocket connection with auto-reconnection
|
|
6
|
+
* - Ethereum wallet authentication
|
|
7
|
+
* - Private key encryption in memory (SEC-3)
|
|
8
|
+
* - Message signature verification (SEC-2)
|
|
9
|
+
* - Message deduplication cache (CB-4)
|
|
10
|
+
* - Indexed agent lookups (PERF-3)
|
|
11
|
+
* - Configurable retry strategies (REL-3)
|
|
12
|
+
* - Webhook integration with circuit breaker
|
|
13
|
+
* - Rate limiting
|
|
14
|
+
* - Health monitoring
|
|
15
|
+
* - Real-time event streaming
|
|
16
|
+
* - Complete error handling
|
|
17
|
+
*
|
|
18
|
+
* Run with: bun run server.ts
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { serve } from "@hono/node-server";
|
|
22
|
+
import * as fs from "fs";
|
|
23
|
+
import { Hono } from "hono";
|
|
24
|
+
import * as path from "path";
|
|
25
|
+
import type { AgentResponse } from "../../dist/index.js";
|
|
26
|
+
import { SDKConfigBuilder, TeneoSDK, SecurePrivateKey } from "../../dist/index.js";
|
|
27
|
+
|
|
28
|
+
// Load environment variables
|
|
29
|
+
const PORT = parseInt(process.env.PORT || "3000");
|
|
30
|
+
const WS_URL =
|
|
31
|
+
process.env.WS_URL;
|
|
32
|
+
const PRIVATE_KEY = process.env.PRIVATE_KEY || "";
|
|
33
|
+
const WALLET_ADDRESS = process.env.WALLET_ADDRESS || "";
|
|
34
|
+
const DEFAULT_ROOM = process.env.DEFAULT_ROOM || "as1LfBarJNzOIpOQJQ7PH";
|
|
35
|
+
const ENABLE_SIG_VERIFICATION = process.env.ENABLE_SIGNATURE_VERIFICATION === "true";
|
|
36
|
+
const TRUSTED_ADDRESSES = process.env.TRUSTED_ADDRESSES?.split(",").filter(Boolean) || [];
|
|
37
|
+
|
|
38
|
+
// Create Hono app
|
|
39
|
+
const app = new Hono();
|
|
40
|
+
|
|
41
|
+
// In-memory storage for demo
|
|
42
|
+
interface StoredEvent {
|
|
43
|
+
type: string;
|
|
44
|
+
timestamp: string;
|
|
45
|
+
data: any;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface StoredMessage {
|
|
49
|
+
id: string;
|
|
50
|
+
timestamp: string;
|
|
51
|
+
content: string;
|
|
52
|
+
from: string;
|
|
53
|
+
response?: AgentResponse;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const recentEvents: StoredEvent[] = [];
|
|
57
|
+
const recentMessages: StoredMessage[] = [];
|
|
58
|
+
const recentWebhooks: any[] = [];
|
|
59
|
+
let messageCounter = 0;
|
|
60
|
+
let errorCounter = 0;
|
|
61
|
+
let sdk: TeneoSDK | null = null;
|
|
62
|
+
const sseClients: Set<ReadableStreamDefaultController> = new Set();
|
|
63
|
+
|
|
64
|
+
// Initialize SDK with all features
|
|
65
|
+
async function initializeSDK() {
|
|
66
|
+
console.log("[SERVER] Initializing Teneo SDK with all features...");
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
// SEC-3: Use encrypted private key in memory
|
|
70
|
+
// This protects the private key from memory dumps and inspection
|
|
71
|
+
const secureKey = new SecurePrivateKey(PRIVATE_KEY);
|
|
72
|
+
console.log("[SDK] Private key encrypted in memory (SEC-3)");
|
|
73
|
+
|
|
74
|
+
const config = new SDKConfigBuilder()
|
|
75
|
+
.withWebSocketUrl(WS_URL)
|
|
76
|
+
.withAuthentication(secureKey, WALLET_ADDRESS) // Pass SecurePrivateKey instead of plain string
|
|
77
|
+
.withAutoJoinRooms([DEFAULT_ROOM])
|
|
78
|
+
.withReconnection({ enabled: true, delay: 5000, maxAttempts: 10 })
|
|
79
|
+
// REL-3: Configure custom retry strategies for production resilience
|
|
80
|
+
.withReconnectionStrategy({
|
|
81
|
+
type: "exponential",
|
|
82
|
+
baseDelay: 3000, // Start with 3s instead of default 5s
|
|
83
|
+
maxDelay: 120000, // Max 2 minutes instead of default 1 minute
|
|
84
|
+
maxAttempts: 15, // More attempts for production reliability
|
|
85
|
+
jitter: true, // Prevent thundering herd
|
|
86
|
+
backoffMultiplier: 2.5 // Faster backoff than default 2
|
|
87
|
+
})
|
|
88
|
+
.withWebhookRetryStrategy({
|
|
89
|
+
type: "exponential",
|
|
90
|
+
baseDelay: 1000,
|
|
91
|
+
maxDelay: 30000,
|
|
92
|
+
maxAttempts: 5, // More attempts for webhook reliability
|
|
93
|
+
jitter: false, // Predictable delays for webhooks
|
|
94
|
+
backoffMultiplier: 2
|
|
95
|
+
})
|
|
96
|
+
.withResponseFormat({ format: "both", includeMetadata: true })
|
|
97
|
+
.withLogging("debug")
|
|
98
|
+
.withCache(true, 300000, 100)
|
|
99
|
+
// CB-4: Message deduplication to prevent duplicate processing
|
|
100
|
+
.withMessageDeduplication(
|
|
101
|
+
true, // Enabled by default
|
|
102
|
+
120000, // 2 minute TTL (increased from default 60s for production)
|
|
103
|
+
50000 // 50k message cache (increased from default 10k for high volume)
|
|
104
|
+
)
|
|
105
|
+
.withSignatureVerification({
|
|
106
|
+
enabled: ENABLE_SIG_VERIFICATION,
|
|
107
|
+
trustedAddresses: TRUSTED_ADDRESSES,
|
|
108
|
+
requireFor: ["task_response", "agent_selected"],
|
|
109
|
+
strictMode: false
|
|
110
|
+
})
|
|
111
|
+
.build();
|
|
112
|
+
|
|
113
|
+
// Allow localhost webhooks for development
|
|
114
|
+
config.allowInsecureWebhooks = true;
|
|
115
|
+
|
|
116
|
+
sdk = new TeneoSDK(config);
|
|
117
|
+
|
|
118
|
+
// Configure webhook to point to our server
|
|
119
|
+
sdk.configureWebhook(`http://localhost:${PORT}/webhook`, {
|
|
120
|
+
"X-API-Key": "production-dashboard-secret",
|
|
121
|
+
"Content-Type": "application/json"
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Set up comprehensive event listeners
|
|
125
|
+
setupSDKEventListeners(sdk);
|
|
126
|
+
|
|
127
|
+
// Connect to Teneo network
|
|
128
|
+
console.log("[SDK] Connecting to Teneo network...");
|
|
129
|
+
await sdk.connect();
|
|
130
|
+
console.log("[SDK] Successfully connected and authenticated!");
|
|
131
|
+
|
|
132
|
+
return sdk;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error("[SDK] Failed to initialize:", error);
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Setup all SDK event listeners
|
|
140
|
+
function setupSDKEventListeners(sdk: TeneoSDK) {
|
|
141
|
+
// Connection events
|
|
142
|
+
sdk.on("connection:open", () => {
|
|
143
|
+
addEvent("connection:open", { message: "Connected to WebSocket" });
|
|
144
|
+
broadcastSSE({ type: "connection", status: "connected" });
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
sdk.on("connection:close", (code, reason) => {
|
|
148
|
+
addEvent("connection:close", { code, reason });
|
|
149
|
+
broadcastSSE({ type: "connection", status: "disconnected", code, reason });
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
sdk.on("connection:reconnecting", (attempt) => {
|
|
153
|
+
addEvent("connection:reconnecting", { attempt });
|
|
154
|
+
broadcastSSE({ type: "connection", status: "reconnecting", attempt });
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
sdk.on("connection:reconnected", () => {
|
|
158
|
+
addEvent("connection:reconnected", { message: "Reconnected successfully" });
|
|
159
|
+
broadcastSSE({ type: "connection", status: "reconnected" });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Authentication events
|
|
163
|
+
sdk.on("auth:challenge", (challenge) => {
|
|
164
|
+
addEvent("auth:challenge", { challenge: challenge.substring(0, 20) + "..." });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
sdk.on("auth:success", (state) => {
|
|
168
|
+
addEvent("auth:success", {
|
|
169
|
+
walletAddress: state.walletAddress,
|
|
170
|
+
rooms: state.rooms?.length || 0,
|
|
171
|
+
isWhitelisted: state.isWhitelisted
|
|
172
|
+
});
|
|
173
|
+
broadcastSSE({ type: "auth", status: "success", state });
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
sdk.on("auth:error", (error) => {
|
|
177
|
+
addEvent("auth:error", { error });
|
|
178
|
+
errorCounter++;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Signature verification events
|
|
182
|
+
sdk.on("signature:verified", (messageType, address) => {
|
|
183
|
+
addEvent("signature:verified", { messageType, address });
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
sdk.on("signature:failed", (messageType, reason, address) => {
|
|
187
|
+
addEvent("signature:failed", { messageType, reason, address });
|
|
188
|
+
errorCounter++;
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Deduplication events (CB-4)
|
|
192
|
+
sdk.on("message:duplicate", (message) => {
|
|
193
|
+
addEvent("message:duplicate", {
|
|
194
|
+
messageType: message.type,
|
|
195
|
+
messageId: message.id,
|
|
196
|
+
from: message.from
|
|
197
|
+
});
|
|
198
|
+
broadcastSSE({ type: "message:duplicate", message: { type: message.type, id: message.id } });
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Agent events
|
|
202
|
+
sdk.on("agent:selected", (data) => {
|
|
203
|
+
addEvent("agent:selected", {
|
|
204
|
+
agentName: data.agentName,
|
|
205
|
+
reasoning: data.reasoning,
|
|
206
|
+
command: data.command
|
|
207
|
+
});
|
|
208
|
+
broadcastSSE({ type: "agent:selected", data });
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
sdk.on("agent:response", (response) => {
|
|
212
|
+
addEvent("agent:response", {
|
|
213
|
+
agentName: response.agentName,
|
|
214
|
+
success: response.success,
|
|
215
|
+
contentLength: response.content?.length || 0
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Update stored message with response
|
|
219
|
+
const msg = recentMessages.find((m) => !m.response);
|
|
220
|
+
if (msg) {
|
|
221
|
+
msg.response = response;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
broadcastSSE({ type: "agent:response", response });
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
sdk.on("agent:list", (agents) => {
|
|
228
|
+
addEvent("agent:list", { count: agents.length });
|
|
229
|
+
broadcastSSE({ type: "agent:list", agents });
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Room events
|
|
233
|
+
sdk.on("room:subscribed", (data) => {
|
|
234
|
+
addEvent("room:subscribed", { roomId: data.roomId, subscriptions: data.subscriptions });
|
|
235
|
+
broadcastSSE({ type: "room:subscribed", data });
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
sdk.on("room:unsubscribed", (data) => {
|
|
239
|
+
addEvent("room:unsubscribed", { roomId: data.roomId, subscriptions: data.subscriptions });
|
|
240
|
+
broadcastSSE({ type: "room:unsubscribed", data });
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
sdk.on("room:list", (rooms) => {
|
|
244
|
+
addEvent("room:list", { count: rooms.length });
|
|
245
|
+
broadcastSSE({ type: "room:list", rooms });
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Webhook events
|
|
249
|
+
sdk.on("webhook:sent", (payload, url) => {
|
|
250
|
+
addEvent("webhook:sent", { event: payload.event, url });
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
sdk.on("webhook:success", (response, url) => {
|
|
254
|
+
addEvent("webhook:success", { url, status: response.status });
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
sdk.on("webhook:error", (error, url) => {
|
|
258
|
+
addEvent("webhook:error", { error: error.message, url });
|
|
259
|
+
errorCounter++;
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
sdk.on("webhook:retry", (attempt, url) => {
|
|
263
|
+
addEvent("webhook:retry", { attempt, url });
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Error events
|
|
267
|
+
sdk.on("error", (error) => {
|
|
268
|
+
addEvent("error", {
|
|
269
|
+
name: error.name,
|
|
270
|
+
message: error.message,
|
|
271
|
+
code: error.code,
|
|
272
|
+
recoverable: error.recoverable
|
|
273
|
+
});
|
|
274
|
+
errorCounter++;
|
|
275
|
+
broadcastSSE({ type: "error", error: { message: error.message, code: error.code } });
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
sdk.on("warning", (warning) => {
|
|
279
|
+
addEvent("warning", { warning });
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Lifecycle events
|
|
283
|
+
sdk.on("ready", () => {
|
|
284
|
+
addEvent("ready", { message: "SDK ready" });
|
|
285
|
+
broadcastSSE({ type: "ready" });
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Helper functions
|
|
290
|
+
function addEvent(type: string, data: any) {
|
|
291
|
+
const event: StoredEvent = {
|
|
292
|
+
type,
|
|
293
|
+
timestamp: new Date().toISOString(),
|
|
294
|
+
data
|
|
295
|
+
};
|
|
296
|
+
recentEvents.unshift(event);
|
|
297
|
+
if (recentEvents.length > 100) recentEvents.pop();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function broadcastSSE(data: any) {
|
|
301
|
+
const message = `data: ${JSON.stringify(data)}\n\n`;
|
|
302
|
+
sseClients.forEach((controller) => {
|
|
303
|
+
try {
|
|
304
|
+
controller.enqueue(new TextEncoder().encode(message));
|
|
305
|
+
} catch (error) {
|
|
306
|
+
sseClients.delete(controller);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ===== API ROUTES =====
|
|
312
|
+
|
|
313
|
+
// Serve dashboard
|
|
314
|
+
app.get("/", (c) => {
|
|
315
|
+
const htmlPath = path.join(__dirname, "public", "dashboard.html");
|
|
316
|
+
const html = fs.readFileSync(htmlPath, "utf-8");
|
|
317
|
+
return c.html(html);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// Health check endpoint
|
|
321
|
+
app.get("/health", async (c) => {
|
|
322
|
+
if (!sdk) {
|
|
323
|
+
return c.json({ status: "unhealthy", error: "SDK not initialized" }, 503);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const health = sdk.getHealth();
|
|
327
|
+
return c.json(health);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Metrics endpoint
|
|
331
|
+
app.get("/metrics", (c) => {
|
|
332
|
+
if (!sdk) {
|
|
333
|
+
return c.json({ error: "SDK not initialized" }, 503);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const connectionState = sdk.getConnectionState();
|
|
337
|
+
const authState = sdk.getAuthState();
|
|
338
|
+
const webhookStatus = sdk.getWebhookStatus();
|
|
339
|
+
const agents = sdk.getAgents();
|
|
340
|
+
const rooms = sdk.getRooms();
|
|
341
|
+
|
|
342
|
+
return c.json({
|
|
343
|
+
connection: {
|
|
344
|
+
connected: sdk.isConnected,
|
|
345
|
+
authenticated: sdk.isAuthenticated,
|
|
346
|
+
reconnectAttempts: connectionState.reconnectAttempts
|
|
347
|
+
},
|
|
348
|
+
auth: {
|
|
349
|
+
walletAddress: authState.walletAddress,
|
|
350
|
+
rooms: authState.rooms?.length || 0
|
|
351
|
+
},
|
|
352
|
+
agents: {
|
|
353
|
+
total: agents.length,
|
|
354
|
+
online: agents.filter((a) => a.status === "online").length
|
|
355
|
+
},
|
|
356
|
+
rooms: {
|
|
357
|
+
total: rooms.length,
|
|
358
|
+
subscribedRooms: sdk.getSubscribedRooms()
|
|
359
|
+
},
|
|
360
|
+
webhooks: {
|
|
361
|
+
configured: webhookStatus.configured,
|
|
362
|
+
pending: webhookStatus.queue.pending,
|
|
363
|
+
failed: webhookStatus.queue.failed,
|
|
364
|
+
circuitState: webhookStatus.queue.circuitState
|
|
365
|
+
},
|
|
366
|
+
messages: {
|
|
367
|
+
sent: messageCounter,
|
|
368
|
+
recent: recentMessages.length
|
|
369
|
+
},
|
|
370
|
+
errors: {
|
|
371
|
+
total: errorCounter
|
|
372
|
+
},
|
|
373
|
+
uptime: process.uptime()
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// CB-4: Deduplication status endpoint
|
|
378
|
+
app.get("/api/deduplication", (c) => {
|
|
379
|
+
if (!sdk) {
|
|
380
|
+
return c.json({ error: "SDK not initialized" }, 503);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const status = sdk.getDeduplicationStatus();
|
|
384
|
+
|
|
385
|
+
if (!status) {
|
|
386
|
+
return c.json({
|
|
387
|
+
enabled: false,
|
|
388
|
+
message: "Message deduplication is not enabled"
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return c.json({
|
|
393
|
+
enabled: true,
|
|
394
|
+
...status,
|
|
395
|
+
utilization: ((status.cacheSize / status.maxSize) * 100).toFixed(2) + "%"
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// Webhook receiver endpoint
|
|
400
|
+
app.post("/webhook", async (c) => {
|
|
401
|
+
const payload = await c.req.json();
|
|
402
|
+
|
|
403
|
+
console.log("[WEBHOOK] Received:", payload.event);
|
|
404
|
+
|
|
405
|
+
// Store webhook for display
|
|
406
|
+
recentWebhooks.unshift({
|
|
407
|
+
...payload,
|
|
408
|
+
receivedAt: new Date().toISOString()
|
|
409
|
+
});
|
|
410
|
+
if (recentWebhooks.length > 50) recentWebhooks.pop();
|
|
411
|
+
|
|
412
|
+
// Update message with full task response if available
|
|
413
|
+
if (payload.event === "task_response" && payload.data) {
|
|
414
|
+
const msg = recentMessages.find((m) => !m.response || m.response.content?.includes("Started"));
|
|
415
|
+
if (msg) {
|
|
416
|
+
msg.response = {
|
|
417
|
+
taskId: payload.data.taskId,
|
|
418
|
+
agentId: payload.data.agentId,
|
|
419
|
+
agentName: payload.data.agentName || "Agent",
|
|
420
|
+
content: payload.data.content,
|
|
421
|
+
contentType: payload.data.contentType,
|
|
422
|
+
success: payload.data.success !== false,
|
|
423
|
+
timestamp: payload.data.timestamp || new Date().toISOString(),
|
|
424
|
+
humanized: payload.data.humanized || payload.data.content
|
|
425
|
+
};
|
|
426
|
+
broadcastSSE({ type: "message:updated", message: msg });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Broadcast to dashboard
|
|
431
|
+
broadcastSSE({ type: "webhook:received", payload });
|
|
432
|
+
|
|
433
|
+
return c.json({ status: "success", received: true });
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// Send message
|
|
437
|
+
app.post("/api/message", async (c) => {
|
|
438
|
+
if (!sdk || !sdk.isConnected) {
|
|
439
|
+
return c.json({ error: "SDK not connected" }, 503);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
try {
|
|
443
|
+
const { content, waitForResponse = false } = await c.req.json();
|
|
444
|
+
|
|
445
|
+
if (!content || typeof content !== "string") {
|
|
446
|
+
return c.json({ error: "Content is required" }, 400);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const messageId = `msg_${Date.now()}`;
|
|
450
|
+
const storedMessage: StoredMessage = {
|
|
451
|
+
id: messageId,
|
|
452
|
+
timestamp: new Date().toISOString(),
|
|
453
|
+
content,
|
|
454
|
+
from: "dashboard"
|
|
455
|
+
};
|
|
456
|
+
recentMessages.unshift(storedMessage);
|
|
457
|
+
if (recentMessages.length > 100) recentMessages.pop();
|
|
458
|
+
messageCounter++;
|
|
459
|
+
|
|
460
|
+
const response = await sdk.sendMessage(content, {
|
|
461
|
+
room: DEFAULT_ROOM,
|
|
462
|
+
waitForResponse,
|
|
463
|
+
timeout: 60000
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
if (response) {
|
|
467
|
+
storedMessage.response = response as AgentResponse;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return c.json({
|
|
471
|
+
success: true,
|
|
472
|
+
messageId,
|
|
473
|
+
response: response || null
|
|
474
|
+
});
|
|
475
|
+
} catch (error: any) {
|
|
476
|
+
return c.json({ error: error.message }, 500);
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// Send direct command
|
|
481
|
+
app.post("/api/direct-command", async (c) => {
|
|
482
|
+
if (!sdk || !sdk.isConnected) {
|
|
483
|
+
return c.json({ error: "SDK not connected" }, 503);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
try {
|
|
487
|
+
const { agent, command, room } = await c.req.json();
|
|
488
|
+
|
|
489
|
+
if (!agent || !command) {
|
|
490
|
+
return c.json({ error: "Agent and command are required" }, 400);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
await sdk.sendDirectCommand({ agent, command, room });
|
|
494
|
+
|
|
495
|
+
return c.json({ success: true });
|
|
496
|
+
} catch (error: any) {
|
|
497
|
+
return c.json({ error: error.message }, 500);
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// Get agents
|
|
502
|
+
app.get("/api/agents", (c) => {
|
|
503
|
+
if (!sdk) {
|
|
504
|
+
return c.json({ error: "SDK not initialized" }, 503);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const agents = sdk.getAgents();
|
|
508
|
+
return c.json(agents);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// PERF-3: Search agents by capability (O(1) indexed lookup)
|
|
512
|
+
app.get("/api/agents/search/capability/:capability", (c) => {
|
|
513
|
+
if (!sdk) {
|
|
514
|
+
return c.json({ error: "SDK not initialized" }, 503);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
try {
|
|
518
|
+
const capability = c.req.param("capability");
|
|
519
|
+
const agents = sdk.findAgentsByCapability(capability);
|
|
520
|
+
return c.json({
|
|
521
|
+
capability,
|
|
522
|
+
count: agents.length,
|
|
523
|
+
agents
|
|
524
|
+
});
|
|
525
|
+
} catch (error: any) {
|
|
526
|
+
return c.json({ error: error.message }, 400);
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
// PERF-3: Search agents by name (O(k) token-based lookup)
|
|
531
|
+
app.get("/api/agents/search/name/:name", (c) => {
|
|
532
|
+
if (!sdk) {
|
|
533
|
+
return c.json({ error: "SDK not initialized" }, 503);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
try {
|
|
537
|
+
const name = c.req.param("name");
|
|
538
|
+
const agents = sdk.findAgentsByName(name);
|
|
539
|
+
return c.json({
|
|
540
|
+
query: name,
|
|
541
|
+
count: agents.length,
|
|
542
|
+
agents
|
|
543
|
+
});
|
|
544
|
+
} catch (error: any) {
|
|
545
|
+
return c.json({ error: error.message }, 400);
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// PERF-3: Search agents by status (O(1) indexed lookup)
|
|
550
|
+
app.get("/api/agents/search/status/:status", (c) => {
|
|
551
|
+
if (!sdk) {
|
|
552
|
+
return c.json({ error: "SDK not initialized" }, 503);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
try {
|
|
556
|
+
const status = c.req.param("status");
|
|
557
|
+
const agents = sdk.findAgentsByStatus(status);
|
|
558
|
+
return c.json({
|
|
559
|
+
status,
|
|
560
|
+
count: agents.length,
|
|
561
|
+
agents
|
|
562
|
+
});
|
|
563
|
+
} catch (error: any) {
|
|
564
|
+
return c.json({ error: error.message }, 400);
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
// Get rooms
|
|
569
|
+
app.get("/api/rooms", (c) => {
|
|
570
|
+
if (!sdk) {
|
|
571
|
+
return c.json({ error: "SDK not initialized" }, 503);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const rooms = sdk.getRooms();
|
|
575
|
+
return c.json(rooms);
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
// List all rooms
|
|
579
|
+
app.get("/api/rooms/list", async (c) => {
|
|
580
|
+
if (!sdk || !sdk.isConnected) {
|
|
581
|
+
return c.json({ error: "SDK not connected" }, 503);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
const rooms = await sdk.listRooms();
|
|
586
|
+
return c.json(rooms);
|
|
587
|
+
} catch (error: any) {
|
|
588
|
+
return c.json({ error: error.message }, 500);
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
// Join room
|
|
593
|
+
app.post("/api/room/join", async (c) => {
|
|
594
|
+
if (!sdk || !sdk.isConnected) {
|
|
595
|
+
return c.json({ error: "SDK not connected" }, 503);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
try {
|
|
599
|
+
const { roomId } = await c.req.json();
|
|
600
|
+
|
|
601
|
+
if (!roomId) {
|
|
602
|
+
return c.json({ error: "Room ID is required" }, 400);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
await sdk.subscribeToRoom(roomId);
|
|
606
|
+
return c.json({ success: true });
|
|
607
|
+
} catch (error: any) {
|
|
608
|
+
return c.json({ error: error.message }, 500);
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// Leave room
|
|
613
|
+
app.post("/api/room/leave", async (c) => {
|
|
614
|
+
if (!sdk || !sdk.isConnected) {
|
|
615
|
+
return c.json({ error: "SDK not connected" }, 503);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
try {
|
|
619
|
+
const { roomId } = await c.req.json();
|
|
620
|
+
|
|
621
|
+
if (!roomId) {
|
|
622
|
+
return c.json({ error: "Room ID is required" }, 400);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
await sdk.unsubscribeFromRoom(roomId);
|
|
626
|
+
return c.json({ success: true });
|
|
627
|
+
} catch (error: any) {
|
|
628
|
+
return c.json({ error: error.message }, 500);
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
// Get recent events
|
|
633
|
+
app.get("/api/events", (c) => {
|
|
634
|
+
return c.json(recentEvents.slice(0, 50));
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
// Get recent messages
|
|
638
|
+
app.get("/api/messages", (c) => {
|
|
639
|
+
return c.json(recentMessages.slice(0, 20));
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
// Get recent webhooks
|
|
643
|
+
app.get("/api/webhooks", (c) => {
|
|
644
|
+
return c.json(recentWebhooks.slice(0, 20));
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
// Server-Sent Events for real-time updates
|
|
648
|
+
app.get("/api/sse", (c) => {
|
|
649
|
+
const stream = new ReadableStream({
|
|
650
|
+
start(controller) {
|
|
651
|
+
sseClients.add(controller);
|
|
652
|
+
|
|
653
|
+
// Send initial connection and auth status
|
|
654
|
+
if (sdk) {
|
|
655
|
+
const connectionStatus = sdk.isConnected ? "connected" : "disconnected";
|
|
656
|
+
const authStatus = sdk.isAuthenticated ? "success" : "pending";
|
|
657
|
+
|
|
658
|
+
controller.enqueue(
|
|
659
|
+
new TextEncoder().encode(
|
|
660
|
+
`data: ${JSON.stringify({ type: "connection", status: connectionStatus })}\n\n`
|
|
661
|
+
)
|
|
662
|
+
);
|
|
663
|
+
controller.enqueue(
|
|
664
|
+
new TextEncoder().encode(
|
|
665
|
+
`data: ${JSON.stringify({ type: "auth", status: authStatus })}\n\n`
|
|
666
|
+
)
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Keep-alive ping every 30 seconds
|
|
671
|
+
const keepAlive = setInterval(() => {
|
|
672
|
+
try {
|
|
673
|
+
controller.enqueue(new TextEncoder().encode(": ping\n\n"));
|
|
674
|
+
} catch {
|
|
675
|
+
clearInterval(keepAlive);
|
|
676
|
+
sseClients.delete(controller);
|
|
677
|
+
}
|
|
678
|
+
}, 30000);
|
|
679
|
+
|
|
680
|
+
return () => {
|
|
681
|
+
clearInterval(keepAlive);
|
|
682
|
+
sseClients.delete(controller);
|
|
683
|
+
};
|
|
684
|
+
},
|
|
685
|
+
cancel() {
|
|
686
|
+
sseClients.delete(this as any);
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
return c.newResponse(stream, {
|
|
691
|
+
headers: {
|
|
692
|
+
"Content-Type": "text/event-stream",
|
|
693
|
+
"Cache-Control": "no-cache",
|
|
694
|
+
Connection: "keep-alive"
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
// Start server
|
|
700
|
+
async function startServer() {
|
|
701
|
+
try {
|
|
702
|
+
// Initialize SDK first
|
|
703
|
+
await initializeSDK();
|
|
704
|
+
|
|
705
|
+
// Start HTTP server
|
|
706
|
+
console.log(`[SERVER] Starting Hono server on port ${PORT}...`);
|
|
707
|
+
|
|
708
|
+
serve({
|
|
709
|
+
fetch: app.fetch,
|
|
710
|
+
port: PORT
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
console.log(`\nš Production Dashboard running!`);
|
|
714
|
+
console.log(`š Dashboard: http://localhost:${PORT}`);
|
|
715
|
+
console.log(`ā¤ļø Health: http://localhost:${PORT}/health`);
|
|
716
|
+
console.log(`š Metrics: http://localhost:${PORT}/metrics`);
|
|
717
|
+
console.log(`\nPress Ctrl+C to stop\n`);
|
|
718
|
+
} catch (error) {
|
|
719
|
+
console.error("[SERVER] Failed to start:", error);
|
|
720
|
+
process.exit(1);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Graceful shutdown
|
|
725
|
+
process.on("SIGINT", () => {
|
|
726
|
+
console.log("\n[SERVER] Shutting down gracefully...");
|
|
727
|
+
|
|
728
|
+
if (sdk) {
|
|
729
|
+
sdk.disconnect();
|
|
730
|
+
sdk.destroy();
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
sseClients.clear();
|
|
734
|
+
|
|
735
|
+
setTimeout(() => {
|
|
736
|
+
process.exit(0);
|
|
737
|
+
}, 1000);
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
process.on("SIGTERM", () => {
|
|
741
|
+
console.log("\n[SERVER] Received SIGTERM, shutting down...");
|
|
742
|
+
|
|
743
|
+
if (sdk) {
|
|
744
|
+
sdk.disconnect();
|
|
745
|
+
sdk.destroy();
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
sseClients.clear();
|
|
749
|
+
process.exit(0);
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
// Start the server
|
|
753
|
+
startServer();
|