@undefineds.co/xpod 0.2.34 → 0.2.35

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.
Files changed (32) hide show
  1. package/dist/api/container/local.js +1 -5
  2. package/dist/api/container/local.js.map +1 -1
  3. package/dist/api/container/routes.js +16 -1
  4. package/dist/api/container/routes.js.map +1 -1
  5. package/dist/api/handlers/PodManagementHandler.d.ts +8 -0
  6. package/dist/api/handlers/PodManagementHandler.js +5 -3
  7. package/dist/api/handlers/PodManagementHandler.js.map +1 -1
  8. package/dist/api/handlers/WebIdProfileHandler.js +64 -6
  9. package/dist/api/handlers/WebIdProfileHandler.js.map +1 -1
  10. package/dist/api/runtime.js +11 -6
  11. package/dist/api/runtime.js.map +1 -1
  12. package/dist/components/components.jsonld +1 -0
  13. package/dist/components/context.jsonld +36 -0
  14. package/dist/edge/LocalNetworkManager.d.ts +2 -7
  15. package/dist/edge/LocalNetworkManager.js +7 -34
  16. package/dist/edge/LocalNetworkManager.js.map +1 -1
  17. package/dist/index.d.ts +2 -1
  18. package/dist/index.js +3 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/provision/LocalPodProvisioningService.d.ts +34 -0
  21. package/dist/provision/LocalPodProvisioningService.js +294 -0
  22. package/dist/provision/LocalPodProvisioningService.js.map +1 -0
  23. package/dist/provision/LocalPodProvisioningService.jsonld +142 -0
  24. package/dist/provision/ProvisionPodCreator.js +3 -4
  25. package/dist/provision/ProvisionPodCreator.js.map +1 -1
  26. package/dist/tunnel/LocalTunnelProvider.d.ts +2 -2
  27. package/dist/tunnel/LocalTunnelProvider.js +12 -14
  28. package/dist/tunnel/LocalTunnelProvider.js.map +1 -1
  29. package/dist/tunnel/TunnelProvider.d.ts +2 -0
  30. package/dist/tunnel/TunnelProvider.js.map +1 -1
  31. package/dist/tunnel/TunnelProvider.jsonld +4 -0
  32. package/package.json +1 -1
@@ -34,13 +34,15 @@ class LocalTunnelProvider {
34
34
  /**
35
35
  * Local 模式不需要 setup,直接返回基于 Token 的配置
36
36
  */
37
- async setup(_options) {
37
+ async setup(options) {
38
+ const localProtocol = options.localProtocol ?? 'http';
38
39
  // Local 模式下,Tunnel 已在 Cloudflare Dashboard 创建
39
40
  // 只需要返回一个包含 Token 的配置
40
41
  const config = {
41
42
  subdomain: 'local', // 占位符,实际域名由 Cloudflare Tunnel 配置决定
42
43
  provider: 'cloudflare',
43
44
  endpoint: '', // 实际 endpoint 由 Cloudflare Tunnel 配置决定
45
+ originUrl: `${localProtocol}://127.0.0.1:${options.localPort}`,
44
46
  tunnelToken: this.tunnelToken,
45
47
  };
46
48
  this.currentConfig = config;
@@ -48,25 +50,17 @@ class LocalTunnelProvider {
48
50
  }
49
51
  /**
50
52
  * 启动隧道客户端
51
- * 如果 cloudflared 已经在运行,则跳过启动
53
+ * 如果当前 provider 已经启动过进程,则跳过启动
52
54
  */
53
55
  async start(config) {
54
56
  // 如果没有传入 config,使用默认配置
55
- const actualConfig = config ?? {
57
+ const actualConfig = config ?? this.currentConfig ?? {
56
58
  subdomain: 'local',
57
59
  provider: 'cloudflare',
58
60
  endpoint: '',
61
+ originUrl: 'http://127.0.0.1:8080',
59
62
  tunnelToken: this.tunnelToken,
60
63
  };
61
- // 检测是否已经在运行
62
- const alreadyRunning = await this.isCloudflaredRunning();
63
- if (alreadyRunning) {
64
- this.logger.info('Already running externally, skipping start');
65
- this.status = { running: true, connected: true, endpoint: actualConfig.endpoint };
66
- this.currentConfig = actualConfig;
67
- this.managedByUs = false;
68
- return;
69
- }
70
64
  if (this.process) {
71
65
  this.logger.info('Already running (managed by us)');
72
66
  return;
@@ -78,14 +72,18 @@ class LocalTunnelProvider {
78
72
  this.logger.info('Starting cloudflared tunnel...');
79
73
  this.status = { running: true, connected: false };
80
74
  this.managedByUs = true;
81
- this.process = (0, node_child_process_1.spawn)(this.cloudflaredPath, [
75
+ const args = [
82
76
  'tunnel',
83
77
  '--protocol', 'http2',
84
78
  '--no-autoupdate',
85
79
  'run',
86
80
  '--token',
87
81
  token,
88
- ], {
82
+ ];
83
+ if (actualConfig.originUrl) {
84
+ args.push('--url', actualConfig.originUrl);
85
+ }
86
+ this.process = (0, node_child_process_1.spawn)(this.cloudflaredPath, args, {
89
87
  stdio: ['ignore', 'pipe', 'pipe'],
90
88
  });
91
89
  this.process.stdout?.on('data', (data) => {
@@ -1 +1 @@
1
- {"version":3,"file":"LocalTunnelProvider.js","sourceRoot":"","sources":["../../src/tunnel/LocalTunnelProvider.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn, execSync, type ChildProcess } from 'node:child_process';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type {\n TunnelProvider,\n TunnelConfig,\n TunnelSetupOptions,\n TunnelStatus,\n} from './TunnelProvider';\n\n/** cloudflared metrics 端口 */\nconst CLOUDFLARED_METRICS_PORT = 33863;\n\n/**\n * Local Tunnel Provider 配置\n * 仅用于启动 cloudflared,不涉及 Cloudflare API 操作\n */\nexport interface LocalTunnelProviderOptions {\n /** Cloudflare Tunnel Token (从环境变量 CLOUDFLARE_TUNNEL_TOKEN 获取) */\n tunnelToken: string;\n\n /** cloudflared 可执行文件路径 (默认 'cloudflared') */\n cloudflaredPath?: string;\n}\n\n/**\n * Local Tunnel Provider\n * \n * 专为 Local 模式设计的简化版 Tunnel Provider\n * 只负责启动 cloudflared,不涉及 Cloudflare API 操作(创建 Tunnel、DNS 记录等)\n * \n * 使用场景:\n * - 用户已在 Cloudflare Dashboard 创建好 Tunnel\n * - 用户通过环境变量 CLOUDFLARE_TUNNEL_TOKEN 传入 Token\n * - 服务启动时自动启动 cloudflared\n */\nexport class LocalTunnelProvider implements TunnelProvider {\n public readonly name = 'cloudflare-local';\n private readonly logger = getLoggerFor(this);\n\n private readonly tunnelToken: string;\n private readonly cloudflaredPath: string;\n\n private process: ChildProcess | null = null;\n private status: TunnelStatus = {\n running: false,\n connected: false,\n };\n private currentConfig: TunnelConfig | null = null;\n /** 标记隧道进程是否由我们启动和管理 */\n private managedByUs = false;\n\n constructor(options: LocalTunnelProviderOptions) {\n this.tunnelToken = options.tunnelToken;\n this.cloudflaredPath = options.cloudflaredPath ?? 'cloudflared';\n }\n\n /**\n * Local 模式不需要 setup,直接返回基于 Token 的配置\n */\n async setup(_options: TunnelSetupOptions): Promise<TunnelConfig> {\n // Local 模式下,Tunnel 已在 Cloudflare Dashboard 创建\n // 只需要返回一个包含 Token 的配置\n const config: TunnelConfig = {\n subdomain: 'local', // 占位符,实际域名由 Cloudflare Tunnel 配置决定\n provider: 'cloudflare',\n endpoint: '', // 实际 endpoint 由 Cloudflare Tunnel 配置决定\n tunnelToken: this.tunnelToken,\n };\n\n this.currentConfig = config;\n return config;\n }\n\n /**\n * 启动隧道客户端\n * 如果 cloudflared 已经在运行,则跳过启动\n */\n async start(config?: TunnelConfig): Promise<void> {\n // 如果没有传入 config,使用默认配置\n const actualConfig = config ?? {\n subdomain: 'local',\n provider: 'cloudflare' as const,\n endpoint: '',\n tunnelToken: this.tunnelToken,\n };\n\n // 检测是否已经在运行\n const alreadyRunning = await this.isCloudflaredRunning();\n if (alreadyRunning) {\n this.logger.info('Already running externally, skipping start');\n this.status = { running: true, connected: true, endpoint: actualConfig.endpoint };\n this.currentConfig = actualConfig;\n this.managedByUs = false;\n return;\n }\n\n if (this.process) {\n this.logger.info('Already running (managed by us)');\n return;\n }\n\n const token = actualConfig.tunnelToken ?? this.tunnelToken;\n if (!token) {\n throw new Error('Tunnel token is required');\n }\n\n this.logger.info('Starting cloudflared tunnel...');\n this.status = { running: true, connected: false };\n this.managedByUs = true;\n\n this.process = spawn(this.cloudflaredPath, [\n 'tunnel',\n '--protocol', 'http2',\n '--no-autoupdate',\n 'run',\n '--token',\n token,\n ], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n this.process.stdout?.on('data', (data: Buffer) => {\n const output = data.toString().trim();\n if (output) {\n this.logOutput(output);\n }\n\n // 检测连接成功\n if (output.includes('Connection registered') || output.includes('Registered tunnel connection')) {\n this.status.connected = true;\n this.status.lastHeartbeat = new Date();\n this.logger.info('Tunnel connected successfully');\n }\n });\n\n this.process.stderr?.on('data', (data: Buffer) => {\n const output = data.toString().trim();\n if (output) {\n // cloudflared 的正常日志也输出到 stderr\n this.logOutput(output);\n\n // 检测连接成功\n if (output.includes('Registered tunnel connection') || output.includes('Connection registered')) {\n this.status.connected = true;\n this.status.lastHeartbeat = new Date();\n // logOutput 已经打印了信息,这里不需要重复打印\n }\n\n // 检测错误\n if (output.includes('ERR') || output.includes('failed')) {\n this.status.error = output;\n }\n }\n });\n\n this.process.on('exit', (code) => {\n this.logger.info(`Process exited with code ${code}`);\n this.status = { running: false, connected: false };\n this.process = null;\n this.managedByUs = false;\n });\n\n this.process.on('error', (error) => {\n this.logger.error(`Failed to start: ${error.message}`);\n this.status = { running: false, connected: false, error: error.message };\n this.process = null;\n this.managedByUs = false;\n });\n\n this.currentConfig = actualConfig;\n this.status.endpoint = actualConfig.endpoint;\n\n // 等待连接建立\n await this.waitForConnection();\n }\n\n /**\n * 解析并打印 cloudflared 日志\n */\n private logOutput(raw: string): void {\n const lines = raw.split('\\n');\n for (const line of lines) {\n if (!line.trim()) continue;\n \n // 尝试去除时间戳 (例如: 2023-01-01T00:00:00Z INF ...)\n // 正则匹配 ISO 时间戳开头\n const match = line.match(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z\\s+(INF|WRN|ERR)\\s+(.*)$/);\n \n if (match) {\n const level = match[1];\n const content = match[2];\n \n switch (level) {\n case 'INF':\n this.logger.info(content);\n break;\n case 'WRN':\n this.logger.warn(content);\n break;\n case 'ERR':\n this.logger.error(content);\n break;\n default:\n this.logger.info(content);\n }\n } else {\n // 无法解析格式,原样输出\n this.logger.info(line);\n }\n }\n }\n\n /**\n * 停止隧道\n * 只有当隧道是由我们启动时才会停止\n */\n async stop(): Promise<void> {\n if (!this.managedByUs) {\n this.logger.info('Not managed by us, skipping stop');\n this.status = { running: false, connected: false };\n return;\n }\n\n if (this.process) {\n this.logger.info('Stopping tunnel...');\n this.process.kill('SIGTERM');\n\n // 等待进程退出\n await new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n this.logger.info('Force killing process...');\n this.process?.kill('SIGKILL');\n resolve();\n }, 5000);\n\n this.process?.on('exit', () => {\n clearTimeout(timeout);\n resolve();\n });\n });\n\n this.process = null;\n this.logger.info('Tunnel stopped');\n }\n\n this.status = { running: false, connected: false };\n this.managedByUs = false;\n }\n\n /**\n * 获取隧道状态\n */\n getStatus(): TunnelStatus {\n return { ...this.status };\n }\n\n /**\n * 获取公网端点\n */\n getEndpoint(): string | undefined {\n return this.currentConfig?.endpoint;\n }\n\n /**\n * Local 模式不需要 cleanup(不涉及 Cloudflare API 操作)\n */\n async cleanup(_config: TunnelConfig): Promise<void> {\n await this.stop();\n this.currentConfig = null;\n }\n\n // ============ 隧道检测方法 ============\n\n /**\n * 检测 cloudflared 是否已经在运行\n * 通过进程检测 + metrics 端口检测\n */\n async isCloudflaredRunning(): Promise<boolean> {\n // 方法1:检测进程\n if (this.isCloudflaredProcessRunning()) {\n return true;\n }\n\n // 方法2:检测 metrics 端口\n return await this.isMetricsPortOpen();\n }\n\n /**\n * 检测 cloudflared 进程是否存在\n */\n private isCloudflaredProcessRunning(): boolean {\n try {\n if (process.platform === 'win32') {\n execSync('tasklist /FI \"IMAGENAME eq cloudflared.exe\" | find \"cloudflared\"', { \n stdio: 'ignore' \n });\n } else {\n execSync('pgrep -x cloudflared', { stdio: 'ignore' });\n }\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * 检测 cloudflared metrics 端口是否开放\n */\n private async isMetricsPortOpen(): Promise<boolean> {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 2000);\n \n const res = await fetch(`http://localhost:${CLOUDFLARED_METRICS_PORT}/ready`, {\n signal: controller.signal,\n });\n \n clearTimeout(timeout);\n return res.ok;\n } catch {\n return false;\n }\n }\n\n /**\n * 等待连接建立\n */\n private async waitForConnection(timeout = 30000): Promise<void> {\n const start = Date.now();\n\n while (Date.now() - start < timeout) {\n if (this.status.connected) {\n return;\n }\n\n if (!this.status.running && this.status.error) {\n throw new Error(`Tunnel failed to start: ${this.status.error}`);\n }\n\n await new Promise((r) => setTimeout(r, 500));\n }\n\n // 超时不抛错,cloudflared 可能还在连接中\n this.logger.warn('Connection timeout, tunnel may still be connecting...');\n }\n\n /**\n * 获取是否由我们管理\n */\n isManagedByUs(): boolean {\n return this.managedByUs;\n }\n}\n"]}
1
+ {"version":3,"file":"LocalTunnelProvider.js","sourceRoot":"","sources":["../../src/tunnel/LocalTunnelProvider.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn, execSync, type ChildProcess } from 'node:child_process';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type {\n TunnelProvider,\n TunnelConfig,\n TunnelSetupOptions,\n TunnelStatus,\n} from './TunnelProvider';\n\n/** cloudflared metrics 端口 */\nconst CLOUDFLARED_METRICS_PORT = 33863;\n\n/**\n * Local Tunnel Provider 配置\n * 仅用于启动 cloudflared,不涉及 Cloudflare API 操作\n */\nexport interface LocalTunnelProviderOptions {\n /** Cloudflare Tunnel Token (从环境变量 CLOUDFLARE_TUNNEL_TOKEN 获取) */\n tunnelToken: string;\n\n /** cloudflared 可执行文件路径 (默认 'cloudflared') */\n cloudflaredPath?: string;\n}\n\n/**\n * Local Tunnel Provider\n * \n * 专为 Local 模式设计的简化版 Tunnel Provider\n * 只负责启动 cloudflared,不涉及 Cloudflare API 操作(创建 Tunnel、DNS 记录等)\n * \n * 使用场景:\n * - 用户已在 Cloudflare Dashboard 创建好 Tunnel\n * - 用户通过环境变量 CLOUDFLARE_TUNNEL_TOKEN 传入 Token\n * - 服务启动时自动启动 cloudflared\n */\nexport class LocalTunnelProvider implements TunnelProvider {\n public readonly name = 'cloudflare-local';\n private readonly logger = getLoggerFor(this);\n\n private readonly tunnelToken: string;\n private readonly cloudflaredPath: string;\n\n private process: ChildProcess | null = null;\n private status: TunnelStatus = {\n running: false,\n connected: false,\n };\n private currentConfig: TunnelConfig | null = null;\n /** 标记隧道进程是否由我们启动和管理 */\n private managedByUs = false;\n\n constructor(options: LocalTunnelProviderOptions) {\n this.tunnelToken = options.tunnelToken;\n this.cloudflaredPath = options.cloudflaredPath ?? 'cloudflared';\n }\n\n /**\n * Local 模式不需要 setup,直接返回基于 Token 的配置\n */\n async setup(options: TunnelSetupOptions): Promise<TunnelConfig> {\n const localProtocol = options.localProtocol ?? 'http';\n // Local 模式下,Tunnel 已在 Cloudflare Dashboard 创建\n // 只需要返回一个包含 Token 的配置\n const config: TunnelConfig = {\n subdomain: 'local', // 占位符,实际域名由 Cloudflare Tunnel 配置决定\n provider: 'cloudflare',\n endpoint: '', // 实际 endpoint 由 Cloudflare Tunnel 配置决定\n originUrl: `${localProtocol}://127.0.0.1:${options.localPort}`,\n tunnelToken: this.tunnelToken,\n };\n\n this.currentConfig = config;\n return config;\n }\n\n /**\n * 启动隧道客户端\n * 如果当前 provider 已经启动过进程,则跳过启动\n */\n async start(config?: TunnelConfig): Promise<void> {\n // 如果没有传入 config,使用默认配置\n const actualConfig = config ?? this.currentConfig ?? {\n subdomain: 'local',\n provider: 'cloudflare' as const,\n endpoint: '',\n originUrl: 'http://127.0.0.1:8080',\n tunnelToken: this.tunnelToken,\n };\n\n if (this.process) {\n this.logger.info('Already running (managed by us)');\n return;\n }\n\n const token = actualConfig.tunnelToken ?? this.tunnelToken;\n if (!token) {\n throw new Error('Tunnel token is required');\n }\n\n this.logger.info('Starting cloudflared tunnel...');\n this.status = { running: true, connected: false };\n this.managedByUs = true;\n\n const args = [\n 'tunnel',\n '--protocol', 'http2',\n '--no-autoupdate',\n 'run',\n '--token',\n token,\n ];\n if (actualConfig.originUrl) {\n args.push('--url', actualConfig.originUrl);\n }\n\n this.process = spawn(this.cloudflaredPath, args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n this.process.stdout?.on('data', (data: Buffer) => {\n const output = data.toString().trim();\n if (output) {\n this.logOutput(output);\n }\n\n // 检测连接成功\n if (output.includes('Connection registered') || output.includes('Registered tunnel connection')) {\n this.status.connected = true;\n this.status.lastHeartbeat = new Date();\n this.logger.info('Tunnel connected successfully');\n }\n });\n\n this.process.stderr?.on('data', (data: Buffer) => {\n const output = data.toString().trim();\n if (output) {\n // cloudflared 的正常日志也输出到 stderr\n this.logOutput(output);\n\n // 检测连接成功\n if (output.includes('Registered tunnel connection') || output.includes('Connection registered')) {\n this.status.connected = true;\n this.status.lastHeartbeat = new Date();\n // logOutput 已经打印了信息,这里不需要重复打印\n }\n\n // 检测错误\n if (output.includes('ERR') || output.includes('failed')) {\n this.status.error = output;\n }\n }\n });\n\n this.process.on('exit', (code) => {\n this.logger.info(`Process exited with code ${code}`);\n this.status = { running: false, connected: false };\n this.process = null;\n this.managedByUs = false;\n });\n\n this.process.on('error', (error) => {\n this.logger.error(`Failed to start: ${error.message}`);\n this.status = { running: false, connected: false, error: error.message };\n this.process = null;\n this.managedByUs = false;\n });\n\n this.currentConfig = actualConfig;\n this.status.endpoint = actualConfig.endpoint;\n\n // 等待连接建立\n await this.waitForConnection();\n }\n\n /**\n * 解析并打印 cloudflared 日志\n */\n private logOutput(raw: string): void {\n const lines = raw.split('\\n');\n for (const line of lines) {\n if (!line.trim()) continue;\n \n // 尝试去除时间戳 (例如: 2023-01-01T00:00:00Z INF ...)\n // 正则匹配 ISO 时间戳开头\n const match = line.match(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z\\s+(INF|WRN|ERR)\\s+(.*)$/);\n \n if (match) {\n const level = match[1];\n const content = match[2];\n \n switch (level) {\n case 'INF':\n this.logger.info(content);\n break;\n case 'WRN':\n this.logger.warn(content);\n break;\n case 'ERR':\n this.logger.error(content);\n break;\n default:\n this.logger.info(content);\n }\n } else {\n // 无法解析格式,原样输出\n this.logger.info(line);\n }\n }\n }\n\n /**\n * 停止隧道\n * 只有当隧道是由我们启动时才会停止\n */\n async stop(): Promise<void> {\n if (!this.managedByUs) {\n this.logger.info('Not managed by us, skipping stop');\n this.status = { running: false, connected: false };\n return;\n }\n\n if (this.process) {\n this.logger.info('Stopping tunnel...');\n this.process.kill('SIGTERM');\n\n // 等待进程退出\n await new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n this.logger.info('Force killing process...');\n this.process?.kill('SIGKILL');\n resolve();\n }, 5000);\n\n this.process?.on('exit', () => {\n clearTimeout(timeout);\n resolve();\n });\n });\n\n this.process = null;\n this.logger.info('Tunnel stopped');\n }\n\n this.status = { running: false, connected: false };\n this.managedByUs = false;\n }\n\n /**\n * 获取隧道状态\n */\n getStatus(): TunnelStatus {\n return { ...this.status };\n }\n\n /**\n * 获取公网端点\n */\n getEndpoint(): string | undefined {\n return this.currentConfig?.endpoint;\n }\n\n /**\n * Local 模式不需要 cleanup(不涉及 Cloudflare API 操作)\n */\n async cleanup(_config: TunnelConfig): Promise<void> {\n await this.stop();\n this.currentConfig = null;\n }\n\n // ============ 隧道检测方法 ============\n\n /**\n * 检测 cloudflared 是否已经在运行\n * 通过进程检测 + metrics 端口检测\n */\n async isCloudflaredRunning(): Promise<boolean> {\n // 方法1:检测进程\n if (this.isCloudflaredProcessRunning()) {\n return true;\n }\n\n // 方法2:检测 metrics 端口\n return await this.isMetricsPortOpen();\n }\n\n /**\n * 检测 cloudflared 进程是否存在\n */\n private isCloudflaredProcessRunning(): boolean {\n try {\n if (process.platform === 'win32') {\n execSync('tasklist /FI \"IMAGENAME eq cloudflared.exe\" | find \"cloudflared\"', { \n stdio: 'ignore' \n });\n } else {\n execSync('pgrep -x cloudflared', { stdio: 'ignore' });\n }\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * 检测 cloudflared metrics 端口是否开放\n */\n private async isMetricsPortOpen(): Promise<boolean> {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 2000);\n \n const res = await fetch(`http://localhost:${CLOUDFLARED_METRICS_PORT}/ready`, {\n signal: controller.signal,\n });\n \n clearTimeout(timeout);\n return res.ok;\n } catch {\n return false;\n }\n }\n\n /**\n * 等待连接建立\n */\n private async waitForConnection(timeout = 30000): Promise<void> {\n const start = Date.now();\n\n while (Date.now() - start < timeout) {\n if (this.status.connected) {\n return;\n }\n\n if (!this.status.running && this.status.error) {\n throw new Error(`Tunnel failed to start: ${this.status.error}`);\n }\n\n await new Promise((r) => setTimeout(r, 500));\n }\n\n // 超时不抛错,cloudflared 可能还在连接中\n this.logger.warn('Connection timeout, tunnel may still be connecting...');\n }\n\n /**\n * 获取是否由我们管理\n */\n isManagedByUs(): boolean {\n return this.managedByUs;\n }\n}\n"]}
@@ -15,6 +15,8 @@ export interface TunnelConfig {
15
15
  provider: 'cloudflare' | 'frp' | 'sakura-frp';
16
16
  /** 公网访问端点 (如 https://mynode.pods.undefieds.co) */
17
17
  endpoint: string;
18
+ /** 本地 origin 地址 (如 http://localhost:5737),供隧道客户端转发 */
19
+ originUrl?: string;
18
20
  /** Cloudflare Tunnel Token (cloudflare 专用) */
19
21
  tunnelToken?: string;
20
22
  /** Cloudflare Tunnel ID (cloudflare 专用) */
@@ -1 +1 @@
1
- {"version":3,"file":"TunnelProvider.js","sourceRoot":"","sources":["../../src/tunnel/TunnelProvider.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG","sourcesContent":["/**\n * Tunnel Provider Interface\n * \n * 抽象隧道服务,支持多种实现:\n * - CloudflareTunnelProvider: Cloudflare Tunnel(阶段 1)\n * - FrpTunnelProvider: FRP(阶段 2,自建或第三方)\n */\n\n/**\n * 隧道配置\n */\nexport interface TunnelConfig {\n /** 子域名 (如 mynode,完整域名为 mynode.pods.undefieds.co) */\n subdomain: string;\n\n /** 隧道类型 */\n provider: 'cloudflare' | 'frp' | 'sakura-frp';\n\n /** 公网访问端点 (如 https://mynode.pods.undefieds.co) */\n endpoint: string;\n\n /** Cloudflare Tunnel Token (cloudflare 专用) */\n tunnelToken?: string;\n\n /** Cloudflare Tunnel ID (cloudflare 专用) */\n tunnelId?: string;\n\n /** FRP 服务器地址 (frp 专用) */\n frpServer?: string;\n\n /** FRP 服务器端口 (frp 专用) */\n frpServerPort?: number;\n\n /** FRP Token (frp 专用) */\n frpToken?: string;\n\n /** FRP 远程端口 (frp 专用) */\n frpRemotePort?: number;\n}\n\n/**\n * 隧道设置参数\n */\nexport interface TunnelSetupOptions {\n /** 子域名 */\n subdomain: string;\n\n /** 本地服务端口 */\n localPort: number;\n\n /** 本地服务协议 */\n localProtocol?: 'http' | 'https';\n}\n\n/**\n * 隧道状态\n */\nexport interface TunnelStatus {\n /** 是否正在运行 */\n running: boolean;\n\n /** 连接状态 */\n connected: boolean;\n\n /** 公网端点 */\n endpoint?: string;\n\n /** 错误信息 */\n error?: string;\n\n /** 最后心跳时间 */\n lastHeartbeat?: Date;\n}\n\n/**\n * 隧道 Provider 接口\n */\nexport interface TunnelProvider {\n /** Provider 名称 */\n readonly name: string;\n\n /**\n * 初始化隧道(注册、分配资源)\n * @param options 设置参数\n * @returns 隧道配置\n */\n setup(options: TunnelSetupOptions): Promise<TunnelConfig>;\n\n /**\n * 启动隧道客户端\n * @param config 隧道配置\n */\n start(config: TunnelConfig): Promise<void>;\n\n /**\n * 停止隧道\n */\n stop(): Promise<void>;\n\n /**\n * 获取隧道状态\n */\n getStatus(): TunnelStatus;\n\n /**\n * 获取公网访问端点\n */\n getEndpoint(): string | undefined;\n\n /**\n * 清理资源(删除隧道、DNS 记录等)\n * @param config 隧道配置\n */\n cleanup(config: TunnelConfig): Promise<void>;\n}\n"]}
1
+ {"version":3,"file":"TunnelProvider.js","sourceRoot":"","sources":["../../src/tunnel/TunnelProvider.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG","sourcesContent":["/**\n * Tunnel Provider Interface\n * \n * 抽象隧道服务,支持多种实现:\n * - CloudflareTunnelProvider: Cloudflare Tunnel(阶段 1)\n * - FrpTunnelProvider: FRP(阶段 2,自建或第三方)\n */\n\n/**\n * 隧道配置\n */\nexport interface TunnelConfig {\n /** 子域名 (如 mynode,完整域名为 mynode.pods.undefieds.co) */\n subdomain: string;\n\n /** 隧道类型 */\n provider: 'cloudflare' | 'frp' | 'sakura-frp';\n\n /** 公网访问端点 (如 https://mynode.pods.undefieds.co) */\n endpoint: string;\n\n /** 本地 origin 地址 (如 http://localhost:5737),供隧道客户端转发 */\n originUrl?: string;\n\n /** Cloudflare Tunnel Token (cloudflare 专用) */\n tunnelToken?: string;\n\n /** Cloudflare Tunnel ID (cloudflare 专用) */\n tunnelId?: string;\n\n /** FRP 服务器地址 (frp 专用) */\n frpServer?: string;\n\n /** FRP 服务器端口 (frp 专用) */\n frpServerPort?: number;\n\n /** FRP Token (frp 专用) */\n frpToken?: string;\n\n /** FRP 远程端口 (frp 专用) */\n frpRemotePort?: number;\n}\n\n/**\n * 隧道设置参数\n */\nexport interface TunnelSetupOptions {\n /** 子域名 */\n subdomain: string;\n\n /** 本地服务端口 */\n localPort: number;\n\n /** 本地服务协议 */\n localProtocol?: 'http' | 'https';\n}\n\n/**\n * 隧道状态\n */\nexport interface TunnelStatus {\n /** 是否正在运行 */\n running: boolean;\n\n /** 连接状态 */\n connected: boolean;\n\n /** 公网端点 */\n endpoint?: string;\n\n /** 错误信息 */\n error?: string;\n\n /** 最后心跳时间 */\n lastHeartbeat?: Date;\n}\n\n/**\n * 隧道 Provider 接口\n */\nexport interface TunnelProvider {\n /** Provider 名称 */\n readonly name: string;\n\n /**\n * 初始化隧道(注册、分配资源)\n * @param options 设置参数\n * @returns 隧道配置\n */\n setup(options: TunnelSetupOptions): Promise<TunnelConfig>;\n\n /**\n * 启动隧道客户端\n * @param config 隧道配置\n */\n start(config: TunnelConfig): Promise<void>;\n\n /**\n * 停止隧道\n */\n stop(): Promise<void>;\n\n /**\n * 获取隧道状态\n */\n getStatus(): TunnelStatus;\n\n /**\n * 获取公网访问端点\n */\n getEndpoint(): string | undefined;\n\n /**\n * 清理资源(删除隧道、DNS 记录等)\n * @param config 隧道配置\n */\n cleanup(config: TunnelConfig): Promise<void>;\n}\n"]}
@@ -61,6 +61,10 @@
61
61
  "@id": "undefineds:dist/tunnel/TunnelProvider.jsonld#TunnelConfig__member_endpoint",
62
62
  "memberFieldName": "endpoint"
63
63
  },
64
+ {
65
+ "@id": "undefineds:dist/tunnel/TunnelProvider.jsonld#TunnelConfig__member_originUrl",
66
+ "memberFieldName": "originUrl"
67
+ },
64
68
  {
65
69
  "@id": "undefineds:dist/tunnel/TunnelProvider.jsonld#TunnelConfig__member_tunnelToken",
66
70
  "memberFieldName": "tunnelToken"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@undefineds.co/xpod",
3
- "version": "0.2.34",
3
+ "version": "0.2.35",
4
4
  "description": "Xpod is an extended Community Solid Server, offering rich-feature, production-level Solid Pod and identity management.",
5
5
  "repository": "https://github.com/undefinedsco/xpod",
6
6
  "author": "developer@undefineds.co",