@@ -2,6 +2,7 @@ import type { NodeHeartbeatPayload, NodeRegisterPayload } from '@frp-bridge/type
22import type { NodeManager } from '../../node'
33import type { FrpProcessManager } from '../../process'
44import type { RpcServer } from '../../rpc'
5+ import type { NodeDeletePayload , TunnelAddPayload , TunnelDeletePayload } from '../../rpc/message-types'
56import type { CommandHandler , CommandHandlerContext , CommandResult , RuntimeEvent } from '../../runtime'
67import type { ConfigApplyPayload , ConfigApplyRawPayload , ProxyAddPayload , ProxyRemovePayload , ProxyUpdatePayload } from '../types'
78import type { Validator } from './decorators'
@@ -470,8 +471,153 @@ export function createCommandHandlers(deps: CommandDependencies): Record<string,
470471 'node.register' : createNodeRegisterCommand ( deps ) as CommandHandler ,
471472 'node.heartbeat' : createNodeHeartbeatCommand ( deps ) as CommandHandler ,
472473 'node.unregister' : createNodeUnregisterCommand ( deps ) as CommandHandler ,
474+ 'node.delete' : createNodeDeleteCommand ( deps ) as CommandHandler ,
473475 'proxy.add' : createProxyAddCommand ( deps ) as CommandHandler ,
474476 'proxy.update' : createProxyUpdateCommand ( deps ) as CommandHandler ,
475- 'proxy.remove' : createProxyRemoveCommand ( deps ) as CommandHandler
477+ 'proxy.remove' : createProxyRemoveCommand ( deps ) as CommandHandler ,
478+ // Tunnel commands (aliases for proxy commands, matching document spec)
479+ 'tunnel.add' : createTunnelAddCommand ( deps ) as CommandHandler ,
480+ 'tunnel.delete' : createTunnelDeleteCommand ( deps ) as CommandHandler
476481 }
477482}
483+
484+ // ============================================================================
485+ // Tunnel Commands (matching document spec)
486+ // ============================================================================
487+
488+ /**
489+ * Validate tunnel add payload
490+ */
491+ const validateTunnelAdd : Validator < TunnelAddPayload > = ( payload ) => {
492+ if ( ! payload ) {
493+ return { valid : false , error : 'tunnel.add requires payload' }
494+ }
495+ if ( ! payload . name || typeof payload . name !== 'string' ) {
496+ return { valid : false , error : 'tunnel.add requires payload.name' }
497+ }
498+ if ( ! payload . type || typeof payload . type !== 'string' ) {
499+ return { valid : false , error : 'tunnel.add requires payload.type' }
500+ }
501+ if ( typeof payload . localPort !== 'number' ) {
502+ return { valid : false , error : 'tunnel.add requires payload.localPort to be a number' }
503+ }
504+ return { valid : true }
505+ }
506+
507+ /**
508+ * Validate tunnel delete payload
509+ */
510+ const validateTunnelDelete : Validator < TunnelDeletePayload > = ( payload ) => {
511+ if ( ! payload ?. name ) {
512+ return { valid : false , error : 'tunnel.delete requires payload.name' }
513+ }
514+ return { valid : true }
515+ }
516+
517+ /**
518+ * Validate node delete payload
519+ */
520+ const validateNodeDelete : Validator < NodeDeletePayload > = ( payload ) => {
521+ if ( ! payload ?. name ) {
522+ return { valid : false , error : 'node.delete requires payload.name' }
523+ }
524+ return { valid : true }
525+ }
526+
527+ /**
528+ * Local tunnel add handler (client mode)
529+ * Compatible with document spec: { name, type, localPort, remotePort?, ... }
530+ */
531+ async function tunnelAddLocal ( payload : TunnelAddPayload , deps : CommandDependencies ) : Promise < CommandResult > {
532+ // Convert TunnelAddPayload to ProxyConfig format
533+ const proxyConfig = {
534+ name : payload . name ,
535+ type : payload . type ,
536+ localIP : '127.0.0.1' ,
537+ localPort : payload . localPort ,
538+ ...( payload . remotePort && { remotePort : payload . remotePort } ) ,
539+ ...( payload . customDomains && { customDomains : payload . customDomains } ) ,
540+ ...( payload . subdomain && { subdomain : payload . subdomain } )
541+ }
542+
543+ deps . process . addTunnel ( proxyConfig )
544+ return {
545+ status : 'success' ,
546+ result : { ...proxyConfig , success : true }
547+ }
548+ }
549+
550+ /**
551+ * Create tunnel add command handler (matching document spec)
552+ * This is an alias for proxy.add with different payload format
553+ */
554+ export function createTunnelAddCommand ( deps : CommandDependencies ) : CommandHandler < TunnelAddPayload > {
555+ return compose < TunnelAddPayload > (
556+ withErrorHandling ,
557+ withValidation ( validateTunnelAdd ) ,
558+ withPortConflictCheck
559+ ) (
560+ withModeRouting (
561+ tunnelAddLocal ,
562+ 'tunnel.add' ,
563+ payload => ( { proxy : payload as any } )
564+ ) ( async ( ) => ( { status : 'success' } ) , deps ) ,
565+ deps
566+ )
567+ }
568+
569+ /**
570+ * Local tunnel delete handler (client mode)
571+ */
572+ async function tunnelDeleteLocal ( payload : TunnelDeletePayload , deps : CommandDependencies ) : Promise < CommandResult > {
573+ deps . process . removeTunnel ( payload . name )
574+ return {
575+ status : 'success' ,
576+ result : { name : payload . name , success : true }
577+ }
578+ }
579+
580+ /**
581+ * Create tunnel delete command handler (matching document spec)
582+ * This is an alias for proxy.remove
583+ */
584+ export function createTunnelDeleteCommand ( deps : CommandDependencies ) : CommandHandler < TunnelDeletePayload > {
585+ return compose < TunnelDeletePayload > (
586+ withErrorHandling ,
587+ withValidation ( validateTunnelDelete )
588+ ) (
589+ withModeRouting (
590+ tunnelDeleteLocal ,
591+ 'tunnel.delete' ,
592+ payload => ( { name : payload . name } )
593+ ) ( async ( ) => ( { status : 'success' } ) , deps ) ,
594+ deps
595+ )
596+ }
597+
598+ /**
599+ * Node delete handler (server mode only)
600+ * Removes a node from the node manager
601+ */
602+ function nodeDeleteCore ( deps : CommandDependencies ) : CommandHandler < NodeDeletePayload > {
603+ return async ( command ) => {
604+ const nodeName = command . payload ! . name
605+ deps . nodeManager ! . unregisterNode ( nodeName )
606+ return {
607+ status : 'success' ,
608+ result : { deletedNode : nodeName , success : true }
609+ }
610+ }
611+ }
612+
613+ /**
614+ * Create node delete command handler (matching document spec)
615+ */
616+ export function createNodeDeleteCommand ( deps : CommandDependencies ) : CommandHandler < NodeDeletePayload > {
617+ return compose < NodeDeletePayload > (
618+ withErrorHandling ,
619+ withNodeManager ,
620+ withServerModeOnly ,
621+ withValidation ( validateNodeDelete )
622+ ) ( nodeDeleteCore ( deps ) , deps )
623+ }
0 commit comments