@frp-bridge/core 0.0.1 → 0.0.3

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.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { ClientConfig, ServerConfig, ProxyConfig } from '@frp-bridge/types';
1
+ import { NodeRegisterPayload, NodeHeartbeatPayload, NodeInfo as NodeInfo$1, NodeListQuery, NodeListResponse, NodeStatistics, TunnelSyncPayload, ProxyConfig, ClientConfig, ServerConfig, RpcRequest as RpcRequest$1 } from '@frp-bridge/types';
2
+ import { EventEmitter } from 'node:events';
2
3
 
3
4
  type Awaitable<T> = T | Promise<T>;
4
5
  type RuntimeMode = 'client' | 'server';
@@ -62,7 +63,7 @@ interface RuntimeError {
62
63
  message: string;
63
64
  details?: Record<string, unknown>;
64
65
  }
65
- type RuntimeErrorCode = 'VALIDATION_ERROR' | 'RUNTIME_ERROR' | 'SYSTEM_ERROR';
66
+ type RuntimeErrorCode = 'VALIDATION_ERROR' | 'RUNTIME_ERROR' | 'SYSTEM_ERROR' | 'PORT_CONFLICT' | 'RPC_NOT_AVAILABLE' | 'RPC_ERROR';
66
67
  interface ConfigSnapshot {
67
68
  version: number;
68
69
  checksum: string;
@@ -113,13 +114,223 @@ declare class FrpRuntime {
113
114
  private now;
114
115
  }
115
116
 
117
+ /**
118
+ * Client-side node information collector
119
+ * Gathers system information and reports to server via heartbeat
120
+ */
121
+
122
+ interface ClientCollectorOptions {
123
+ /** Node ID (set by server after registration) */
124
+ nodeId?: string;
125
+ /** Heartbeat interval in milliseconds (default: 30000) */
126
+ heartbeatInterval?: number;
127
+ /** Logger instance */
128
+ logger?: {
129
+ debug?: (msg: string, data?: unknown) => void;
130
+ info?: (msg: string, data?: unknown) => void;
131
+ error?: (msg: string, error?: unknown) => void;
132
+ };
133
+ }
134
+ /**
135
+ * Collects node information on client side
136
+ * Used in client mode to send system info and heartbeat to server
137
+ */
138
+ declare class ClientNodeCollector {
139
+ private nodeId?;
140
+ private heartbeatInterval;
141
+ private logger;
142
+ private heartbeatTimer?;
143
+ constructor(options?: ClientCollectorOptions);
144
+ /** Set node ID after server registration */
145
+ setNodeId(nodeId: string): void;
146
+ /** Collect current node information */
147
+ collectNodeInfo(): Partial<NodeRegisterPayload>;
148
+ /** Collect heartbeat payload */
149
+ collectHeartbeat(): Partial<NodeHeartbeatPayload>;
150
+ /**
151
+ * Start periodic heartbeat collection
152
+ * Callback will be called at each interval with heartbeat payload
153
+ */
154
+ startHeartbeat(callback: (payload: Partial<NodeHeartbeatPayload>) => void, interval?: number): void;
155
+ /** Stop periodic heartbeat collection */
156
+ stopHeartbeat(): void;
157
+ /** Check if heartbeat is running */
158
+ isHeartbeatRunning(): boolean;
159
+ }
160
+
161
+ /**
162
+ * Node Manager for server-side node management
163
+ * Handles node registration, heartbeat, tunnel registry, and queries
164
+ */
165
+
166
+ interface NodeManagerOptions {
167
+ heartbeatTimeout?: number;
168
+ logger?: Partial<RuntimeLogger>;
169
+ }
170
+ interface NodeStorage {
171
+ save: (node: NodeInfo$1) => Promise<void> | void;
172
+ delete: (id: string) => Promise<void> | void;
173
+ load: (id: string) => Promise<NodeInfo$1 | undefined> | NodeInfo$1 | undefined;
174
+ list: () => Promise<NodeInfo$1[]> | NodeInfo$1[];
175
+ }
176
+ type NodeEvent = 'node:registered' | 'node:heartbeat' | 'node:unregistered' | 'node:statusChanged' | 'tunnel:synced';
177
+ /**
178
+ * Manages nodes in server mode
179
+ * Stores node info, handles heartbeat, manages global tunnel registry, emits events
180
+ */
181
+ declare class NodeManager extends EventEmitter {
182
+ private context;
183
+ private nodes;
184
+ private heartbeatTimers;
185
+ private tunnelRegistry;
186
+ private storage?;
187
+ private heartbeatTimeout;
188
+ private readonly log;
189
+ constructor(context: RuntimeContext, options?: NodeManagerOptions, storage?: NodeStorage);
190
+ initialize(): Promise<void>;
191
+ /** Register a new node (called when client connects) */
192
+ registerNode(payload: NodeRegisterPayload): Promise<NodeInfo$1>;
193
+ /** Update node heartbeat and status */
194
+ updateHeartbeat(payload: NodeHeartbeatPayload): Promise<void>;
195
+ /** Unregister a node (called when client disconnects) */
196
+ unregisterNode(nodeId: string): Promise<void>;
197
+ /** Get node by id */
198
+ getNode(id: string): Promise<NodeInfo$1 | undefined>;
199
+ /** List nodes with pagination and filtering */
200
+ listNodes(query?: NodeListQuery): Promise<NodeListResponse>;
201
+ /** Get node statistics */
202
+ getStatistics(): Promise<NodeStatistics>;
203
+ /** Check if node exists */
204
+ hasNode(id: string): boolean;
205
+ /** Get all online nodes */
206
+ getOnlineNodes(): NodeInfo$1[];
207
+ /** Get all offline nodes */
208
+ getOfflineNodes(): NodeInfo$1[];
209
+ /** Get nodes by status */
210
+ getNodesByStatus(status: NodeInfo$1['status']): NodeInfo$1[];
211
+ /** Setup heartbeat timer for a node */
212
+ private setupHeartbeatTimer;
213
+ /** Clear heartbeat timer for a node */
214
+ private clearHeartbeatTimer;
215
+ /** Handle heartbeat timeout */
216
+ private handleHeartbeatTimeout;
217
+ /** Sync tunnels for a node (called when node connects or updates tunnels) */
218
+ syncTunnels(payload: TunnelSyncPayload): Promise<void>;
219
+ /** Get tunnels for a specific node */
220
+ getNodeTunnels(nodeId: string): ProxyConfig[];
221
+ /** Get all tunnels across all nodes */
222
+ getAllTunnels(): Map<string, ProxyConfig[]>;
223
+ /** Check if a remotePort is in use across all nodes (for conflict detection) */
224
+ isRemotePortInUse(remotePort: number, excludeNodeId?: string): {
225
+ inUse: boolean;
226
+ nodeId?: string;
227
+ tunnelName?: string;
228
+ };
229
+ /** Clear tunnels for a node (called when node disconnects) */
230
+ private clearNodeTunnels;
231
+ /** Update dispose method to clear tunnels */
232
+ dispose(): Promise<void>;
233
+ }
234
+
235
+ /**
236
+ * File-based node storage implementation
237
+ * Persists node information to disk
238
+ */
239
+
240
+ /**
241
+ * Stores nodes in JSON files
242
+ * Directory structure:
243
+ * ~/.frp-bridge/runtime/nodes/
244
+ * ├── nodes.json (index of all nodes)
245
+ * └── node-{id}.json (individual node data)
246
+ */
247
+ declare class FileNodeStorage implements NodeStorage {
248
+ private storagePath;
249
+ private indexPath;
250
+ private nodeDir;
251
+ constructor(storagePath: string);
252
+ /** Save or update a node */
253
+ save(node: NodeInfo$1): Promise<void>;
254
+ /** Delete a node */
255
+ delete(id: string): Promise<void>;
256
+ /** Load a single node */
257
+ load(id: string): Promise<NodeInfo$1 | undefined>;
258
+ /** Load all nodes */
259
+ list(): Promise<NodeInfo$1[]>;
260
+ /** Update the index of node IDs */
261
+ private updateIndex;
262
+ }
263
+
264
+ /**
265
+ * 配置合并方法 - core 包内部使用
266
+ * 将预设配置和用户配置合并成最终的 frp 配置
267
+ */
268
+
269
+ /**
270
+ * 预设配置接口
271
+ */
272
+ interface PresetConfig {
273
+ frps?: FrpsPresetConfig;
274
+ frpc?: FrpcPresetConfig;
275
+ }
276
+ interface FrpsPresetConfig {
277
+ bindPort?: number;
278
+ vhostHTTPPort?: number;
279
+ vhostHTTPSPort?: number;
280
+ domain?: string;
281
+ dashboardPort?: number;
282
+ dashboardUser?: string;
283
+ dashboardPassword?: string;
284
+ authToken?: string;
285
+ subdomainHost?: string;
286
+ }
287
+ interface FrpcPresetConfig {
288
+ serverAddr?: string;
289
+ serverPort?: number;
290
+ authToken?: string;
291
+ user?: string;
292
+ heartbeatInterval?: number;
293
+ }
294
+ /**
295
+ * 默认预设配置
296
+ */
297
+ declare const DEFAULT_PRESET_CONFIG: PresetConfig;
298
+ /**
299
+ * 合并预设配置和用户配置,生成最终的 TOML 配置
300
+ */
301
+ declare function mergeConfigs(presetConfig: PresetConfig, userConfig: string, type: 'frps' | 'frpc'): string;
302
+ /**
303
+ * 从 tunnels 数组生成并保存 FRP 配置文件
304
+ */
305
+ declare function saveFrpConfigFile(configPath: string, tunnels: ProxyConfig[], presetConfig: PresetConfig, type: 'frps' | 'frpc'): void;
306
+ /**
307
+ * 将配置对象转换为 TOML 格式
308
+ */
309
+ declare function configToToml(config: Record<string, any>): string;
310
+ /**
311
+ * 验证预设配置
312
+ */
313
+ declare function validatePresetConfig(config: PresetConfig, type: 'frps' | 'frpc'): {
314
+ valid: boolean;
315
+ errors: string[];
316
+ };
317
+
116
318
  /**
117
319
  * FRP process management utilities
320
+ *
321
+ * This class serves as a facade that delegates to specialized components:
322
+ * - ProcessController: Process lifecycle management
323
+ * - ConfigurationStore: Configuration file operations
324
+ * - TunnelManager: Tunnel/proxy management
325
+ * - NodeManager: Node information management
326
+ * - BinaryManager: Binary file management
118
327
  */
119
328
 
120
329
  interface FrpProcessManagerOptions {
121
330
  /** Working directory for FRP files */
122
331
  workDir?: string;
332
+ /** Path to config file (overrides default) */
333
+ configPath?: string;
123
334
  /** FRP version (defaults to latest) */
124
335
  version?: string;
125
336
  /** Mode: client or server */
@@ -143,16 +354,29 @@ interface NodeInfo {
143
354
  }
144
355
  /**
145
356
  * Manages FRP client/server lifecycle, config, and tunnels
357
+ *
358
+ * This class now serves as a facade that delegates to specialized components:
359
+ * - ProcessController: Process lifecycle management
360
+ * - ConfigurationStore: Configuration file operations
361
+ * - TunnelManager: Tunnel/proxy management
362
+ * - NodeManager: Node information management
363
+ * - BinaryManager: Binary file management
146
364
  */
147
- declare class FrpProcessManager {
365
+ declare class FrpProcessManager extends EventEmitter {
148
366
  private readonly workDir;
149
- private version;
150
367
  private readonly mode;
151
368
  private readonly specifiedVersion?;
152
369
  private readonly logger;
370
+ private readonly configPath;
371
+ private readonly processController;
372
+ private readonly configStore;
373
+ private readonly binaryManager;
374
+ private readonly presetConfigManager;
375
+ private tunnelManager;
376
+ private nodeManager;
153
377
  private process;
154
- private configPath;
155
- private binaryPath;
378
+ private uptime;
379
+ private isManualStop;
156
380
  constructor(options: FrpProcessManagerOptions);
157
381
  /** Ensure version is fetched and binary path is set */
158
382
  private ensureVersion;
@@ -163,17 +387,17 @@ declare class FrpProcessManager {
163
387
  /** Check if binary exists */
164
388
  hasBinary(): boolean;
165
389
  /** Get current configuration */
166
- getConfig(): ClientConfig | ServerConfig | null;
390
+ getConfig(): Promise<ClientConfig | ServerConfig | null>;
167
391
  /** Update configuration */
168
- updateConfig(config: Partial<ClientConfig | ServerConfig>): void;
392
+ updateConfig(config: Partial<ClientConfig | ServerConfig>): Promise<void>;
169
393
  /** Backup configuration */
170
394
  backupConfig(): Promise<string>;
171
395
  /** Return the absolute config file path */
172
396
  getConfigPath(): string;
173
397
  /** Read raw config file contents */
174
- readConfigFile(): string | null;
398
+ getConfigRaw(): string | null;
175
399
  /** Overwrite config file with provided content */
176
- writeConfigFile(content: string): void;
400
+ updateConfigRaw(content: string): void;
177
401
  /** Start FRP process */
178
402
  start(): Promise<void>;
179
403
  /** Stop FRP process */
@@ -181,23 +405,492 @@ declare class FrpProcessManager {
181
405
  /** Check if process is running */
182
406
  isRunning(): boolean;
183
407
  /** Add node (for client mode) */
184
- addNode(node: NodeInfo): void;
408
+ addNode(node: NodeInfo): Promise<void>;
185
409
  /** Get node info */
186
- getNode(): NodeInfo | null;
410
+ getNode(): Promise<NodeInfo | null>;
187
411
  /** Update node info */
188
- updateNode(node: Partial<NodeInfo>): void;
412
+ updateNode(node: Partial<NodeInfo>): Promise<void>;
189
413
  /** Remove node */
190
- removeNode(): void;
414
+ removeNode(): Promise<void>;
191
415
  /** Add tunnel (proxy) */
192
- addTunnel(proxy: ProxyConfig): void;
416
+ addTunnel(proxy: ProxyConfig): Promise<void>;
193
417
  /** Get tunnel by name */
194
- getTunnel(name: string): ProxyConfig | null;
418
+ getTunnel(name: string): Promise<ProxyConfig | null>;
195
419
  /** Update tunnel */
196
- updateTunnel(name: string, proxy: Partial<ProxyConfig>): void;
420
+ updateTunnel(name: string, proxy: Partial<ProxyConfig>): Promise<void>;
197
421
  /** Remove tunnel */
198
- removeTunnel(name: string): void;
422
+ removeTunnel(name: string): Promise<void>;
199
423
  /** List all tunnels */
200
- listTunnels(): ProxyConfig[];
424
+ listTunnels(): Promise<ProxyConfig[]>;
425
+ /**
426
+ * 生成 FRP 配置文件(合并预设配置和用户 tunnels)
427
+ * @param force 是否强制重新生成
428
+ */
429
+ generateConfig(force?: boolean): Promise<void>;
430
+ /**
431
+ * 获取预设配置
432
+ */
433
+ getPresetConfig(): PresetConfig;
434
+ /**
435
+ * 保存预设配置
436
+ */
437
+ savePresetConfig(config: Record<string, any>): void;
438
+ /**
439
+ * Query current process status
440
+ */
441
+ queryProcess(): {
442
+ pid: number | undefined;
443
+ uptime: number;
444
+ };
445
+ }
446
+
447
+ /**
448
+ * RPC 消息类型定义
449
+ * 提供类型安全的消息结构和类型守卫
450
+ */
451
+ /**
452
+ * RPC 消息类型枚举
453
+ */
454
+ declare enum RpcMessageType {
455
+ REGISTER = "register",
456
+ COMMAND = "command",
457
+ RESPONSE = "response",
458
+ PING = "ping",
459
+ PONG = "pong"
460
+ }
461
+ /**
462
+ * 节点注册消息
463
+ */
464
+ interface RegisterMessage {
465
+ type: RpcMessageType.REGISTER;
466
+ nodeId: string;
467
+ payload: Record<string, unknown>;
468
+ }
469
+ /**
470
+ * RPC 请求
471
+ */
472
+ interface RpcRequest {
473
+ id: string;
474
+ method: string;
475
+ params: Record<string, unknown>;
476
+ timeout?: number;
477
+ }
478
+ /**
479
+ * RPC 响应状态
480
+ */
481
+ type RpcResponseStatus = 'success' | 'error';
482
+ /**
483
+ * RPC 响应
484
+ */
485
+ interface RpcResponse {
486
+ id: string;
487
+ status: RpcResponseStatus;
488
+ result?: unknown;
489
+ error?: {
490
+ code: string;
491
+ message: string;
492
+ };
493
+ }
494
+ /**
495
+ * Ping 消息
496
+ */
497
+ interface PingMessage {
498
+ type: RpcMessageType.PING;
499
+ timestamp: number;
500
+ }
501
+ /**
502
+ * Pong 消息
503
+ */
504
+ interface PongMessage {
505
+ type: RpcMessageType.PONG;
506
+ timestamp: number;
507
+ }
508
+ /**
509
+ * 所有 RPC 消息类型
510
+ */
511
+ type RpcMessage = RegisterMessage | RpcRequest | RpcResponse | PingMessage | PongMessage;
512
+ /**
513
+ * Event-based RPC message type (matching document spec)
514
+ */
515
+ interface EventRpcMessage {
516
+ type: 'command' | 'event';
517
+ action: string;
518
+ payload: unknown;
519
+ id?: string;
520
+ targetNodeId?: string;
521
+ }
522
+ /**
523
+ * Command message (frps -> frpc)
524
+ */
525
+ interface CommandRpcMessage extends EventRpcMessage {
526
+ type: 'command';
527
+ id: string;
528
+ }
529
+ /**
530
+ * Event message (frpc -> frps)
531
+ */
532
+ interface EventRpcMessageEvent extends EventRpcMessage {
533
+ type: 'event';
534
+ id?: string;
535
+ }
536
+ /**
537
+ * Tunnel add payload
538
+ */
539
+ interface TunnelAddPayload {
540
+ name: string;
541
+ type: 'tcp' | 'http' | 'https' | 'stcp' | 'sudp' | 'xtcp';
542
+ localPort: number;
543
+ remotePort?: number;
544
+ customDomains?: string[];
545
+ subdomain?: string;
546
+ nodeId?: string;
547
+ [key: string]: unknown;
548
+ }
549
+ /**
550
+ * Tunnel delete payload
551
+ */
552
+ interface TunnelDeletePayload {
553
+ name: string;
554
+ nodeId?: string;
555
+ }
556
+ /**
557
+ * Tunnel response payload
558
+ */
559
+ interface TunnelResponsePayload {
560
+ success: boolean;
561
+ error?: string;
562
+ tunnel?: TunnelAddPayload;
563
+ }
564
+ /**
565
+ * Node delete payload
566
+ */
567
+ interface NodeDeletePayload {
568
+ name: string;
569
+ }
570
+ /**
571
+ * Node response payload
572
+ */
573
+ interface NodeResponsePayload {
574
+ success: boolean;
575
+ error?: string;
576
+ deletedNode?: string;
577
+ }
578
+ /**
579
+ * All message types including event-based
580
+ */
581
+ type AllRpcMessage = RpcMessage | EventRpcMessage;
582
+ /**
583
+ * 类型守卫:检查是否为注册消息
584
+ */
585
+ declare function isRegisterMessage(msg: unknown): msg is RegisterMessage;
586
+ /**
587
+ * 类型守卫:检查是否为 RPC 请求
588
+ */
589
+ declare function isRpcRequest(msg: unknown): msg is RpcRequest;
590
+ /**
591
+ * 类型守卫:检查是否为 RPC 响应
592
+ */
593
+ declare function isRpcResponse(msg: unknown): msg is RpcResponse;
594
+ /**
595
+ * 类型守卫:检查是否为 Ping 消息
596
+ */
597
+ declare function isPingMessage(msg: unknown): msg is PingMessage;
598
+ /**
599
+ * 类型守卫:检查是否为 Pong 消息
600
+ */
601
+ declare function isPongMessage(msg: unknown): msg is PongMessage;
602
+ /**
603
+ * 类型守卫:检查是否为 Event-based RPC 消息
604
+ */
605
+ declare function isEventRpcMessage(msg: unknown): msg is EventRpcMessage;
606
+ /**
607
+ * 类型守卫:检查是否为 Command 消息
608
+ */
609
+ declare function isCommandMessage(msg: unknown): msg is CommandRpcMessage;
610
+ /**
611
+ * 类型守卫:检查是否为 Event 消息
612
+ */
613
+ declare function isEventMessage(msg: unknown): msg is EventRpcMessageEvent;
614
+ /**
615
+ * 类型守卫:检查是否为 TunnelAddPayload
616
+ */
617
+ declare function isTunnelAddPayload(data: unknown): data is TunnelAddPayload;
618
+ /**
619
+ * 类型守卫:检查是否为 TunnelDeletePayload
620
+ */
621
+ declare function isTunnelDeletePayload(data: unknown): data is TunnelDeletePayload;
622
+ /**
623
+ * 类型守卫:检查是否为 NodeDeletePayload
624
+ */
625
+ declare function isNodeDeletePayload(data: unknown): data is NodeDeletePayload;
626
+
627
+ /**
628
+ * RPC 中间件系统
629
+ * 提供可扩展的请求处理管道
630
+ */
631
+
632
+ /**
633
+ * 中间件上下文
634
+ */
635
+ interface MiddlewareContext {
636
+ request: RpcRequest;
637
+ response: Partial<RpcResponse>;
638
+ startTime: number;
639
+ }
640
+ /**
641
+ * 中间件函数类型
642
+ */
643
+ type MiddlewareFn = (context: MiddlewareContext, next: () => Promise<void>) => Promise<void>;
644
+ /**
645
+ * 中间件选项
646
+ */
647
+ interface MiddlewareOptions {
648
+ preHooks?: MiddlewareFn[];
649
+ postHooks?: MiddlewareFn[];
650
+ }
651
+ /**
652
+ * 日志中间件
653
+ */
654
+ declare function loggingMiddleware(logger?: {
655
+ info?: (msg: string, data?: unknown) => void;
656
+ warn?: (msg: string, data?: unknown) => void;
657
+ error?: (msg: string, data?: unknown) => void;
658
+ }): MiddlewareFn;
659
+ /**
660
+ * 认证中间件
661
+ */
662
+ declare function authMiddleware(validateToken: (token: string | undefined) => boolean | Promise<boolean>): MiddlewareFn;
663
+ /**
664
+ * 超时中间件
665
+ */
666
+ declare function timeoutMiddleware(timeoutMs: number): MiddlewareFn;
667
+ /**
668
+ * 错误处理中间件
669
+ */
670
+ declare function errorHandlerMiddleware(logger?: {
671
+ error?: (msg: string, data?: unknown) => void;
672
+ }): MiddlewareFn;
673
+ /**
674
+ * 中间件管道
675
+ */
676
+ declare class MiddlewarePipeline {
677
+ private middlewares;
678
+ /**
679
+ * 添加中间件
680
+ */
681
+ use(middleware: MiddlewareFn): this;
682
+ /**
683
+ * 执行中间件管道
684
+ */
685
+ execute(request: RpcRequest, handler: () => Promise<unknown>): Promise<Partial<RpcResponse>>;
686
+ /**
687
+ * 执行处理器
688
+ */
689
+ private executeHandler;
690
+ /**
691
+ * 清空中间件
692
+ */
693
+ clear(): void;
694
+ }
695
+
696
+ /**
697
+ * RPC 重连策略
698
+ * 提供可配置的重连机制,包括指数退避
699
+ */
700
+ /**
701
+ * 重连策略接口
702
+ */
703
+ interface ReconnectStrategy {
704
+ /**
705
+ * 判断是否应该重连
706
+ */
707
+ shouldReconnect: (attempt: number) => boolean;
708
+ /**
709
+ * 获取重连延迟时间(毫秒)
710
+ */
711
+ getDelay: (attempt: number) => number;
712
+ /**
713
+ * 达到最大重连次数时的回调
714
+ */
715
+ onMaxAttemptsReached: () => void;
716
+ }
717
+ /**
718
+ * 指数退避重连策略
719
+ */
720
+ declare class ExponentialBackoffStrategy implements ReconnectStrategy {
721
+ private maxAttempts;
722
+ private baseDelay;
723
+ private maxDelay;
724
+ private logger?;
725
+ constructor(maxAttempts?: number, baseDelay?: number, maxDelay?: number, logger?: {
726
+ error?: (msg: string, data?: unknown) => void;
727
+ } | undefined);
728
+ shouldReconnect(attempt: number): boolean;
729
+ getDelay(attempt: number): number;
730
+ onMaxAttemptsReached(): void;
731
+ }
732
+ /**
733
+ * 固定间隔重连策略
734
+ */
735
+ declare class FixedIntervalStrategy implements ReconnectStrategy {
736
+ private maxAttempts;
737
+ private interval;
738
+ private logger?;
739
+ constructor(maxAttempts?: number, interval?: number, logger?: {
740
+ error?: (msg: string, data?: unknown) => void;
741
+ } | undefined);
742
+ shouldReconnect(attempt: number): boolean;
743
+ getDelay(): number;
744
+ onMaxAttemptsReached(): void;
745
+ }
746
+ /**
747
+ * 线性增长重连策略
748
+ */
749
+ declare class LinearBackoffStrategy implements ReconnectStrategy {
750
+ private maxAttempts;
751
+ private baseDelay;
752
+ private increment;
753
+ private maxDelay;
754
+ private logger?;
755
+ constructor(maxAttempts?: number, baseDelay?: number, increment?: number, maxDelay?: number, logger?: {
756
+ error?: (msg: string, data?: unknown) => void;
757
+ } | undefined);
758
+ shouldReconnect(attempt: number): boolean;
759
+ getDelay(attempt: number): number;
760
+ onMaxAttemptsReached(): void;
761
+ }
762
+ /**
763
+ * 无限重连策略(永不停止)
764
+ */
765
+ declare class InfiniteReconnectStrategy implements ReconnectStrategy {
766
+ private baseDelay;
767
+ private maxDelay;
768
+ constructor(baseDelay?: number, maxDelay?: number);
769
+ shouldReconnect(): boolean;
770
+ getDelay(attempt: number): number;
771
+ onMaxAttemptsReached(): void;
772
+ }
773
+
774
+ interface RpcClientOptions {
775
+ url: string;
776
+ nodeId: string;
777
+ getRegisterPayload: () => Promise<NodeInfo$1> | NodeInfo$1;
778
+ handleRequest: (req: RpcRequest$1) => Promise<unknown>;
779
+ handleCommand?: (command: CommandRpcMessage) => Promise<unknown>;
780
+ reconnectStrategy?: ReconnectStrategy;
781
+ logger?: Partial<RuntimeLogger>;
782
+ }
783
+ declare class RpcClient {
784
+ private readonly options;
785
+ private ws;
786
+ private reconnectTimer?;
787
+ private reconnectAttempt;
788
+ private readonly reconnectStrategy;
789
+ private connectionState;
790
+ private readonly log;
791
+ constructor(options: RpcClientOptions);
792
+ connect(): Promise<void>;
793
+ disconnect(): void;
794
+ /**
795
+ * Get current connection state
796
+ */
797
+ getConnectionState(): 'connecting' | 'connected' | 'disconnected';
798
+ /**
799
+ * Check if connected
800
+ */
801
+ isConnected(): boolean;
802
+ /**
803
+ * Send an event message to server (matching document spec)
804
+ */
805
+ sendEvent(event: EventRpcMessageEvent): boolean;
806
+ private createConnection;
807
+ private handleMessage;
808
+ /**
809
+ * Handle event-based command messages from server (matching document spec)
810
+ */
811
+ private handleCommandEvent;
812
+ private handleRpcRequest;
813
+ private send;
814
+ private scheduleReconnect;
815
+ }
816
+
817
+ /**
818
+ * Command status for tracking
819
+ */
820
+ interface RpcCommandStatus {
821
+ commandId: string;
822
+ nodeId: string;
823
+ action: string;
824
+ status: 'pending' | 'completed' | 'failed';
825
+ result?: unknown;
826
+ error?: string;
827
+ timestamp: number;
828
+ }
829
+ interface RpcServerOptions {
830
+ port: number;
831
+ heartbeatInterval?: number;
832
+ validateToken?: (token: string | undefined, nodeId: string | undefined) => boolean | Promise<boolean>;
833
+ authorize?: (nodeId: string, method: string) => boolean | Promise<boolean>;
834
+ onRegister?: (nodeId: string, payload: NodeInfo$1) => void | Promise<void>;
835
+ onEvent?: (nodeId: string, event: EventRpcMessage) => void | Promise<void>;
836
+ commandTimeout?: number;
837
+ logger?: Partial<RuntimeLogger>;
838
+ }
839
+ declare class RpcServer {
840
+ private readonly options;
841
+ private readonly clients;
842
+ private readonly pendingRequests;
843
+ private readonly commandStatuses;
844
+ private readonly wsToNode;
845
+ private heartbeatTimer?;
846
+ private server?;
847
+ private readonly defaultCommandTimeout;
848
+ private readonly log;
849
+ constructor(options: RpcServerOptions);
850
+ start(): void;
851
+ stop(): void;
852
+ rpcCall(nodeId: string, method: string, params: Record<string, unknown>, timeout?: number): Promise<unknown>;
853
+ /**
854
+ * Send event-based message to a specific node (matching document spec)
855
+ */
856
+ sendToNode(nodeId: string, message: EventRpcMessage): boolean;
857
+ /**
858
+ * Broadcast event-based message to all connected nodes
859
+ */
860
+ broadcast(message: EventRpcMessage): void;
861
+ /**
862
+ * Get list of all online node IDs
863
+ */
864
+ getOnlineNodes(): string[];
865
+ /**
866
+ * Check if a specific node is online
867
+ */
868
+ isNodeOnline(nodeId: string): boolean;
869
+ /**
870
+ * Get the count of online nodes
871
+ */
872
+ getOnlineNodeCount(): number;
873
+ /**
874
+ * Get command status by ID
875
+ */
876
+ getRpcCommandStatus(commandId: string): RpcCommandStatus | undefined;
877
+ /**
878
+ * Get all command statuses
879
+ */
880
+ getAllRpcCommandStatuses(): RpcCommandStatus[];
881
+ /**
882
+ * Clear completed/failed command statuses older than specified milliseconds
883
+ */
884
+ clearOldStatuses(maxAge?: number): void;
885
+ private handleMessage;
886
+ /**
887
+ * Handle event-based messages from clients
888
+ */
889
+ private handleEventMessage;
890
+ private handleRegister;
891
+ private handleRpcResponse;
892
+ private handleClose;
893
+ private startHeartbeat;
201
894
  }
202
895
 
203
896
  interface FrpBridgeRuntimeOptions {
@@ -210,34 +903,93 @@ interface FrpBridgeRuntimeOptions {
210
903
  }
211
904
  interface FrpBridgeProcessOptions extends Partial<Omit<FrpProcessManagerOptions, 'mode'>> {
212
905
  mode?: 'client' | 'server';
213
- workDir?: string;
214
906
  }
907
+ interface FrpBridgeRpcOptions {
908
+ serverPort?: number;
909
+ serverHeartbeatInterval?: number;
910
+ serverValidateToken?: (token: string | undefined, nodeId: string | undefined) => boolean | Promise<boolean>;
911
+ serverAuthorize?: (nodeId: string, method: string) => boolean | Promise<boolean>;
912
+ serverOnRegister?: (nodeId: string, payload: NodeInfo$1) => void | Promise<void>;
913
+ serverOnEvent?: (nodeId: string, event: EventRpcMessage) => void | Promise<void>;
914
+ serverCommandTimeout?: number;
915
+ clientUrl?: string;
916
+ clientNodeId?: string;
917
+ clientToken?: string;
918
+ clientReconnectInterval?: number;
919
+ getRegisterPayload?: () => Promise<NodeInfo$1> | NodeInfo$1;
920
+ handleRequest?: (req: RpcRequest$1) => Promise<unknown>;
921
+ }
922
+
215
923
  interface FrpBridgeOptions {
216
924
  mode: 'client' | 'server';
217
925
  workDir?: string;
926
+ configPath?: string;
218
927
  runtime?: FrpBridgeRuntimeOptions;
219
928
  process?: FrpBridgeProcessOptions;
929
+ rpc?: FrpBridgeRpcOptions;
220
930
  storage?: SnapshotStorage;
221
931
  commands?: Record<string, CommandHandler>;
222
932
  queries?: Record<string, QueryHandler>;
223
933
  eventSink?: (event: RuntimeEvent) => void;
224
934
  }
935
+ /**
936
+ * FrpBridge - Main facade class for managing FRP bridge operations.
937
+ *
938
+ * This class serves as a facade that coordinates multiple components:
939
+ * - Runtime management (command/query execution)
940
+ * - Process management (FRP process lifecycle)
941
+ * - Node management (server mode only)
942
+ * - RPC communication
943
+ *
944
+ * Design patterns used:
945
+ * - Facade Pattern: Simplifies interface to complex subsystems
946
+ * - Dependency Injection: All dependencies injected via constructor
947
+ * - Factory Pattern: Handlers created via factory functions
948
+ */
225
949
  declare class FrpBridge {
226
- private readonly options;
227
950
  private readonly runtime;
228
951
  private readonly process;
952
+ private readonly mode;
229
953
  private readonly eventSink?;
954
+ private readonly nodeManager?;
955
+ private readonly clientCollector?;
956
+ private readonly rpcServer?;
957
+ private readonly rpcClient?;
230
958
  constructor(options: FrpBridgeOptions);
959
+ /**
960
+ * Execute a command
961
+ */
231
962
  execute<TPayload, TResult = unknown>(command: RuntimeCommand<TPayload>): Promise<CommandResult<TResult>>;
963
+ /**
964
+ * Execute a query
965
+ */
232
966
  query<TPayload, TResult = unknown>(query: RuntimeQuery<TPayload>): Promise<QueryResult<TResult>>;
967
+ /**
968
+ * Get current runtime state snapshot
969
+ */
233
970
  snapshot(): RuntimeState;
971
+ /**
972
+ * Drain and return all pending events
973
+ */
234
974
  drainEvents(): RuntimeEvent[];
235
975
  getProcessManager(): FrpProcessManager;
236
976
  getRuntime(): FrpRuntime;
237
- private createDefaultCommands;
238
- private createDefaultQueries;
977
+ getNodeManager(): NodeManager | undefined;
978
+ getClientCollector(): ClientNodeCollector | undefined;
979
+ getRpcServer(): RpcServer | undefined;
980
+ getRpcClient(): RpcClient | undefined;
981
+ /**
982
+ * Initialize all async components
983
+ */
984
+ initialize(): Promise<void>;
985
+ /**
986
+ * Cleanup and dispose all resources
987
+ */
988
+ dispose(): Promise<void>;
989
+ /**
990
+ * Forward runtime events to external event sink
991
+ */
239
992
  private forwardEvents;
240
- private runConfigMutation;
241
993
  }
242
994
 
243
995
  /**
@@ -257,26 +1009,122 @@ declare const ARCH_MAP: Record<string, string>;
257
1009
  /** Platform OS mapping */
258
1010
  declare const OS_MAP: Record<string, string>;
259
1011
 
260
- /** Custom error for FRP Bridge operations */
261
- declare class FrpBridgeError extends Error {
1012
+ /**
1013
+ * Base error class for all FRP Bridge errors
1014
+ * Provides consistent error structure and handling
1015
+ */
1016
+ declare abstract class FrpBridgeErrorBase extends Error {
1017
+ /**
1018
+ * Error code for programmatic error handling
1019
+ */
1020
+ abstract readonly code: string;
1021
+ /**
1022
+ * HTTP status code (optional, for API responses)
1023
+ */
1024
+ readonly statusCode?: number;
1025
+ /**
1026
+ * Additional error details
1027
+ */
1028
+ readonly details?: unknown;
1029
+ constructor(message: string, details?: unknown);
1030
+ /**
1031
+ * Convert error to plain object for serialization
1032
+ */
1033
+ toJSON(): {
1034
+ code: string;
1035
+ message: string;
1036
+ statusCode?: number;
1037
+ details?: unknown;
1038
+ };
1039
+ }
1040
+ /**
1041
+ * Generic error for uncategorized errors
1042
+ */
1043
+ declare class GenericError extends FrpBridgeErrorBase {
262
1044
  readonly code: string;
263
- readonly details?: unknown | undefined;
264
- constructor(message: string, code: string, details?: unknown | undefined);
265
- }
266
- /** Error codes */
267
- declare enum ErrorCode {
268
- BINARY_NOT_FOUND = "BINARY_NOT_FOUND",
269
- DOWNLOAD_FAILED = "DOWNLOAD_FAILED",
270
- EXTRACTION_FAILED = "EXTRACTION_FAILED",
271
- CONFIG_NOT_FOUND = "CONFIG_NOT_FOUND",
272
- CONFIG_INVALID = "CONFIG_INVALID",
273
- PROCESS_ALREADY_RUNNING = "PROCESS_ALREADY_RUNNING",
274
- PROCESS_NOT_RUNNING = "PROCESS_NOT_RUNNING",
275
- PROCESS_START_FAILED = "PROCESS_START_FAILED",
276
- UNSUPPORTED_PLATFORM = "UNSUPPORTED_PLATFORM",
277
- VERSION_FETCH_FAILED = "VERSION_FETCH_FAILED",
278
- MODE_ERROR = "MODE_ERROR",
279
- NOT_FOUND = "NOT_FOUND"
1045
+ constructor(message: string, code: string, details?: unknown);
1046
+ }
1047
+
1048
+ /**
1049
+ * Categorized error classes for FRP Bridge
1050
+ * Each error extends FrpBridgeErrorBase directly
1051
+ */
1052
+
1053
+ /**
1054
+ * Configuration errors (400 Bad Request)
1055
+ */
1056
+ declare class ConfigNotFoundError extends FrpBridgeErrorBase {
1057
+ readonly code = "CONFIG_NOT_FOUND";
1058
+ readonly statusCode = 400;
1059
+ }
1060
+ declare class ConfigInvalidError extends FrpBridgeErrorBase {
1061
+ readonly code = "CONFIG_INVALID";
1062
+ readonly statusCode = 400;
1063
+ }
1064
+ /**
1065
+ * Process errors
1066
+ */
1067
+ declare class ProcessNotRunningError extends FrpBridgeErrorBase {
1068
+ readonly code = "PROCESS_NOT_RUNNING";
1069
+ readonly statusCode = 409;
1070
+ }
1071
+ declare class ProcessAlreadyRunningError extends FrpBridgeErrorBase {
1072
+ readonly code = "PROCESS_ALREADY_RUNNING";
1073
+ readonly statusCode = 409;
1074
+ }
1075
+ declare class ProcessStartFailedError extends FrpBridgeErrorBase {
1076
+ readonly code = "PROCESS_START_FAILED";
1077
+ readonly statusCode = 500;
1078
+ }
1079
+ /**
1080
+ * Binary errors (500 Internal Server Error)
1081
+ */
1082
+ declare class BinaryNotFoundError extends FrpBridgeErrorBase {
1083
+ readonly code = "BINARY_NOT_FOUND";
1084
+ readonly statusCode = 500;
1085
+ }
1086
+ declare class DownloadFailedError extends FrpBridgeErrorBase {
1087
+ readonly code = "DOWNLOAD_FAILED";
1088
+ readonly statusCode = 500;
1089
+ }
1090
+ declare class ExtractionFailedError extends FrpBridgeErrorBase {
1091
+ readonly code = "EXTRACTION_FAILED";
1092
+ readonly statusCode = 500;
1093
+ }
1094
+ /**
1095
+ * Network/Version errors (503 Service Unavailable)
1096
+ */
1097
+ declare class VersionFetchError extends FrpBridgeErrorBase {
1098
+ readonly code = "VERSION_FETCH_FAILED";
1099
+ readonly statusCode = 503;
1100
+ }
1101
+ /**
1102
+ * Validation errors (400 Bad Request)
1103
+ */
1104
+ declare class ValidationError extends FrpBridgeErrorBase {
1105
+ readonly code = "VALIDATION_ERROR";
1106
+ readonly statusCode = 400;
1107
+ }
1108
+ /**
1109
+ * Mode/State errors (409 Conflict)
1110
+ */
1111
+ declare class ModeError extends FrpBridgeErrorBase {
1112
+ readonly code = "MODE_ERROR";
1113
+ readonly statusCode = 409;
1114
+ }
1115
+ /**
1116
+ * Resource not found (404 Not Found)
1117
+ */
1118
+ declare class NotFoundError extends FrpBridgeErrorBase {
1119
+ readonly code = "NOT_FOUND";
1120
+ readonly statusCode = 404;
1121
+ }
1122
+ /**
1123
+ * Platform errors (500 Internal Server Error)
1124
+ */
1125
+ declare class PlatformError extends FrpBridgeErrorBase {
1126
+ readonly code = "UNSUPPORTED_PLATFORM";
1127
+ readonly statusCode = 500;
280
1128
  }
281
1129
 
282
1130
  declare class FileSnapshotStorage implements SnapshotStorage {
@@ -288,6 +1136,51 @@ declare class FileSnapshotStorage implements SnapshotStorage {
288
1136
  private buildPath;
289
1137
  }
290
1138
 
1139
+ /**
1140
+ * Unified TOML parsing and serialization module
1141
+ * Wraps smol-toml with consistent error handling
1142
+ */
1143
+ interface ParseOptions {
1144
+ /**
1145
+ * Parse integers as BigInt
1146
+ */
1147
+ integersAsBigInt?: boolean | 'asNeeded';
1148
+ }
1149
+ interface StringifyOptions {
1150
+ /**
1151
+ * Serialize numbers as floats
1152
+ */
1153
+ numbersAsFloat?: boolean;
1154
+ }
1155
+ /**
1156
+ * Parse TOML string to JavaScript object
1157
+ * @param content - TOML string content
1158
+ * @param options - Parse options
1159
+ * @returns Parsed JavaScript object
1160
+ * @throws {Error} If TOML is invalid
1161
+ */
1162
+ declare function parse<T = Record<string, any>>(content: string, options?: ParseOptions): T;
1163
+ /**
1164
+ * Serialize JavaScript object to TOML string
1165
+ * @param obj - JavaScript object to serialize
1166
+ * @param options - Stringify options
1167
+ * @returns TOML string
1168
+ * @throws {Error} If object contains unserializable values
1169
+ */
1170
+ declare function stringify(obj: Record<string, any>, options?: StringifyOptions): string;
1171
+ /**
1172
+ * Check if a string is valid TOML
1173
+ * @param content - String content to check
1174
+ * @returns true if valid TOML, false otherwise
1175
+ */
1176
+ declare function isValidToml(content: string): boolean;
1177
+ /**
1178
+ * Parse TOML file content safely (returns null on error)
1179
+ * @param content - TOML string content
1180
+ * @returns Parsed object or null if parsing fails
1181
+ */
1182
+ declare function safeParse<T = Record<string, any>>(content: string): T | null;
1183
+
291
1184
  /**
292
1185
  * Utility functions
293
1186
  */
@@ -308,10 +1201,16 @@ declare function executeCommand(command: string): Promise<{
308
1201
  declare function commandExists(command: string): Promise<boolean>;
309
1202
  /** Ensure directory exists */
310
1203
  declare function ensureDir(dirPath: string): void;
311
- /** Parse TOML-like config to JSON */
1204
+ /** Find existing FRP version in work directory */
1205
+ declare function findExistingVersion(workDir: string): string | null;
1206
+ /**
1207
+ * Parse TOML-like config to JSON
1208
+ */
312
1209
  declare function parseToml(content: string): Record<string, any>;
313
- /** Convert JSON to TOML-like config */
1210
+ /**
1211
+ * Convert JSON to TOML-like config
1212
+ */
314
1213
  declare function toToml(obj: Record<string, any>): string;
315
1214
 
316
- export { ARCH_MAP, BINARY_NAMES, ErrorCode, FileSnapshotStorage, FrpBridge, FrpBridgeError, FrpProcessManager, FrpRuntime, GITHUB_OWNER, GITHUB_REPO, OS_MAP, commandExists, downloadFile, ensureDir, executeCommand, getDownloadUrl, getLatestVersion, getPlatform, parseToml, toToml };
317
- export type { Awaitable, CommandHandler, CommandHandlerContext, CommandMetadata, CommandResult, CommandStatus, ConfigSnapshot, FrpBridgeOptions, FrpProcessManagerOptions, NodeInfo, QueryHandler, QueryResult, RuntimeAdapters, RuntimeCommand, RuntimeContext, RuntimeError, RuntimeErrorCode, RuntimeEvent, RuntimeLogger, RuntimeMode, RuntimeQuery, RuntimeState, RuntimeStatus, SnapshotStorage };
1215
+ export { ARCH_MAP, BINARY_NAMES, BinaryNotFoundError, ClientNodeCollector, ConfigInvalidError, ConfigNotFoundError, DEFAULT_PRESET_CONFIG, DownloadFailedError, FrpBridgeErrorBase as Error, ExponentialBackoffStrategy, ExtractionFailedError, FileNodeStorage, FileSnapshotStorage, FixedIntervalStrategy, FrpBridge, FrpBridgeErrorBase, FrpProcessManager, FrpRuntime, GITHUB_OWNER, GITHUB_REPO, GenericError, InfiniteReconnectStrategy, LinearBackoffStrategy, MiddlewarePipeline, ModeError, NodeManager, NotFoundError, OS_MAP, PlatformError, ProcessAlreadyRunningError, ProcessNotRunningError, ProcessStartFailedError, RpcClient, RpcMessageType, RpcServer, ValidationError, VersionFetchError, authMiddleware, commandExists, configToToml, downloadFile, ensureDir, errorHandlerMiddleware, executeCommand, findExistingVersion, getDownloadUrl, getLatestVersion, getPlatform, isCommandMessage, isEventMessage, isEventRpcMessage, isNodeDeletePayload, isPingMessage, isPongMessage, isRegisterMessage, isRpcRequest, isRpcResponse, isTunnelAddPayload, isTunnelDeletePayload, isValidToml, loggingMiddleware, mergeConfigs, parse, parseToml, safeParse, saveFrpConfigFile, stringify, timeoutMiddleware, toToml, validatePresetConfig };
1216
+ export type { AllRpcMessage, Awaitable, ClientCollectorOptions, CommandHandler, CommandHandlerContext, CommandMetadata, CommandResult, CommandRpcMessage, CommandStatus, ConfigSnapshot, EventRpcMessage, EventRpcMessageEvent, FrpBridgeOptions, FrpProcessManagerOptions, FrpcPresetConfig, FrpsPresetConfig, MiddlewareContext, MiddlewareFn, MiddlewareOptions, NodeDeletePayload, NodeEvent, NodeInfo, NodeManagerOptions, NodeResponsePayload, NodeStorage, ParseOptions, PingMessage, PongMessage, PresetConfig, QueryHandler, QueryResult, ReconnectStrategy, RegisterMessage, RpcClientOptions, RpcCommandStatus, RpcMessage, RpcRequest, RpcResponse, RpcResponseStatus, RpcServerOptions, RuntimeAdapters, RuntimeCommand, RuntimeContext, RuntimeError, RuntimeErrorCode, RuntimeEvent, RuntimeLogger, RuntimeMode, RuntimeQuery, RuntimeState, RuntimeStatus, SnapshotStorage, StringifyOptions, TunnelAddPayload, TunnelDeletePayload, TunnelResponsePayload };