@frp-bridge/core 0.0.3 → 0.0.4
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 +1288 -756
- package/dist/index.js +258 -0
- package/package.json +7 -11
- package/dist/index.d.mts +0 -1216
- package/dist/index.mjs +0 -8
package/dist/index.mjs
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import{join as u}from"pathe";import{createWriteStream as Ze,existsSync as g,mkdirSync as U,readdirSync as et,appendFileSync as tt,writeFileSync as H,chmodSync as rt,readFileSync as z,renameSync as st,unlinkSync as nt,statSync as ot}from"node:fs";import{writeFile as W,readFile as M,readdir as it,unlink as at}from"node:fs/promises";import{exec as ct,spawn as lt}from"node:child_process";import{get as dt}from"node:http";import{get as ue}from"node:https";import b from"node:process";import{promisify as ut}from"node:util";import{ProxyType as w}from"@frp-bridge/types";import{homedir as J,cpus as he,totalmem as ge,release as ht,platform as gt,hostname as pt}from"node:os";import{consola as S}from"consola";import{join as pe}from"node:path";import{randomUUID as fe}from"node:crypto";import{EventEmitter as G}from"node:events";import{parse as ft,stringify as mt}from"smol-toml";import{WebSocket as C,WebSocketServer as yt}from"ws";import{Buffer as N}from"node:buffer";const wt="Unknown command",vt="Unknown query";class me{constructor(e,t={}){this.context=e,this.storage=t.storage,Object.entries(t.commands??{}).forEach(([s,n])=>{this.commandHandlers.set(s,n)}),Object.entries(t.queries??{}).forEach(([s,n])=>{this.queryHandlers.set(s,n)})}storage;commandHandlers=new Map;queryHandlers=new Map;eventBuffer=[];commandQueue=Promise.resolve();state={status:"idle",version:0};registerCommand(e,t){this.commandHandlers.set(e,t)}registerQuery(e,t){this.queryHandlers.set(e,t)}execute(e){const t=this.commandQueue.then(()=>this.runCommand(e));return this.commandQueue=t.then(()=>{},()=>{}),t}async query(e){const t=this.queryHandlers.get(e.name);if(!t)throw this.buildError("VALIDATION_ERROR",`${vt}: ${e.name}`);return t(e,this.context)}snapshot(){return{...this.state}}drainEvents(){const e=this.eventBuffer;return this.eventBuffer=[],e}async runCommand(e){const t=this.commandHandlers.get(e.name);if(!t)return{status:"failed",error:this.buildError("VALIDATION_ERROR",`${wt}: ${e.name}`)};const s={...this.state},n={bumped:!1},o={context:this.context,state:s,emit:i=>this.pushEvents(i),requestVersionBump:()=>this.bumpVersion(e.metadata?.author,n)};this.state.status="running";try{const i=await t(e,o);return i.events&&this.pushEvents(i.events),i.snapshot&&await this.persistSnapshot(i.snapshot,e.metadata?.author),i.error?(this.state.lastError=i.error,this.state.status="error"):i.status==="success"&&(this.state.lastError=void 0,this.state.status="running"),{...i,version:i.version??this.state.version}}catch(i){const a=this.normalizeError(i);return this.state.lastError=a,this.state.status="error",{status:"failed",error:a,version:this.state.version}}}pushEvents(e){const t=this.now();e.forEach(s=>{this.eventBuffer.push({...s,timestamp:s.timestamp??t,version:s.version??this.state.version})})}bumpVersion(e,t){return t.bumped?this.state.version:(t.bumped=!0,this.state.version+=1,this.state.lastAppliedAt=this.now(),e&&this.pushEvents([{type:"config:version-bumped",timestamp:this.now(),version:this.state.version,payload:{author:e}}]),this.state.version)}async persistSnapshot(e,t){this.storage&&e&&await this.storage.save({...e,version:e.version??this.state.version,appliedAt:e.appliedAt??this.now(),author:e.author??t})}normalizeError(e){return e&&typeof e=="object"&&"code"in e&&"message"in e?e:{code:"SYSTEM_ERROR",message:e instanceof Error?e.message:"Unknown error"}}buildError(e,t,s){return{code:e,message:t,details:s}}now(){return this.context.clock?this.context.clock():Date.now()}}const Q="fatedier",K="frp",T={client:b.platform==="win32"?"frpc.exe":"frpc",server:b.platform==="win32"?"frps.exe":"frps"},ye={x64:"amd64",arm64:"arm64",arm:"arm",ia32:"386"},we={win32:"windows",darwin:"darwin",linux:"linux",freebsd:"freebsd"},X=ut(ct);async function ve(){const r=`https://api.github.com/repos/${Q}/${K}/releases/latest`;return new Promise((e,t)=>{ue(r,{headers:{"User-Agent":"frp-bridge"}},s=>{if(s.statusCode!==200){t(new Error(`Failed to fetch latest version: ${s.statusCode}`));return}let n="";s.on("data",o=>n+=o),s.on("end",()=>{try{const o=JSON.parse(n).tag_name?.replace(/^v/,"")||"0.65.0";e(o)}catch(o){t(o)}})}).on("error",t)})}function Pe(){const r=we[b.platform],e=ye[b.arch];if(!r||!e)throw new Error(`Unsupported platform: ${b.platform}-${b.arch}`);return`${r}_${e}`}function be(r,e){const t=e.startsWith("windows_")?"zip":"tar.gz";return`https://github.com/${Q}/${K}/releases/download/v${r}/frp_${r}_${e}.${t}`}async function Y(r,e){return new Promise((t,s)=>{const n=Ze(e);(r.startsWith("https")?ue:dt)(r,o=>{if(o.statusCode===302||o.statusCode===301){const i=o.headers.location;if(i){n.close(),Y(i,e).then(t).catch(s);return}}if(o.statusCode!==200){s(new Error(`Failed to download: ${o.statusCode}`));return}o.pipe(n),n.on("finish",()=>{n.close(),t()})}).on("error",o=>{n.close(),s(o)})})}async function Z(r){return X(r)}async function q(r){try{return b.platform==="win32"?await X(`where ${r}`):await X(`which ${r}`),!0}catch{return!1}}function v(r){g(r)||U(r,{recursive:!0})}function Se(r){const e=`${r}/bin`;if(!g(e))return null;try{const t=et(e,{withFileTypes:!0}).filter(s=>s.isDirectory()).map(s=>s.name);return t.length>0?t[0]:null}catch{return null}}function Pt(r){const e=r.split(`
|
|
2
|
-
`),t={};let s="",n=null;for(const o of e){const i=o.trim();if(!i||i.startsWith("#"))continue;if(i.startsWith("[[")&&i.endsWith("]]")){s=i.slice(2,-2).trim(),Array.isArray(t[s])||(t[s]=[]),n={},t[s].push(n);continue}if(i.startsWith("[")&&i.endsWith("]")){s=i.slice(1,-1).trim(),n=null,t[s]||(t[s]={});continue}const a=i.indexOf("=");if(a>0){const c=i.slice(0,a).trim();let l=i.slice(a+1).trim();(l.startsWith('"')&&l.endsWith('"')||l.startsWith("'")&&l.endsWith("'"))&&(l=l.slice(1,-1)),l==="true"?l=!0:l==="false"?l=!1:Number.isNaN(Number(l))||(l=Number(l)),s?n?n[c]=l:t[s][c]=l:t[c]=l}}return t}function bt(r){const e=[];for(const[t,s]of Object.entries(r))(typeof s!="object"||s===null)&&e.push(ee(t,s));for(const[t,s]of Object.entries(r))if(Array.isArray(s)&&s.length>0&&typeof s[0]=="object"&&s[0]!==null){e.push("");for(const n of s){e.push(`[[${t}]]`);for(const[o,i]of Object.entries(n))e.push(ee(o,i));e.push("")}}for(const[t,s]of Object.entries(r))if(typeof s=="object"&&s!==null&&!Array.isArray(s)){e.push(""),e.push(`[${t}]`);for(const[n,o]of Object.entries(s))e.push(ee(n,o))}return e.join(`
|
|
3
|
-
`).trim()}function ee(r,e){return typeof e=="string"?`${r} = "${e}"`:typeof e=="boolean"||typeof e=="number"?`${r} = ${e}`:Array.isArray(e)?`${r} = [${e.map(t=>typeof t=="string"?`"${t}"`:t).join(", ")}]`:`${r} = "${String(e)}"`}function Ce(r){return!!r&&typeof r=="object"&&"version"in r}class Te{constructor(e){this.directory=e,v(e)}async save(e){if(typeof e.version!="number")throw new TypeError("Snapshot version must be a number when using FileSnapshotStorage");v(this.directory);const t=JSON.stringify(e,null,2);await W(this.buildPath(e.version),t,"utf-8")}async load(e){const t=this.buildPath(e);if(!g(t))return;const s=await M(t,"utf-8"),n=JSON.parse(s);if(!Ce(n))throw new TypeError(`Invalid snapshot schema at version ${e}`);return n}async list(){v(this.directory);const e=await it(this.directory),t=[];for(const s of e){if(!s.endsWith(".json"))continue;const n=await M(u(this.directory,s),"utf-8"),o=JSON.parse(n);Ce(o)&&t.push(o)}return t.sort((s,n)=>s.version-n.version)}buildPath(e){return u(this.directory,`${e}.json`)}}class f extends Error{statusCode;details;constructor(e,t){super(e),this.name=this.constructor.name,this.details=t}toJSON(){return{code:this.code,message:this.message,statusCode:this.statusCode,details:this.details}}}class Re extends f{code;constructor(e,t,s){super(e,s),this.code=t}}class D extends f{code="CONFIG_NOT_FOUND";statusCode=400}class E extends f{code="CONFIG_INVALID";statusCode=400}class St extends f{code="PROCESS_NOT_RUNNING";statusCode=409}class Ct extends f{code="PROCESS_ALREADY_RUNNING";statusCode=409}class Tt extends f{code="PROCESS_START_FAILED";statusCode=500}class te extends f{code="BINARY_NOT_FOUND";statusCode=500}class Rt extends f{code="DOWNLOAD_FAILED";statusCode=500}class re extends f{code="EXTRACTION_FAILED";statusCode=500}class xt extends f{code="VERSION_FETCH_FAILED";statusCode=503}class se extends f{code="VALIDATION_ERROR";statusCode=400}class m extends f{code="MODE_ERROR";statusCode=409}class L extends f{code="NOT_FOUND";statusCode=404}class Nt extends f{code="UNSUPPORTED_PLATFORM";statusCode=500}function A(r,e){return async(t,s)=>{if(e.mode!=="server")throw new m("This operation is only available in server mode");return r(t,s)}}function $t(r,e){return async(t,s)=>{if(e.mode!=="client")throw new m("This operation is only available in client mode");return r(t,s)}}function P(r){return(e,t)=>async(s,n)=>{const o=r(s.payload);if(!o.valid)throw new se(o.error||"Validation failed");return e(s,n)}}function h(r,e){return async(t,s)=>{try{return await r(t,s)}catch(n){return Mt(n)}}}function I(r,e){return async(t,s)=>{if(!e.nodeManager)throw new m("Node manager not available");return r(t,s)}}function y(...r){return(e,t)=>r.reduceRight((s,n)=>n(s,t),e)}function Mt(r){return r instanceof se?{status:"failed",error:{code:r.code,message:r.message}}:r instanceof m?{status:"failed",error:{code:r.code,message:r.message}}:r instanceof Error?{status:"failed",error:{code:"RUNTIME_ERROR",message:r.message}}:{status:"failed",error:{code:"UNKNOWN_ERROR",message:"An unknown error occurred"}}}const xe={required:(r="payload")=>e=>e?{valid:!0}:{valid:!1,error:`${r} is required`},string:r=>e=>{const t=e[r];return!t||typeof t!="string"||!t.trim()?{valid:!1,error:`${String(r)} is required and must be a non-empty string`}:{valid:!0}},number:(r,e,t)=>s=>{const n=s[r];return typeof n!="number"?{valid:!1,error:`${String(r)} must be a number`}:e!==void 0&&n<e?{valid:!1,error:`${String(r)} must be >= ${e}`}:t!==void 0&&n>t?{valid:!1,error:`${String(r)} must be <= ${t}`}:{valid:!0}},all:(...r)=>e=>{for(const t of r){const s=t(e);if(!s.valid)return s}return{valid:!0}}};function Dt(r){return[w.TCP,w.UDP,w.STCP,w.XTCP,w.SUDP,w.TCPMUX].includes(r)}function O(r,e,t){return(s,n)=>async(o,i)=>{const a=o.payload;if(n.mode==="server"){if(!a.nodeId)return{status:"failed",error:{code:"VALIDATION_ERROR",message:"nodeId is required in server mode"}};if(!n.rpcServer)return{status:"failed",error:{code:"RPC_NOT_AVAILABLE",message:"RPC server is not available"}};try{const c=t?t(a):a;return{status:"success",result:await n.rpcServer.rpcCall(a.nodeId,e,c)}}catch(c){return{status:"failed",error:{code:"RPC_ERROR",message:c instanceof Error?c.message:"RPC call failed"}}}}return r(a,n)}}function ne(r,e){return async(t,s)=>{const n=t.payload,o=n.proxy;if(o?.remotePort&&o?.type&&Dt(o.type)){const i=e.nodeManager?.isRemotePortInUse(o.remotePort,n.nodeId);if(i?.inUse)return{status:"failed",error:{code:"PORT_CONFLICT",message:`Remote port ${o.remotePort} is already in use by tunnel "${i.tunnelName}" on node ${i.nodeId}`}}}return r(t,s)}}const Et={serverOnly:(r,e)=>y(h,I,A)(r,e),clientOnly:(r,e)=>y(h,$t)(r,e),withValidation:(r,e,t)=>y(h,P(r))(e,t),serverWithRequired:(...r)=>(e,t)=>y(h,I,A,P(xe.all(...r.map(s=>xe.string(s)))))(e,t)};async function Ne(r,e,t,s){await e();const n=t??!0;let o;return n&&(r.isRunning()&&await r.stop(),await r.start(),o=[{type:"process:started",timestamp:Date.now()}]),s.requestVersionBump(),{status:"success",events:o}}const At=r=>r?.config?{valid:!0}:{valid:!1,error:"config.apply requires payload.config"},It=r=>r?.content?.trim()?{valid:!0}:{valid:!1,error:"config.applyRaw requires payload.content"};function Ot(r){return async(e,t)=>Ne(r.process,async()=>{r.process.updateConfig(e.payload.config)},e.payload.restart,t)}function kt(r){return y(h,P(At))(Ot(r),r)}function Ft(r){return async(e,t)=>{const s=e.payload.content;try{const{parse:n}=await Promise.resolve().then(function(){return Mr});n(s)}catch(n){throw new Error(`config.applyRaw received invalid TOML content: ${n instanceof Error?n.message:"Unknown error"}`)}return Ne(r.process,async()=>{r.process.updateConfigRaw(s)},e.payload.restart,t)}}function Ut(r){return y(h,P(It))(Ft(r),r)}function Ht(r){return h(async()=>r.process.isRunning()?(await r.process.stop(),{status:"success",events:[{type:"process:stopped",timestamp:Date.now()}]}):{status:"success"})}const qt=r=>r?.nodeId?{valid:!0}:{valid:!1,error:"node.heartbeat requires nodeId"},Lt=r=>r?.nodeId?{valid:!0}:{valid:!1,error:"node.unregister requires nodeId"};function jt(r){return async e=>({status:"success",result:await r.nodeManager.registerNode(e.payload)})}function Bt(r){return Et.serverWithRequired("hostname","serverAddr","serverPort")(jt(r),r)}function _t(r){return async e=>(await r.nodeManager.updateHeartbeat(e.payload),{status:"success"})}function Vt(r){return y(h,I,A,P(qt))(_t(r),r)}function zt(r){return async e=>(r.nodeManager.unregisterNode(e.payload.nodeId),{status:"success"})}function Wt(r){return y(h,I,A,P(Lt))(zt(r),r)}const Jt=r=>r?.proxy?{valid:!0}:{valid:!1,error:"proxy.add requires payload.proxy"},Gt=r=>r?.name?{valid:!0}:{valid:!1,error:"proxy.update requires payload.name"},Qt=r=>r?.name?{valid:!0}:{valid:!1,error:"proxy.remove requires payload.name"};async function Kt(r,e){return e.process.addTunnel(r.proxy),{status:"success",result:r.proxy}}function Xt(r){return y(h,P(Jt),ne)(O(Kt,"proxy.add",e=>({proxy:e.proxy}))(async()=>({status:"success"}),r),r)}async function Yt(r,e){return e.process.updateTunnel(r.name,r.proxy),{status:"success",result:{name:r.name,...r.proxy}}}function Zt(r){return y(h,P(Gt),ne)(O(Yt,"proxy.update",e=>({name:e.name,proxy:e.proxy}))(async()=>({status:"success"}),r),r)}async function er(r,e){return e.process.removeTunnel(r.name),{status:"success",result:{name:r.name}}}function tr(r){return y(h,P(Qt))(O(er,"proxy.remove",e=>({name:e.name}))(async()=>({status:"success"}),r),r)}function rr(r){return h(async()=>({status:"success",result:r.process.getPresetConfig()}))}function sr(r){return h(async e=>{const t=e.payload?.config;if(!t||typeof t!="object")throw new Error("preset.set requires payload.config");return r.process.savePresetConfig(t),await r.process.generateConfig(!0),{status:"success",result:t}})}function nr(r){return h(async e=>{const t=e.payload?.force??!1;return await r.process.generateConfig(t),{status:"success",result:{configPath:r.process.getConfigPath()}}})}function or(r){return{"config.apply":kt(r),"config.applyRaw":Ut(r),"config.generate":nr(r),"preset.get":rr(r),"preset.set":sr(r),"process.stop":Ht(r),"node.register":Bt(r),"node.heartbeat":Vt(r),"node.unregister":Wt(r),"node.delete":pr(r),"proxy.add":Xt(r),"proxy.update":Zt(r),"proxy.remove":tr(r),"tunnel.add":dr(r),"tunnel.delete":hr(r)}}const ir=r=>r?!r.name||typeof r.name!="string"?{valid:!1,error:"tunnel.add requires payload.name"}:!r.type||typeof r.type!="string"?{valid:!1,error:"tunnel.add requires payload.type"}:typeof r.localPort!="number"?{valid:!1,error:"tunnel.add requires payload.localPort to be a number"}:{valid:!0}:{valid:!1,error:"tunnel.add requires payload"},ar=r=>r?.name?{valid:!0}:{valid:!1,error:"tunnel.delete requires payload.name"},cr=r=>r?.name?{valid:!0}:{valid:!1,error:"node.delete requires payload.name"};async function lr(r,e){const t={name:r.name,type:r.type,localIP:"127.0.0.1",localPort:r.localPort,...r.remotePort&&{remotePort:r.remotePort},...r.customDomains&&{customDomains:r.customDomains},...r.subdomain&&{subdomain:r.subdomain}};return e.process.addTunnel(t),{status:"success",result:{...t,success:!0}}}function dr(r){return y(h,P(ir),ne)(O(lr,"tunnel.add",e=>({proxy:e}))(async()=>({status:"success"}),r),r)}async function ur(r,e){return e.process.removeTunnel(r.name),{status:"success",result:{name:r.name,success:!0}}}function hr(r){return y(h,P(ar))(O(ur,"tunnel.delete",e=>({name:e.name}))(async()=>({status:"success"}),r),r)}function gr(r){return async e=>{const t=e.payload.name;return r.nodeManager.unregisterNode(t),{status:"success",result:{deletedNode:t,success:!0}}}}function pr(r){return y(h,I,A,P(cr))(gr(r),r)}function fr(r){return async()=>{const e=r.runtime.snapshot();return{result:{running:r.process.isRunning(),config:r.process.getConfig()},version:e.version}}}function mr(r){return async()=>{const e=r.runtime.snapshot();return{result:e,version:e.version}}}function yr(r){return async()=>{if(!r.nodeManager)return{result:{items:[],total:0,page:1,pageSize:100,hasMore:!1},version:r.runtime.snapshot().version};const e={page:1,pageSize:100};return{result:r.nodeManager.listNodes(e),version:r.runtime.snapshot().version}}}function wr(r){return async e=>{if(!r.nodeManager)return{result:null,version:r.runtime.snapshot().version};const t=e.payload?.nodeId;return t?{result:r.nodeManager.getNode(t)??null,version:r.runtime.snapshot().version}:{result:null,version:r.runtime.snapshot().version}}}function vr(r){return async()=>r.nodeManager?{result:r.nodeManager.getStatistics(),version:r.runtime.snapshot().version}:{result:{total:0,online:0,offline:0,connecting:0,error:0},version:r.runtime.snapshot().version}}function Pr(r){return async()=>{if(r.mode!=="client")return{result:[],version:r.runtime.snapshot().version};try{return{result:await r.process.listTunnels(),version:r.runtime.snapshot().version}}catch{return{result:[],version:r.runtime.snapshot().version}}}}function br(r){return async e=>{if(r.mode!=="client")return{result:null,version:r.runtime.snapshot().version};const t=e.payload?.name;if(!t)return{result:null,version:r.runtime.snapshot().version};try{return{result:await r.process.getTunnel(t)??null,version:r.runtime.snapshot().version}}catch{return{result:null,version:r.runtime.snapshot().version}}}}function Sr(r){return{"process.status":fr(r),"runtime.snapshot":mr(r),"node.list":yr(r),"node.get":wr(r),"node.statistics":vr(r),"proxy.list":Pr(r),"proxy.get":br(r)}}const $e={debug:0,info:1,success:1,warn:2,error:3},j={reset:"\x1B[0m",dim:"\x1B[2m",debug:"\x1B[36m",info:"\x1B[34m",success:"\x1B[32m",warn:"\x1B[33m",error:"\x1B[31m"};function Cr(r){const e=r.getFullYear(),t=String(r.getMonth()+1).padStart(2,"0"),s=String(r.getDate()).padStart(2,"0"),n=String(r.getHours()).padStart(2,"0"),o=String(r.getMinutes()).padStart(2,"0"),i=String(r.getSeconds()).padStart(2,"0");return`${e}-${t}-${s} ${n}:${o}:${i}`}function Me(r){const e=r.getFullYear(),t=String(r.getMonth()+1).padStart(2,"0"),s=String(r.getDate()).padStart(2,"0");return`${e}-${t}-${s}`}function Tr(r){return r.padEnd(7)}function Rr(){return J()}function xr(r,e){return r.startsWith("/")||/^[a-z]:/i.test(r)?r:pe(e,r)}class Nr{currentDate;logFilePath;logDir;constructor(e){this.logDir=e,this.currentDate=Me(new Date),this.ensureLogDir(),this.logFilePath=this.getLogFilePath()}ensureLogDir(){g(this.logDir)||U(this.logDir,{recursive:!0})}getLogFilePath(){return pe(this.logDir,`frp-bridge-${this.currentDate}.log`)}write(e){const t=Me(new Date);t!==this.currentDate&&(this.currentDate=t,this.logFilePath=this.getLogFilePath());try{tt(this.logFilePath,`${e}
|
|
4
|
-
`,"utf-8")}catch{}}}let oe={};function $r(r){oe={...oe,...r}}function $(r,e){let t={};t={};const{level:s="info",dir:n="logs",workspaceRoot:o=oe.workspaceRoot??Rr(),enableConsole:i=!0,enableFile:a=!0}=t,c=xr(n,o),l={value:s},d=a?new Nr(c):null;function p(R,V){return(ae,x)=>{if($e[V]<$e[l.value])return;const ce=Cr(new Date),le=`[${R}]`,de=Tr(V.toUpperCase());let F="";x&&(x instanceof Error?F=` ${x.message}${x.stack?`
|
|
5
|
-
${x.stack}`:""}`:F=` ${JSON.stringify(x)}`);const Ke=`${ce} ${de} ${le} ${ae}${F}`;if(i){const Xe=j[V],Ye=`${j.dim}${ce}${j.reset} ${Xe}${de}${j.reset} ${le} ${ae}${F}`;console.log(Ye)}d&&d.write(Ke)}}return{debug:p(r,"debug"),info:p(r,"info"),success:p(r,"success"),warn:p(r,"warn"),error:p(r,"error"),setLevel(R){l.value=R}}}class De{nodeId;heartbeatInterval;logger;heartbeatTimer;constructor(e={}){this.nodeId=e.nodeId,this.heartbeatInterval=e.heartbeatInterval??3e4,this.logger=e.logger}setNodeId(e){this.nodeId=e}collectNodeInfo(){const e=he(),t=ge();return{hostname:pt(),osType:gt(),osRelease:ht(),cpuCores:e.length,memTotal:t,protocol:"tcp",serverAddr:"",serverPort:0}}collectHeartbeat(){if(!this.nodeId)throw new Error("Node ID not set. Call setNodeId() first or wait for registration.");const e=he(),t=ge();return{nodeId:this.nodeId,status:"online",lastHeartbeat:Date.now(),cpuCores:e.length,memTotal:t}}startHeartbeat(e,t){if(this.heartbeatTimer){this.logger?.debug?.("Heartbeat already running");return}const s=t??this.heartbeatInterval;try{const n=this.collectHeartbeat();e(n)}catch(n){this.logger?.error?.("Failed to collect initial heartbeat",n)}this.heartbeatTimer=setInterval(()=>{try{const n=this.collectHeartbeat();e(n)}catch(n){this.logger?.error?.("Failed to collect heartbeat",n)}},s),this.logger?.info?.(`Heartbeat started with interval ${s}ms`)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=void 0,this.logger?.info?.("Heartbeat stopped"))}isHeartbeatRunning(){return!!this.heartbeatTimer}}class Ee{constructor(e){this.storagePath=e,this.nodeDir=e,this.indexPath=u(e,"nodes.json"),g(this.nodeDir)||U(this.nodeDir,{recursive:!0})}indexPath;nodeDir;async save(e){const t=u(this.nodeDir,`node-${e.id}.json`);await W(t,JSON.stringify(e,null,2),"utf-8"),await this.updateIndex(e.id,!0)}async delete(e){const t=u(this.nodeDir,`node-${e}.json`);try{await at(t)}catch{}await this.updateIndex(e,!1)}async load(e){const t=u(this.nodeDir,`node-${e}.json`);try{const s=await M(t,"utf-8");return JSON.parse(s)}catch{return}}async list(){try{const e=await M(this.indexPath,"utf-8"),t=JSON.parse(e),s=[];for(const n of t)try{const o=await this.load(n);o&&s.push(o)}catch{}return s}catch{return[]}}async updateIndex(e,t){let s=[];try{const n=await M(this.indexPath,"utf-8");s=JSON.parse(n)}catch{}t?s.includes(e)||s.push(e):s=s.filter(n=>n!==e),await W(this.indexPath,JSON.stringify(s,null,2),"utf-8")}}let Ae=class extends G{constructor(e,t={},s){super(),this.context=e,this.heartbeatTimeout=t.heartbeatTimeout??9e4,this.storage=s}nodes=new Map;heartbeatTimers=new Map;tunnelRegistry=new Map;storage;heartbeatTimeout;log=$("NodeMgr");async initialize(){if(this.storage)try{const e=await this.storage.list();for(const t of e)this.nodes.set(t.id,t),this.setupHeartbeatTimer(t.id);this.log.info(`Loaded ${e.length} nodes from storage`)}catch(e){this.log.error("Failed to load nodes from storage",{error:e})}}async registerNode(e){const t=Date.now(),s=fe(),n={id:s,ip:e.ip,port:e.port,protocol:e.protocol,serverAddr:e.serverAddr,serverPort:e.serverPort,hostname:e.hostname,osType:e.osType,osRelease:e.osRelease,platform:e.platform,cpuCores:e.cpuCores,memTotal:e.memTotal,frpVersion:e.frpVersion,bridgeVersion:e.bridgeVersion,token:e.token,status:"online",connectedAt:t,lastHeartbeat:t,createdAt:t,updatedAt:t};if(this.nodes.set(s,n),this.setupHeartbeatTimer(s),this.storage)try{await this.storage.save(n)}catch(o){this.log.error("Failed to save node",{nodeId:s,error:o})}return this.emit("node:registered",{type:"node:registered",timestamp:t,payload:{nodeId:s,nodeInfo:n}}),this.log.success("Node registered",{nodeId:s,hostname:e.hostname,ip:e.ip}),n}async updateHeartbeat(e){const t=this.nodes.get(e.nodeId);if(!t){this.log.debug("Heartbeat for unknown node",{nodeId:e.nodeId});return}const s=t.status,n=Date.now();if(t.status=e.status,t.lastHeartbeat=n,t.updatedAt=n,e.cpuCores!==void 0&&(t.cpuCores=e.cpuCores),e.memTotal!==void 0&&(t.memTotal=e.memTotal),this.setupHeartbeatTimer(e.nodeId),this.storage)try{await this.storage.save(t)}catch(o){this.log.error("Failed to save node heartbeat",{nodeId:e.nodeId,error:o})}this.emit("node:heartbeat",{type:"node:heartbeat",timestamp:n,payload:{nodeId:e.nodeId}}),s!==e.status&&(this.log.info("Node status changed",{nodeId:e.nodeId,oldStatus:s,newStatus:e.status}),this.emit("node:statusChanged",{type:"node:statusChanged",timestamp:n,payload:{nodeId:e.nodeId,oldStatus:s,newStatus:e.status}}))}async unregisterNode(e){const t=this.nodes.get(e);if(!t){this.log.debug("Attempted to unregister unknown node",{nodeId:e});return}const s=Date.now();if(this.nodes.delete(e),this.clearHeartbeatTimer(e),this.clearNodeTunnels(e),this.storage)try{await this.storage.delete(e)}catch(n){this.log.error("Failed to delete node",{nodeId:e,error:n})}this.emit("node:unregistered",{type:"node:unregistered",timestamp:s,payload:{nodeId:e}}),this.log.info("Node unregistered",{nodeId:e,hostname:t.hostname})}async getNode(e){return this.nodes.get(e)}async listNodes(e){const t=e?.page??1,s=e?.pageSize??20,n=e?.status,o=e?.search?.toLowerCase();let i=Array.from(this.nodes.values());n&&(i=i.filter(d=>d.status===n)),e?.labels&&(i=i.filter(d=>d.labels?Object.entries(e.labels).every(([p,R])=>d.labels?.[p]===R):!1)),o&&(i=i.filter(d=>d.hostname?.toLowerCase().includes(o)||d.ip.toLowerCase().includes(o)||d.id.toLowerCase().includes(o)));const a=i.length,c=(t-1)*s,l=c+s;return{items:i.slice(c,l),total:a,page:t,pageSize:s,hasMore:l<a}}async getStatistics(){const e=Array.from(this.nodes.values());return{total:e.length,online:e.filter(t=>t.status==="online").length,offline:e.filter(t=>t.status==="offline").length,connecting:e.filter(t=>t.status==="connecting").length,error:e.filter(t=>t.status==="error").length}}hasNode(e){return this.nodes.has(e)}getOnlineNodes(){return Array.from(this.nodes.values()).filter(e=>e.status==="online")}getOfflineNodes(){return Array.from(this.nodes.values()).filter(e=>e.status==="offline")}getNodesByStatus(e){return Array.from(this.nodes.values()).filter(t=>t.status===e)}setupHeartbeatTimer(e){this.clearHeartbeatTimer(e);const t=setTimeout(()=>{this.handleHeartbeatTimeout(e)},this.heartbeatTimeout);this.heartbeatTimers.set(e,t)}clearHeartbeatTimer(e){const t=this.heartbeatTimers.get(e);t&&(clearTimeout(t),this.heartbeatTimers.delete(e))}async handleHeartbeatTimeout(e){const t=this.nodes.get(e);if(!t)return;const s=t.status;if(t.status="offline",t.updatedAt=Date.now(),this.storage)try{await this.storage.save(t)}catch(n){this.log.error("Failed to save node after timeout",{nodeId:e,error:n})}this.emit("node:statusChanged",{type:"node:statusChanged",timestamp:Date.now(),payload:{nodeId:e,oldStatus:s,newStatus:"offline",reason:"heartbeat_timeout"}}),this.log.warn("Node heartbeat timeout",{nodeId:e,hostname:t.hostname})}async syncTunnels(e){const{nodeId:t,tunnels:s,timestamp:n}=e,o=this.nodes.get(t);if(!o){this.log.warn("Tunnel sync failed: node not found",{nodeId:t});return}if(this.tunnelRegistry.set(t,s),o.tunnels=s,o.updatedAt=n,this.storage)try{await this.storage.save(o)}catch(i){this.log.error("Failed to save node after tunnel sync",{nodeId:t,error:i})}this.emit("tunnel:synced",{type:"tunnel:synced",timestamp:Date.now(),payload:{nodeId:t,tunnelCount:s.length}}),this.log.success("Tunnels synced for node",{nodeId:t,tunnelCount:s.length})}getNodeTunnels(e){return this.tunnelRegistry.get(e)||[]}getAllTunnels(){return new Map(this.tunnelRegistry)}isRemotePortInUse(e,t){for(const[s,n]of this.tunnelRegistry.entries())if(!(t&&s===t))for(const o of n){const i=o.remotePort;if(i&&i===e)return{inUse:!0,nodeId:s,tunnelName:o.name}}return{inUse:!1}}clearNodeTunnels(e){this.tunnelRegistry.delete(e),this.log.debug("Cleared tunnels for node",{nodeId:e})}async dispose(){for(const e of this.heartbeatTimers.values())clearTimeout(e);this.heartbeatTimers.clear(),this.tunnelRegistry.clear(),this.log.info("NodeManager disposed")}};function k(r,e){try{return ft(r,e)}catch(t){throw t instanceof Error?new TypeError(`Failed to parse TOML: ${t.message}`):new Error("Failed to parse TOML: Unknown error")}}function B(r,e){try{return mt(r,e)}catch(t){throw t instanceof Error?new TypeError(`Failed to stringify to TOML: ${t.message}`):new Error("Failed to stringify to TOML: Unknown error")}}function Ie(r){try{return k(r),!0}catch{return!1}}function Oe(r){try{return k(r)}catch{return null}}const Mr={__proto__:null,isValidToml:Ie,parse:k,safeParse:Oe,stringify:B},_={frps:{bindPort:7e3,vhostHTTPPort:7e3,vhostHTTPSPort:443,dashboardPort:7500,dashboardUser:"admin",dashboardPassword:"admin"},frpc:{serverPort:7e3}};function ke(r,e,t){const s=[],n=t==="frps"?r.frps:r.frpc;if(!n)return e;const o={};if(t==="frps"){const i=n;if(i.bindPort&&(o.bindPort=i.bindPort),i.vhostHTTPPort&&(o.vhostHTTPPort=i.vhostHTTPPort),i.vhostHTTPSPort&&(o.vhostHTTPSPort=i.vhostHTTPSPort),i.domain&&(o.domain=i.domain),i.subdomainHost&&(o.subdomainHost=i.subdomainHost),i.dashboardPort||i.dashboardUser||i.dashboardPassword){const a={};i.dashboardPort&&(a.addr=`0.0.0.0:${i.dashboardPort}`),i.dashboardUser&&(a.user=i.dashboardUser),i.dashboardPassword&&(a.password=i.dashboardPassword),o.webServer=a}i.authToken&&(o.auth={token:i.authToken})}else{const i=n;i.serverAddr&&(o.serverAddr=i.serverAddr),i.serverPort&&(o.serverPort=i.serverPort),i.user&&(o.user=i.user),i.heartbeatInterval&&(o.heartbeatInterval=i.heartbeatInterval),i.authToken&&(o.auth={token:i.authToken})}return s.push(Ar(o,"")),e.trim()&&s.push(e.trim()),s.filter(Boolean).join(`
|
|
6
|
-
`)}function Fe(r,e,t,s){const n=Er(e),o=ke(t,n,s),i=r.includes("/")||r.includes("\\")?r.substring(0,Math.max(r.lastIndexOf("/"),r.lastIndexOf("\\"))):".";v(i),H(r,o,"utf-8")}function Dr(r){const e=new Set(["name","type","localIP","localPort","annotations","metadatas"]);return{tcp:new Set([...e,"remotePort"]),udp:new Set([...e,"remotePort"]),http:new Set([...e,"customDomains","subdomain","locations","hostHeaderRewrite","httpUser","httpPassword"]),https:new Set([...e,"customDomains","subdomain"]),stcp:new Set([...e,"secretKey","allowUsers"]),xtcp:new Set([...e,"secretKey","allowUsers"]),sudp:new Set([...e,"secretKey","allowUsers"]),tcpmux:new Set([...e,"customDomains","subdomain","multiplexer","httpUser","httpPassword","routeByHTTPUser"])}[r.toLowerCase()]||e}function Er(r){if(!r||r.length===0)return"";const e=[];for(const t of r){e.push(""),e.push("[[proxies]]");const s=Dr(t.type);for(const[n,o]of Object.entries(t))if(o!=null&&s.has(n)){if(typeof o=="string")e.push(`${n} = "${o}"`);else if(typeof o=="number"||typeof o=="boolean")e.push(`${n} = ${o}`);else if(Array.isArray(o))o.length>0&&e.push(`${n} = ${JSON.stringify(o)}`);else if(typeof o=="object"){const i=[];for(const[a,c]of Object.entries(o))c!=null&&(typeof c=="string"?i.push(`${a} = "${c}"`):typeof c=="boolean"||typeof c=="number"?i.push(`${a} = ${c}`):Array.isArray(c)&&c.length>0&&i.push(`${a} = ${JSON.stringify(c)}`));i.length>0&&(e.push(`[${n}]`),e.push(...i))}}}return e.join(`
|
|
7
|
-
`)}function Ar(r,e=""){const t=[];for(const[s,n]of Object.entries(r))if(n!=null)if(typeof n=="object"&&!Array.isArray(n)){const o=e?`${e}.${s}`:s;t.push(`[${o}]`);for(const[i,a]of Object.entries(n))typeof a=="string"?t.push(`${i} = "${a}"`):(typeof a=="boolean"||typeof a=="number")&&t.push(`${i} = ${a}`)}else typeof n=="string"?t.push(`${s} = "${n}"`):(typeof n=="boolean"||typeof n=="number")&&t.push(`${s} = ${n}`);return t.join(`
|
|
8
|
-
`)}function Ir(r){return B(r)}function Or(r,e){const t=[],s=e==="frps"?r.frps:r.frpc;if(!s)return{valid:!0,errors:[]};if(e==="frps"){const n=s;n.bindPort!==void 0&&(n.bindPort<1||n.bindPort>65535)&&t.push("bindPort must be between 1 and 65535"),n.vhostHTTPPort!==void 0&&(n.vhostHTTPPort<1||n.vhostHTTPPort>65535)&&t.push("vhostHTTPPort must be between 1 and 65535"),n.dashboardPort!==void 0&&(n.dashboardPort<1||n.dashboardPort>65535)&&t.push("dashboardPort must be between 1 and 65535")}else{const n=s;n.serverPort!==void 0&&(n.serverPort<1||n.serverPort>65535)&&t.push("serverPort must be between 1 and 65535"),n.serverAddr&&typeof n.serverAddr!="string"&&t.push("serverAddr must be a string")}return{valid:t.length===0,errors:t}}class kr{async extractArchive(e,t){if(!await q("unzip"))throw new re("unzip is required for extraction on Windows");await Z(`unzip -o "${e}" -d "${t}"`)}setExecutable(e){}getArchiveExtension(){return"zip"}}class Fr{async extractArchive(e,t){const s=await q("gzip"),n=await q("tar");if(!s||!n)throw new re("gzip and tar are required for extraction");await Z(`tar -xzf "${e}" -C "${t}"`)}setExecutable(e){rt(e,493)}getArchiveExtension(){return"tar.gz"}}class Ur{static create(){return b.platform==="win32"?new kr:new Fr}}class Hr{workDir;mode;logger;platformStrategy=Ur.create();version=null;binaryPath="";constructor(e){this.workDir=e.workDir,this.mode=e.mode,this.logger=e.logger??S.withTag("BinaryManager")}async ensureInstalled(e){if(!this.version){this.version=e||Se(this.workDir)||"";const t=this.mode==="client"?T.client:T.server;this.binaryPath=u(this.workDir,"bin",this.version,t)}return this.hasBinary()||await this.download(),this.binaryPath}async download(e){const t=e||this.version||await this.getLatest(),s=Pe(),n=be(t,s),o=this.platformStrategy.getArchiveExtension(),i=u(this.workDir,`frp_${t}.${o}`),a=u(this.workDir,"bin",t);v(a),await Y(n,i);const c=u(this.workDir,"temp");v(c),await this.platformStrategy.extractArchive(i,c);const l=u(c,`frp_${t}_${s}`),d=u(l,this.mode==="client"?T.client:T.server);if(!g(d))throw new te(`Binary not found: ${d}`);const p=await import("fs-extra");await p.copy(d,this.binaryPath),this.platformStrategy.setExecutable(this.binaryPath),this.version=t;const R=this.mode==="client"?T.client:T.server;this.binaryPath=u(this.workDir,"bin",t,R),await p.remove(i),await p.remove(c)}async update(e){if(this.version!==e){if(this.hasBinary()){const t=`${this.binaryPath}.bak`;await(await import("fs-extra")).copy(this.binaryPath,t)}await this.download(e)}}getInstalledVersion(){return this.version}getBinaryPath(){return this.binaryPath}hasBinary(){return g(this.binaryPath)}async remove(e){const t=e||this.version;if(!t)return;const s=this.mode==="client"?T.client:T.server,n=u(this.workDir,"bin",t,s);g(n)&&await(await import("fs-extra")).remove(n),t===this.version&&(this.version=null,this.binaryPath="")}async getLatest(){return ve()}async checkUpdate(){const e=await this.getLatest();return{current:this.version,latest:e}}}class qr{logger;cache=new Map;cacheTTL;constructor(e={}){this.logger=e.logger??S.withTag("ConfigurationStore"),this.cacheTTL=e.cacheTTL??5e3}async load(e){const t=this.getFromCache(e);if(t)return t;if(!g(e))throw new D(`Config file not found: ${e}`);const s=z(e,"utf-8"),n=this.getFileMtime(e);let o;try{o=k(s)}catch(i){throw new E(`Failed to parse config: ${i instanceof Error?i.message:String(i)}`)}return this.setToCache(e,o,n),o}async save(e,t){const s=this.validate(t);if(!s.valid)throw new E(`Config validation failed: ${s.errors.join(", ")}`);const n=B(t),o=`${e}.tmp`;this.ensureDirectory(e),H(o,n,"utf-8");try{st(o,e);const i=this.getFileMtime(e);this.setToCache(e,t,i)}catch(i){throw g(o)&&nt(o),i}}merge(e,t){const s={...e};for(const n of Object.keys(t)){const o=t[n];n==="auth"?s[n]={...e.auth,...o}:s[n]=o??e[n]}return s}validate(e){const t=[];return!e||typeof e!="object"?(t.push("Config must be an object"),{valid:!1,errors:t}):{valid:t.length===0,errors:t}}exists(e){return g(e)}getRaw(e){return g(e)?z(e,"utf-8"):null}writeRaw(e,t){this.ensureDirectory(e),H(e,t,"utf-8"),this.invalidateCache(e)}invalidateCache(e){this.cache.delete(e)}clearCache(){this.cache.clear()}getFromCache(e){const t=this.cache.get(e);if(!t)return null;const s=Date.now(),n=this.getFileMtime(e);return t.mtime===n&&s-t.cachedAt<this.cacheTTL?t.config:(this.cache.delete(e),null)}setToCache(e,t,s){this.cache.set(e,{path:e,config:t,mtime:s,cachedAt:Date.now()})}getFileMtime(e){try{return ot(e).mtimeMs}catch{return 0}}ensureDirectory(e){const t=e.substring(0,Math.max(e.lastIndexOf("/"),e.lastIndexOf("\\")));t&&t!==e&&!g(t)&&U(t,{recursive:!0})}}class Lr{configStore;configPath;log=$("Node");constructor(e){this.configStore=e.configStore,this.configPath=e.configPath}async setNode(e){const t=await this.loadConfig()||{};t.serverAddr=e.serverAddr,t.serverPort=e.serverPort??7e3,e.token&&(t.auth={...t.auth,token:e.token}),e.config&&Object.assign(t,e.config),await this.configStore.save(this.configPath,t),this.log.success("Node configured",{serverAddr:e.serverAddr,serverPort:e.serverPort??7e3})}async getNode(){const e=await this.loadConfig();return!e||!e.serverAddr?(this.log.debug("Node not configured"),null):{id:"default",name:"default",serverAddr:e.serverAddr,serverPort:e.serverPort,token:e.auth?.token}}async updateNode(e){const t=await this.loadConfig()||{};e.serverAddr&&(t.serverAddr=e.serverAddr),e.serverPort&&(t.serverPort=e.serverPort),e.token&&(t.auth={...t.auth,token:e.token}),e.config&&Object.assign(t,e.config),await this.configStore.save(this.configPath,t),this.log.success("Node updated",{updates:Object.keys(e)})}async clearNode(){const{unlinkSync:e}=await import("node:fs"),{existsSync:t}=await import("node:fs");t(this.configPath)?(e(this.configPath),this.log.success("Node configuration cleared")):this.log.debug("No node configuration to clear")}validateNode(e){const t=[];e.serverAddr||t.push("Server address is required"),e.serverPort!==void 0&&(e.serverPort<1||e.serverPort>65535)&&t.push("Server port must be between 1-65535");const s=t.length===0;return s?this.log.debug("Node validation passed",{serverAddr:e.serverAddr}):this.log.warn("Node validation failed",{errors:t}),{valid:s,errors:t}}async loadConfig(){return this.configStore.exists(this.configPath)?this.configStore.load(this.configPath):null}}class jr{logger;configDir;constructor(e){this.logger=e.logger??S.withTag("PresetConfigManager"),this.configDir=e.configDir||u(e.workDir,"config")}getPresetConfigPath(e){return u(this.configDir,`${e}-preset.json`)}load(e){const t=this.getPresetConfigPath(e);if(!g(t))return this.logger.info(`Preset config not found, using defaults for ${e}`),{[e]:_[e]||{}};try{const s=z(t,"utf-8"),n=JSON.parse(s);return{[e]:n}}catch(s){return this.logger.error(`Failed to load preset config for ${e}:`,{error:s}),{[e]:_[e]||{}}}}save(e,t){const s=this.getPresetConfigPath(e);v(this.configDir),H(s,JSON.stringify(t,null,2),"utf-8"),this.logger.info(`Preset config saved for ${e}`)}getDefaultConfig(e){return _[e]||{}}}class Br extends G{logger;log=$("Process");process=null;processStartTime=null;currentBinaryPath="";currentConfigPath="";isManualStop=!1;gracefulTimeout=5e3;constructor(e={}){super(),this.logger=e.logger??console}async start(e,t){this.validateStartPrerequisites(e,t),this.isRunning()&&await this.stop(),this.currentBinaryPath=e,this.currentConfigPath=t,this.log.info("Starting process",{binaryPath:e,configPath:t}),this.process=lt(e,["-c",t],{stdio:"inherit"}),this.processStartTime=Date.now(),this.isManualStop=!1,this.setupProcessListeners();const s=this.createProcessHandle();return this.log.success("Process started",{pid:s.pid,configPath:t}),this.emit("process:started",{type:"process:started",timestamp:Date.now(),payload:{pid:s.pid,uptime:0}}),s}async stop(){if(!this.process)return;this.isManualStop=!0;const e=this.process,t=e.pid;return this.log.info("Stopping process",{pid:t}),new Promise(s=>{const n=()=>{const o=this.processStartTime?Date.now()-this.processStartTime:void 0;this.emit("process:stopped",{type:"process:stopped",timestamp:Date.now(),payload:{uptime:o}}),this.processStartTime=null,this.log.success("Process stopped",{pid:t,uptime:o}),s()};e.exitCode===null?(e.once("exit",n),e.kill("SIGTERM"),setTimeout(()=>{e.exitCode===null&&(this.log.warn("Process did not exit gracefully, forcing kill",{pid:t}),e.kill("SIGKILL"))},this.gracefulTimeout)):n()}).finally(()=>{this.process=null})}async restart(e,t){const s=e,n=t;return this.log.info("Restarting process",{binaryPath:e,configPath:t}),this.isRunning()&&await this.stop(),await new Promise(o=>setTimeout(o,500)),this.start(s,n)}isRunning(){if(!this.process)return!1;const e=this.process.exitCode===null&&this.process.signalCode===null;return e||(this.process=null,this.processStartTime=null),e}getStatus(){return!this.process||!this.processStartTime?null:{pid:this.process.pid??0,running:this.isRunning(),uptime:Date.now()-this.processStartTime,startTime:this.processStartTime,exitCode:this.process.exitCode,signal:this.process.signalCode}}getPid(){return this.process?.pid??null}getUptime(){return this.processStartTime?Date.now()-this.processStartTime:0}validateStartPrerequisites(e,t){if(!g(e))throw new te(`Binary not found: ${e}`);if(!g(t))throw new D(`Config file not found: ${t}`)}setupProcessListeners(){this.process&&(this.process.on("exit",(e,t)=>{const s=this.processStartTime?Date.now()-this.processStartTime:void 0;this.isManualStop||(this.log.error("Process exited unexpectedly",{code:e,signal:t,uptime:s}),this.emit("process:exited",{type:"process:exited",timestamp:Date.now(),payload:{code:e??void 0,signal:t??void 0,uptime:s,unexpected:!0}})),this.process=null,this.processStartTime=null}),this.process.on("error",e=>{this.log.error("Process error",{error:e.message,pid:this.process?.pid}),this.emit("process:error",{type:"process:error",timestamp:Date.now(),payload:{error:e.message,pid:this.process?.pid}}),this.logger.error("Process error",{error:e})}))}createProcessHandle(){if(!this.process)throw new Re("Process not initialized","PROCESS_NOT_INITIALIZED");return{pid:this.process.pid??0,startTime:Date.now(),running:!0,exitCode:null,signal:null,configPath:this.currentConfigPath,binaryPath:this.currentBinaryPath}}}class _r{configStore;configPath;logger;log=$("Tunnel");constructor(e){this.configStore=e.configStore,this.configPath=e.configPath,this.logger=e.logger??console}async add(e){const t=await this.loadConfig()||{};Array.isArray(t.proxies)||(t.proxies=[]),this.validateUniqueness(t,e),t.proxies.push(e),await this.configStore.save(this.configPath,t),this.log.success("Tunnel added",{name:e.name,type:e.type})}async get(e){const t=await this.loadConfig();if(!t)return this.log.debug("Tunnel not found: config is empty",{name:e}),null;if(Array.isArray(t.proxies)){const s=t.proxies.find(n=>n&&n.name===e)||null;return s||this.log.debug("Tunnel not found in proxies array",{name:e}),s}return t[e]||null}async update(e,t){const s=await this.loadConfig();if(!s)throw new L(`Tunnel ${e} not found`);if(Array.isArray(s.proxies)){const n=s.proxies.findIndex(c=>c&&c.name===e);if(n===-1)throw new L(`Tunnel ${e} not found`);const o=s.proxies[n],i={...o,...t},a=t.remotePort;a&&a!==o.remotePort&&this.validateRemotePort(s.proxies,a,i.type,n),s.proxies[n]=i}else if(s[e])s[e]={...s[e],...t};else throw new L(`Tunnel ${e} not found`);await this.configStore.save(this.configPath,s),this.log.success("Tunnel updated",{name:e,changes:Object.keys(t)})}async remove(e){const t=await this.loadConfig();if(!t){this.log.debug("Cannot remove tunnel: config is empty",{name:e});return}let s=!1;if(Array.isArray(t.proxies)){const n=t.proxies.findIndex(o=>o&&o.name===e);n!==-1?(t.proxies.splice(n,1),s=!0,this.log.success("Tunnel removed",{name:e})):this.log.debug("Tunnel not found for removal",{name:e})}else t[e]?(delete t[e],s=!0,this.log.success("Tunnel removed",{name:e})):this.log.debug("Tunnel not found for removal",{name:e});s&&await this.configStore.save(this.configPath,t)}async list(){const e=await this.loadConfig();if(!e)return this.log.debug("List tunnels: config is empty"),[];const t=[];if(Array.isArray(e.proxies))for(const n of e.proxies)n&&typeof n=="object"&&"type"in n&&t.push(n);const s=new Set(t.map(n=>n.name));for(const[n,o]of Object.entries(e))if(n!=="proxies"&&typeof o=="object"&&o!==null&&"type"in o&&!Array.isArray(o)){const i={...o,name:o.name||n};s.has(i.name)||(t.push(i),s.add(i.name))}return this.log.debug("Listed tunnels",{count:t.length}),t}async exists(e){return await this.get(e)!==null}validate(e){const t=[];e.name||t.push("Tunnel name is required"),e.type||t.push("Tunnel type is required");const s=t.length===0;return s?this.log.debug("Tunnel validation passed",{name:e.name,type:e.type}):this.log.warn("Tunnel validation failed",{errors:t}),{valid:s,errors:t}}async loadConfig(){return this.configStore.exists(this.configPath)?this.configStore.load(this.configPath):null}validateUniqueness(e,t){const s=e.proxies||[];if(s.find(o=>o&&o.name===t.name))throw this.log.warn("Tunnel name already exists",{name:t.name}),new E(`Tunnel ${t.name} already exists`);const n=t.remotePort;n&&this.typeUsesRemotePort(t.type)&&this.validateRemotePort(s,n,t.type),this.log.debug("Tunnel uniqueness validation passed",{name:t.name})}validateRemotePort(e,t,s,n=-1){if(this.typeUsesRemotePort(s)){if(e.some((o,i)=>{if(i===n)return!1;const a=o.remotePort;return o&&a===t&&this.typeUsesRemotePort(o.type)}))throw this.log.warn("Remote port already in use",{remotePort:t,type:s}),new E(`Remote port ${t} is already in use`);this.log.debug("Remote port validation passed",{remotePort:t,type:s})}}typeUsesRemotePort(e){return[w.TCP,w.UDP,w.STCP,w.XTCP,w.SUDP,w.TCPMUX].includes(e)}}class Ue extends G{workDir;mode;specifiedVersion;logger;configPath;processController;configStore;binaryManager;presetConfigManager;tunnelManager=null;nodeManager=null;process=null;uptime=null;isManualStop=!1;constructor(e){super(),this.mode=e.mode,this.specifiedVersion=e.version,this.workDir=e.workDir||u(J(),".frp-bridge"),this.configPath=e.configPath||u(this.workDir,`frp${this.mode==="client"?"c":"s"}.toml`),this.logger=e.logger??S.withTag("FrpProcessManager"),this.configStore=new qr({logger:this.logger}),this.processController=new Br({logger:this.logger}),this.binaryManager=new Hr({workDir:this.workDir,mode:this.mode,logger:this.logger}),this.presetConfigManager=new jr({workDir:this.workDir,logger:this.logger}),this.mode==="client"&&(this.tunnelManager=new _r({configStore:this.configStore,configPath:this.configPath,logger:this.logger}),this.nodeManager=new Lr({configStore:this.configStore,configPath:this.configPath,logger:this.logger})),this.processController.on("process:started",t=>{this.emit(t.type,t)}),this.processController.on("process:stopped",t=>{this.emit(t.type,t)}),this.processController.on("process:exited",t=>{this.emit(t.type,t)}),this.processController.on("process:error",t=>{this.emit(t.type,t)})}async ensureVersion(){return await this.binaryManager.ensureInstalled(this.specifiedVersion)}async downloadFrpBinary(){await this.binaryManager.download()}async updateFrpBinary(e){const t=e||await this.binaryManager.getLatest();await this.binaryManager.update(t)}hasBinary(){return this.binaryManager.hasBinary()}async getConfig(){return this.configStore.exists(this.configPath)?this.configStore.load(this.configPath):null}async updateConfig(e){const t=await this.getConfig(),s=this.configStore.merge(t||{},e);await this.configStore.save(this.configPath,s)}async backupConfig(){if(!this.configStore.exists(this.configPath))throw new D("Config file does not exist");const e=Date.now(),t=`${this.configPath}.${e}.bak`;return await(await import("fs-extra")).copy(this.configPath,t),t}getConfigPath(){return this.configPath}getConfigRaw(){return this.configStore.getRaw(this.configPath)}updateConfigRaw(e){this.configStore.writeRaw(this.configPath,e)}async start(){const e=await this.ensureVersion();if(this.isRunning()&&await this.stop(),this.hasBinary()||await this.downloadFrpBinary(),!this.configStore.exists(this.configPath))throw new D("Config file does not exist");await this.processController.start(e,this.configPath);const t=this.processController.getStatus();t&&(this.uptime=t.startTime,this.process={pid:t.pid,exitCode:t.exitCode,signalCode:t.signal,kill:()=>{}})}async stop(){this.processController.isRunning()&&(this.isManualStop=!0,await this.processController.stop(),this.uptime=null,this.process=null)}isRunning(){return this.processController.isRunning()}async addNode(e){if(!this.nodeManager)throw new m("Nodes can only be added in client mode");await this.nodeManager.setNode(e)}async getNode(){if(!this.nodeManager)throw new m("Nodes are only available in client mode");return this.nodeManager.getNode()}async updateNode(e){if(!this.nodeManager)throw new m("Nodes can only be updated in client mode");await this.nodeManager.updateNode(e)}async removeNode(){if(!this.nodeManager)throw new m("Nodes can only be removed in client mode");await this.nodeManager.clearNode()}async addTunnel(e){if(!this.tunnelManager)throw new m("Tunnels can only be added in client mode");await this.tunnelManager.add(e)}async getTunnel(e){if(!this.tunnelManager)throw new m("Tunnels are only available in client mode");return this.tunnelManager.get(e)}async updateTunnel(e,t){if(!this.tunnelManager)throw new m("Tunnels can only be updated in client mode");await this.tunnelManager.update(e,t)}async removeTunnel(e){if(!this.tunnelManager)throw new m("Tunnels can only be removed in client mode");await this.tunnelManager.remove(e)}async listTunnels(){if(!this.tunnelManager)throw new m("Tunnels are only available in client mode");return this.tunnelManager.list()}async generateConfig(e=!1){const t=this.mode==="server"?"frps":"frpc";if(!e&&this.configStore.exists(this.configPath))return;const s=this.presetConfigManager.load(t);let n=[];this.mode==="client"&&this.tunnelManager&&(n=await this.tunnelManager.list()),Fe(this.configPath,n,s,t),this.logger.info(`Generated FRP config: ${this.configPath}`)}getPresetConfig(){const e=this.mode==="server"?"frps":"frpc";return this.presetConfigManager.load(e)}savePresetConfig(e){const t=this.mode==="server"?"frps":"frpc";this.presetConfigManager.save(t,e)}queryProcess(){const e=this.uptime?Date.now()-this.uptime:0;return{pid:this.processController.getStatus()?.pid??this.process?.pid,uptime:e}}}var He=(r=>(r.REGISTER="register",r.COMMAND="command",r.RESPONSE="response",r.PING="ping",r.PONG="pong",r))(He||{});function qe(r){return typeof r=="object"&&r!==null&&r.type==="register"}function Le(r){return typeof r=="object"&&r!==null&&typeof r.id=="string"&&typeof r.method=="string"}function je(r){return typeof r=="object"&&r!==null&&typeof r.id=="string"&&typeof r.status=="string"}function Be(r){return typeof r=="object"&&r!==null&&r.type==="ping"}function _e(r){return typeof r=="object"&&r!==null&&r.type==="pong"}function ie(r){return typeof r=="object"&&r!==null&&(r.type==="command"||r.type==="event")&&typeof r.action=="string"&&"payload"in r}function Ve(r){return ie(r)&&r.type==="command"&&typeof r.id=="string"}function ze(r){return ie(r)&&r.type==="event"}function Vr(r){if(typeof r!="object"||r===null)return!1;const e=r;return typeof e.name=="string"&&typeof e.type=="string"&&typeof e.localPort=="number"&&(e.remotePort===void 0||typeof e.remotePort=="number")}function zr(r){return typeof r=="object"&&r!==null&&typeof r.name=="string"}function Wr(r){return typeof r=="object"&&r!==null&&typeof r.name=="string"}function Jr(r){return async(e,t)=>{const{request:s}=e;r?.info?.("RPC request",{method:s.method,params:s.params});try{await t(),r?.info?.("RPC response",{method:s.method,duration:Date.now()-e.startTime,status:e.response.status})}catch(n){throw r?.error?.("RPC error",{method:s.method,error:n instanceof Error?n.message:"Unknown error"}),n}}}function Gr(r){return async(e,t)=>{const{request:s,response:n}=e,o=s.params.token;if(!await r(o)){n.status="error",n.error={code:"UNAUTHORIZED",message:"Invalid or missing token"};return}await t()}}function Qr(r){return async(e,t)=>{const{request:s}=e,n=s.timeout??r,o=new Promise((i,a)=>{setTimeout(()=>{a(new Error(`RPC timeout: ${s.method}`))},n)});try{await Promise.race([t(),o])}catch(i){throw e.response.status="error",e.response.error={code:"TIMEOUT",message:i instanceof Error?i.message:"Request timeout"},i}}}function Kr(r){return async(e,t)=>{try{await t()}catch(s){e.response.status="error",e.response.error={code:s instanceof Error?s.name:"UNKNOWN_ERROR",message:s instanceof Error?s.message:"Unknown error"},r?.error?.("RPC middleware error",s)}}}class Xr{middlewares=[];use(e){return this.middlewares.push(e),this}async execute(e,t){const s={request:e,response:{id:e.id},startTime:Date.now()};let n=async()=>this.executeHandler(s,t);for(let o=this.middlewares.length-1;o>=0;o--){const i=this.middlewares[o],a=n;n=async()=>i(s,a)}return await n(),s.response}async executeHandler(e,t){const s=await t();e.response.result=s,e.response.status="success"}clear(){this.middlewares=[]}}class We{constructor(e=10,t=1e3,s=3e4,n){this.maxAttempts=e,this.baseDelay=t,this.maxDelay=s,this.logger=n}shouldReconnect(e){return e<this.maxAttempts}getDelay(e){const t=Math.min(this.baseDelay*2**e,this.maxDelay),s=t*.25*(Math.random()*2-1);return Math.max(0,Math.floor(t+s))}onMaxAttemptsReached(){const e=`Max reconnection attempts reached (${this.maxAttempts})`;this.logger?.error?.(e),console.error(`[RpcClient] ${e}`)}}class Yr{constructor(e=10,t=5e3,s){this.maxAttempts=e,this.interval=t,this.logger=s}shouldReconnect(e){return e<this.maxAttempts}getDelay(){return this.interval}onMaxAttemptsReached(){const e=`Max reconnection attempts reached (${this.maxAttempts})`;this.logger?.error?.(e),console.error(`[RpcClient] ${e}`)}}class Zr{constructor(e=10,t=1e3,s=1e3,n=3e4,o){this.maxAttempts=e,this.baseDelay=t,this.increment=s,this.maxDelay=n,this.logger=o}shouldReconnect(e){return e<this.maxAttempts}getDelay(e){return Math.min(this.baseDelay+e*this.increment,this.maxDelay)}onMaxAttemptsReached(){const e=`Max reconnection attempts reached (${this.maxAttempts})`;this.logger?.error?.(e),console.error(`[RpcClient] ${e}`)}}class es{constructor(e=1e3,t=3e4){this.baseDelay=e,this.maxDelay=t}shouldReconnect(){return!0}getDelay(e){return Math.min(this.baseDelay*2**e,this.maxDelay)}onMaxAttemptsReached(){}}function ts(r){return typeof r=="string"?r:N.isBuffer(r)?r.toString():r instanceof ArrayBuffer?N.from(new Uint8Array(r)).toString():Array.isArray(r)?N.concat(r.map(e=>N.isBuffer(e)?e:N.from(e))).toString():N.from(r).toString()}function Je(r,e){try{const t=ts(r);return JSON.parse(t)}catch(t){e?.warn?.("parse message failed",t);return}}class Ge{constructor(e){this.options=e,this.reconnectStrategy=e.reconnectStrategy??new We}ws=null;reconnectTimer;reconnectAttempt=0;reconnectStrategy;connectionState="disconnected";log=$("RpcClient");async connect(){this.reconnectAttempt=0,await this.createConnection()}disconnect(){this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=void 0,this.ws?.close(),this.ws=null,this.connectionState="disconnected",this.log.info("Disconnected from server")}getConnectionState(){return this.connectionState}isConnected(){return this.connectionState==="connected"&&this.ws?.readyState===C.OPEN}sendEvent(e){if(!this.isConnected())return this.log.warn("Not connected, cannot send event"),!1;try{return this.send(e),!0}catch(t){return this.log.error("Send event error:",{error:t}),!1}}async createConnection(){return new Promise((e,t)=>{const s=new C(this.options.url);this.ws=s,this.connectionState="connecting",s.on("open",async()=>{try{const n=await this.options.getRegisterPayload();this.send({type:"register",nodeId:this.options.nodeId,payload:n}),this.connectionState="connected",this.reconnectAttempt=0,this.log.success(`Connected to server as ${this.options.nodeId}`),e()}catch(n){this.connectionState="disconnected",this.log.error("rpc client register failed",{error:n}),t(n)}}),s.on("message",n=>{this.handleMessage(n).catch(o=>{this.log.error("rpc client handle message failed",{error:o})})}),s.on("close",()=>{this.connectionState="disconnected",this.log.info("Connection closed, scheduling reconnect"),this.scheduleReconnect()}),s.on("error",n=>{this.log.warn("rpc client socket error",{error:n}),this.scheduleReconnect(),t(n)})})}async handleMessage(e){const t=Je(e,this.options.logger);if(t){if(Be(t)){this.send({type:"pong",timestamp:Date.now()});return}if(Ve(t)){await this.handleCommandEvent(t);return}Le(t)&&await this.handleRpcRequest(t)}}async handleCommandEvent(e){this.log.info(`Received command: ${e.action}`);try{if(this.options.handleCommand){const t=await this.options.handleCommand(e),s={type:"event",action:`${e.action}ed`,payload:{success:!0,result:t},id:e.id};this.send(s)}else{const t={type:"event",action:`${e.action}ed`,payload:{success:!1,error:"No command handler registered"},id:e.id};this.send(t)}}catch(t){const s={type:"event",action:`${e.action}ed`,payload:{success:!1,error:t instanceof Error?t.message:"Unknown error"},id:e.id};this.send(s)}}async handleRpcRequest(e){try{const t=await this.options.handleRequest(e);this.send({id:e.id,status:"success",result:t})}catch(t){this.send({id:e.id,status:"error",error:{code:"EXECUTION_ERROR",message:t instanceof Error?t.message:"Unknown error"}})}}send(e){this.ws?.readyState===C.OPEN&&this.ws.send(JSON.stringify(e))}scheduleReconnect(){if(this.reconnectTimer)return;if(!this.reconnectStrategy.shouldReconnect(this.reconnectAttempt)){this.reconnectStrategy.onMaxAttemptsReached(),this.log.error("Max reconnection attempts reached");return}const e=this.reconnectStrategy.getDelay(this.reconnectAttempt);this.reconnectAttempt++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=void 0,this.createConnection().catch(t=>{this.log.error("rpc client reconnect failed",{error:t}),this.scheduleReconnect()})},e),this.log.info(`Reconnecting in ${e}ms (attempt ${this.reconnectAttempt})`)}}class Qe{constructor(e){this.options=e,this.defaultCommandTimeout=e.commandTimeout??6e4}clients=new Map;pendingRequests=new Map;commandStatuses=new Map;wsToNode=new Map;heartbeatTimer;server;defaultCommandTimeout;log=$("RpcServer");start(){this.server||(this.server=new yt({port:this.options.port}),this.server.on("connection",(e,t)=>{const s=new URL(t.url??"/","ws://localhost").searchParams.get("token")??void 0;e.on("message",n=>{this.handleMessage(e,n,s).catch(o=>{this.log.error("rpc server handle message failed",{error:o})})}),e.on("close",()=>{this.handleClose(e)})}),this.startHeartbeat(),this.log.success("RpcServer started",{port:this.options.port}))}stop(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=void 0,this.pendingRequests.forEach(e=>clearTimeout(e.timer)),this.pendingRequests.clear(),this.clients.forEach(e=>e.close()),this.clients.clear(),this.wsToNode.clear(),this.server?.close(),this.server=void 0}async rpcCall(e,t,s,n=3e4){const o=this.clients.get(e);if(!o||o.readyState!==C.OPEN)throw new Error("Client not connected");if(this.options.authorize&&!await this.options.authorize(e,t))throw new Error("UNAUTHORIZED");const i=fe(),a={id:i,method:t,params:s,timeout:n};return new Promise((c,l)=>{const d=setTimeout(()=>{this.pendingRequests.delete(i),l(new Error(`RPC timeout: ${t}`))},n);this.pendingRequests.set(i,{resolve:c,reject:l,timer:d}),o.send(JSON.stringify(a))})}sendToNode(e,t){const s=this.clients.get(e);if(!s)return this.log.error(`Node not found: ${e}`),!1;if(s.readyState!==C.OPEN)return this.log.error(`Node not ready: ${e}`),!1;try{return s.send(JSON.stringify(t)),t.type==="command"&&t.id&&(this.commandStatuses.set(t.id,{commandId:t.id,nodeId:e,action:t.action,status:"pending",timestamp:Date.now()}),setTimeout(()=>{const n=this.commandStatuses.get(t.id);n&&n.status==="pending"&&(n.status="failed",n.error="Command timeout",this.log.warn(`Command ${t.id} (${t.action}) timeout`))},this.defaultCommandTimeout)),!0}catch(n){return this.log.error(`Send failed to ${e}:`,{error:n}),!1}}broadcast(e){const t=JSON.stringify(e);let s=0,n=0;for(const[o,i]of this.clients.entries())if(i.readyState===C.OPEN)try{i.send(t),s++}catch(a){this.log.error(`Broadcast failed to ${o}:`,{error:a}),n++}this.log.info("Broadcast completed",{total:this.clients.size,success:s,failed:n})}getOnlineNodes(){return Array.from(this.clients.keys())}isNodeOnline(e){const t=this.clients.get(e);return t!==void 0&&t.readyState===C.OPEN}getOnlineNodeCount(){let e=0;for(const t of this.clients.values())t.readyState===C.OPEN&&e++;return e}getRpcCommandStatus(e){return this.commandStatuses.get(e)}getAllRpcCommandStatuses(){return Array.from(this.commandStatuses.values())}clearOldStatuses(e=3e5){const t=Date.now();for(const[s,n]of this.commandStatuses.entries())n.status!=="pending"&&t-n.timestamp>e&&this.commandStatuses.delete(s)}async handleMessage(e,t,s){const n=Je(t,this.options.logger);if(n){if(qe(n)){await this.handleRegister(e,n,s);return}if(!_e(n)){if(ze(n)){await this.handleEventMessage(e,n);return}je(n)&&this.handleRpcResponse(n)}}}async handleEventMessage(e,t){const s=this.wsToNode.get(e);if(!s){this.log.warn("Received event from unregistered client");return}if(this.log.info(`Event from ${s}`,{action:t.action,payload:t.payload}),t.id){const n=this.commandStatuses.get(t.id);if(n){const o=t.payload;n.status=o?.success?"completed":"failed",n.result=o?.result,n.error=o?.error}}this.options.onEvent&&await this.options.onEvent(s,t)}async handleRegister(e,t,s){const{nodeId:n}=t;if(!n){e.close();return}if(!(!this.options.validateToken||await this.options.validateToken(s,n))){this.log.warn(`Node ${n} rejected: invalid token`),e.close();return}this.clients.set(n,e),this.wsToNode.set(e,n),this.log.success(`Node connected: ${n}`);const o=t.payload;o&&this.options.onRegister&&await this.options.onRegister(n,o)}handleRpcResponse(e){const t=this.pendingRequests.get(e.id);t&&(clearTimeout(t.timer),this.pendingRequests.delete(e.id),e.status==="success"?t.resolve(e.result):t.reject(new Error(e.error?.message??"RPC error")))}handleClose(e){const t=this.wsToNode.get(e);t&&(this.clients.delete(t),this.wsToNode.delete(e),this.log.info(`Node disconnected: ${t}`))}startHeartbeat(){const e=this.options.heartbeatInterval??3e4;this.heartbeatTimer=setInterval(()=>{this.clients.forEach((t,s)=>{if(t.readyState===C.OPEN){const n={type:"ping",timestamp:Date.now()};t.send(JSON.stringify(n))}else this.clients.delete(s)})},e)}}class rs{constructor(e){this.config=e}initialize(){const{rootWorkDir:e,runtimeDir:t,processDir:s}=this.setupDirectories();$r({workspaceRoot:e,enableFile:!0});const n=this.createLoggers(),o=this.createProcessManager(s,n.processLogger),i=this.createRuntimeContext(t,n.runtimeLogger),a=this.createNodeManager(i,t,n.runtimeLogger),c=this.createClientCollector(),{rpcServer:l,rpcClient:d}=this.createRpcComponents();return{runtimeContext:i,process:o,nodeManager:a,clientCollector:c,rpcServer:l,rpcClient:d,rootWorkDir:e,runtimeDir:t,processDir:s}}setupDirectories(){const e=this.config.workDir??u(J(),".frp-web"),t=this.config.runtime?.workDir??u(e,"runtime"),s=this.config.process?.workDir??u(e,"process");return v(e),v(t),v(s),{rootWorkDir:e,runtimeDir:t,processDir:s}}createLoggers(){const e=this.config.runtime?.logger??S.withTag("FrpRuntime"),t=this.config.process?.logger??S.withTag("FrpProcessManager");return{runtimeLogger:e,processLogger:t}}createProcessManager(e,t){return new Ue({mode:this.config.process?.mode??this.config.mode,version:this.config.process?.version,workDir:e,configPath:this.config.configPath,logger:t})}createRuntimeContext(e,t){return{id:this.config.runtime?.id??"default",mode:this.config.runtime?.mode??this.config.mode,workDir:e,platform:this.config.runtime?.platform??b.platform,clock:this.config.runtime?.clock,logger:t}}createNodeManager(e,t,s){if(this.config.mode!=="server")return;const n=u(t,"nodes");v(n);const o=new Ee(n);return new Ae(e,{heartbeatTimeout:9e4,logger:s},o)}createClientCollector(){if(this.config.mode==="client")return new De({heartbeatInterval:3e4,logger:S.withTag("ClientNodeCollector")})}createRpcComponents(){const e=this.config.rpc,t={};if(this.config.mode==="server"&&e?.serverPort&&(t.rpcServer=new Qe({port:e.serverPort,heartbeatInterval:e.serverHeartbeatInterval,validateToken:e.serverValidateToken,authorize:e.serverAuthorize,onRegister:e.serverOnRegister,onEvent:e.serverOnEvent,commandTimeout:e.serverCommandTimeout,logger:S.withTag("RpcServer")})),this.config.mode==="client"&&e?.clientUrl&&e.clientNodeId){const s=this.appendToken(e.clientUrl,e.clientToken);t.rpcClient=new Ge({url:s,nodeId:e.clientNodeId,getRegisterPayload:e.getRegisterPayload??(async()=>{throw new Error("rpc getRegisterPayload is required in client mode")}),handleRequest:e.handleRequest??(async()=>{}),logger:S.withTag("RpcClient")})}return t}appendToken(e,t){if(!t)return e;const s=new URL(e);return s.searchParams.set("token",t),s.toString()}}function ss(r,e){e&&(r.on("process:started",t=>{e(t)}),r.on("process:stopped",t=>{e(t)}),r.on("process:exited",t=>{e(t)}),r.on("process:error",t=>{e(t)}))}class ns{runtime;process;mode;eventSink;nodeManager;clientCollector;rpcServer;rpcClient;constructor(e){this.mode=e.mode;const t=new rs(e).initialize();this.process=t.process,this.nodeManager=t.nodeManager,this.clientCollector=t.clientCollector,this.rpcServer=t.rpcServer,this.rpcClient=t.rpcClient;const s=e.storage??new Te(u(t.runtimeDir,"snapshots"));this.runtime=new me(t.runtimeContext,{storage:s,commands:{},queries:{}});const n={process:this.process,nodeManager:this.nodeManager,rpcServer:this.rpcServer,mode:this.mode},o={process:this.process,nodeManager:this.nodeManager,runtime:this.runtime,mode:this.mode},i=or(n),a=Sr(o),c={...i,...e.commands??{}},l={...a,...e.queries??{}};Object.entries(c).forEach(([d,p])=>{this.runtime.registerCommand(d,p)}),Object.entries(l).forEach(([d,p])=>{this.runtime.registerQuery(d,p)}),this.eventSink=e.eventSink,ss(this.process,this.eventSink)}execute(e){return this.runtime.execute(e).finally(()=>{this.forwardEvents()})}query(e){return this.runtime.query(e).finally(()=>{this.forwardEvents()})}snapshot(){return this.runtime.snapshot()}drainEvents(){return this.runtime.drainEvents()}getProcessManager(){return this.process}getRuntime(){return this.runtime}getNodeManager(){return this.nodeManager}getClientCollector(){return this.clientCollector}getRpcServer(){return this.rpcServer}getRpcClient(){return this.rpcClient}async initialize(){this.nodeManager&&await this.nodeManager.initialize(),this.rpcServer&&this.rpcServer.start(),this.rpcClient&&await this.rpcClient.connect()}async dispose(){this.nodeManager&&this.nodeManager.dispose(),this.clientCollector&&this.clientCollector.stopHeartbeat(),this.rpcServer&&this.rpcServer.stop(),this.rpcClient&&this.rpcClient.disconnect()}forwardEvents(){this.eventSink&&this.runtime.drainEvents().forEach(e=>this.eventSink?.(e))}}export{ye as ARCH_MAP,T as BINARY_NAMES,te as BinaryNotFoundError,De as ClientNodeCollector,E as ConfigInvalidError,D as ConfigNotFoundError,_ as DEFAULT_PRESET_CONFIG,Rt as DownloadFailedError,We as ExponentialBackoffStrategy,re as ExtractionFailedError,Ee as FileNodeStorage,Te as FileSnapshotStorage,Yr as FixedIntervalStrategy,ns as FrpBridge,f as FrpBridgeErrorBase,Ue as FrpProcessManager,me as FrpRuntime,Q as GITHUB_OWNER,K as GITHUB_REPO,Re as GenericError,es as InfiniteReconnectStrategy,Zr as LinearBackoffStrategy,Xr as MiddlewarePipeline,m as ModeError,Ae as NodeManager,L as NotFoundError,we as OS_MAP,Nt as PlatformError,Ct as ProcessAlreadyRunningError,St as ProcessNotRunningError,Tt as ProcessStartFailedError,Ge as RpcClient,He as RpcMessageType,Qe as RpcServer,se as ValidationError,xt as VersionFetchError,Gr as authMiddleware,q as commandExists,Ir as configToToml,Y as downloadFile,v as ensureDir,Kr as errorHandlerMiddleware,Z as executeCommand,Se as findExistingVersion,be as getDownloadUrl,ve as getLatestVersion,Pe as getPlatform,Ve as isCommandMessage,ze as isEventMessage,ie as isEventRpcMessage,Wr as isNodeDeletePayload,Be as isPingMessage,_e as isPongMessage,qe as isRegisterMessage,Le as isRpcRequest,je as isRpcResponse,Vr as isTunnelAddPayload,zr as isTunnelDeletePayload,Ie as isValidToml,Jr as loggingMiddleware,ke as mergeConfigs,k as parse,Pt as parseToml,Oe as safeParse,Fe as saveFrpConfigFile,B as stringify,Qr as timeoutMiddleware,bt as toToml,Or as validatePresetConfig};
|