Skip to content

Commit c312d7c

Browse files
committed
feat: improve node manager
1 parent 8b79260 commit c312d7c

5 files changed

Lines changed: 287 additions & 41 deletions

File tree

packages/core/src/bridge/index.ts

Lines changed: 160 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,18 @@ export interface ConfigApplyRawPayload {
2727

2828
export interface ProxyAddPayload {
2929
proxy: ProxyConfig
30+
nodeId?: string // Target node ID (for server mode RPC forwarding)
3031
}
3132

3233
export interface ProxyUpdatePayload {
3334
name: string
3435
proxy: Partial<ProxyConfig>
36+
nodeId?: string // Target node ID (for server mode RPC forwarding)
3537
}
3638

3739
export interface ProxyRemovePayload {
3840
name: string
41+
nodeId?: string // Target node ID (for server mode RPC forwarding)
3942
}
4043

4144
export interface ProxyGetPayload {
@@ -452,29 +455,81 @@ export class FrpBridge {
452455
}
453456
}
454457

455-
// Proxy/tunnel management commands (client mode only)
458+
// Helper function to check if proxy type uses remotePort
459+
const typeUsesRemotePort = (type: string): boolean => {
460+
return ['tcp', 'udp', 'stcp', 'xtcp', 'sudp', 'tcpmux'].includes(type)
461+
}
462+
463+
// Proxy/tunnel management commands
456464
const proxyAdd: CommandHandler<ProxyAddPayload> = async (command, _ctx) => {
457-
if (this.mode !== 'client') {
465+
const payload = command.payload
466+
if (!payload || !payload.proxy) {
458467
return {
459468
status: 'failed',
460469
error: {
461470
code: 'VALIDATION_ERROR',
462-
message: 'proxy.add is only available in client mode'
471+
message: 'proxy.add requires payload.proxy'
463472
}
464473
}
465474
}
466475

467-
const payload = command.payload
468-
if (!payload || !payload.proxy) {
469-
return {
470-
status: 'failed',
471-
error: {
472-
code: 'VALIDATION_ERROR',
473-
message: 'proxy.add requires payload.proxy'
476+
// Server mode: forward to node via RPC or validate globally
477+
if (this.mode === 'server') {
478+
if (!payload.nodeId) {
479+
return {
480+
status: 'failed',
481+
error: {
482+
code: 'VALIDATION_ERROR',
483+
message: 'proxy.add requires payload.nodeId in server mode'
484+
}
485+
}
486+
}
487+
488+
// Check global port conflict before forwarding
489+
const proxyRemotePort = (payload.proxy as any).remotePort
490+
if (proxyRemotePort && typeUsesRemotePort(payload.proxy.type)) {
491+
const portCheck = this.nodeManager?.isRemotePortInUse(proxyRemotePort, payload.nodeId)
492+
if (portCheck?.inUse) {
493+
return {
494+
status: 'failed',
495+
error: {
496+
code: 'PORT_CONFLICT',
497+
message: `Remote port ${proxyRemotePort} is already in use by tunnel "${portCheck.tunnelName}" on node ${portCheck.nodeId}`
498+
}
499+
}
500+
}
501+
}
502+
503+
// Forward to node via RPC
504+
if (!this.rpcServer) {
505+
return {
506+
status: 'failed',
507+
error: {
508+
code: 'RPC_NOT_AVAILABLE',
509+
message: 'RPC server not available'
510+
}
511+
}
512+
}
513+
514+
try {
515+
const result = await this.rpcServer.rpcCall(payload.nodeId, 'proxy.add', { proxy: payload.proxy })
516+
return {
517+
status: 'success',
518+
result
519+
}
520+
}
521+
catch (error) {
522+
return {
523+
status: 'failed',
524+
error: {
525+
code: 'RPC_ERROR',
526+
message: error instanceof Error ? error.message : 'Failed to add tunnel on node'
527+
}
474528
}
475529
}
476530
}
477531

532+
// Client mode: add locally
478533
try {
479534
this.process.addTunnel(payload.proxy)
480535
return {
@@ -494,27 +549,73 @@ export class FrpBridge {
494549
}
495550

496551
const proxyUpdate: CommandHandler<ProxyUpdatePayload> = async (command, _ctx) => {
497-
if (this.mode !== 'client') {
552+
const payload = command.payload
553+
if (!payload || !payload.name) {
498554
return {
499555
status: 'failed',
500556
error: {
501557
code: 'VALIDATION_ERROR',
502-
message: 'proxy.update is only available in client mode'
558+
message: 'proxy.update requires payload.name'
503559
}
504560
}
505561
}
506562

507-
const payload = command.payload
508-
if (!payload || !payload.name) {
509-
return {
510-
status: 'failed',
511-
error: {
512-
code: 'VALIDATION_ERROR',
513-
message: 'proxy.update requires payload.name'
563+
// Server mode: forward to node via RPC
564+
if (this.mode === 'server') {
565+
if (!payload.nodeId) {
566+
return {
567+
status: 'failed',
568+
error: {
569+
code: 'VALIDATION_ERROR',
570+
message: 'proxy.update requires payload.nodeId in server mode'
571+
}
572+
}
573+
}
574+
575+
// Check global port conflict if remotePort is being changed
576+
const newRemotePort = (payload.proxy as any)?.remotePort
577+
if (newRemotePort && typeUsesRemotePort((payload.proxy as any)?.type)) {
578+
const portCheck = this.nodeManager?.isRemotePortInUse(newRemotePort, payload.nodeId)
579+
if (portCheck?.inUse) {
580+
return {
581+
status: 'failed',
582+
error: {
583+
code: 'PORT_CONFLICT',
584+
message: `Remote port ${newRemotePort} is already in use by tunnel "${portCheck.tunnelName}" on node ${portCheck.nodeId}`
585+
}
586+
}
587+
}
588+
}
589+
590+
if (!this.rpcServer) {
591+
return {
592+
status: 'failed',
593+
error: {
594+
code: 'RPC_NOT_AVAILABLE',
595+
message: 'RPC server not available'
596+
}
597+
}
598+
}
599+
600+
try {
601+
const result = await this.rpcServer.rpcCall(payload.nodeId, 'proxy.update', { name: payload.name, proxy: payload.proxy })
602+
return {
603+
status: 'success',
604+
result
605+
}
606+
}
607+
catch (error) {
608+
return {
609+
status: 'failed',
610+
error: {
611+
code: 'RPC_ERROR',
612+
message: error instanceof Error ? error.message : 'Failed to update tunnel on node'
613+
}
514614
}
515615
}
516616
}
517617

618+
// Client mode: update locally
518619
try {
519620
this.process.updateTunnel(payload.name, payload.proxy)
520621
return {
@@ -534,27 +635,58 @@ export class FrpBridge {
534635
}
535636

536637
const proxyRemove: CommandHandler<ProxyRemovePayload> = async (command, _ctx) => {
537-
if (this.mode !== 'client') {
638+
const payload = command.payload
639+
if (!payload || !payload.name) {
538640
return {
539641
status: 'failed',
540642
error: {
541643
code: 'VALIDATION_ERROR',
542-
message: 'proxy.remove is only available in client mode'
644+
message: 'proxy.remove requires payload.name'
543645
}
544646
}
545647
}
546648

547-
const payload = command.payload
548-
if (!payload || !payload.name) {
549-
return {
550-
status: 'failed',
551-
error: {
552-
code: 'VALIDATION_ERROR',
553-
message: 'proxy.remove requires payload.name'
649+
// Server mode: forward to node via RPC
650+
if (this.mode === 'server') {
651+
if (!payload.nodeId) {
652+
return {
653+
status: 'failed',
654+
error: {
655+
code: 'VALIDATION_ERROR',
656+
message: 'proxy.remove requires payload.nodeId in server mode'
657+
}
658+
}
659+
}
660+
661+
if (!this.rpcServer) {
662+
return {
663+
status: 'failed',
664+
error: {
665+
code: 'RPC_NOT_AVAILABLE',
666+
message: 'RPC server not available'
667+
}
668+
}
669+
}
670+
671+
try {
672+
const result = await this.rpcServer.rpcCall(payload.nodeId, 'proxy.remove', { name: payload.name })
673+
return {
674+
status: 'success',
675+
result
676+
}
677+
}
678+
catch (error) {
679+
return {
680+
status: 'failed',
681+
error: {
682+
code: 'RPC_ERROR',
683+
message: error instanceof Error ? error.message : 'Failed to remove tunnel on node'
684+
}
554685
}
555686
}
556687
}
557688

689+
// Client mode: remove locally
558690
try {
559691
this.process.removeTunnel(payload.name)
560692
return {

0 commit comments

Comments
 (0)