@undefineds.co/xpod 0.3.31 → 0.3.32

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 (135) hide show
  1. package/dist/api/auth/AuthContext.d.ts +3 -2
  2. package/dist/api/auth/AuthContext.js +2 -1
  3. package/dist/api/auth/AuthContext.js.map +1 -1
  4. package/dist/api/auth/ClientCredentialsAuthenticator.d.ts +2 -12
  5. package/dist/api/auth/ClientCredentialsAuthenticator.js +4 -4
  6. package/dist/api/auth/ClientCredentialsAuthenticator.js.map +1 -1
  7. package/dist/api/auth/ServiceTokenAuthenticator.d.ts +2 -2
  8. package/dist/api/auth/ServiceTokenAuthenticator.js.map +1 -1
  9. package/dist/api/container/business-token.d.ts +1 -1
  10. package/dist/api/container/business-token.js +5 -1
  11. package/dist/api/container/business-token.js.map +1 -1
  12. package/dist/api/container/common.js +14 -10
  13. package/dist/api/container/common.js.map +1 -1
  14. package/dist/api/container/routes.js +16 -3
  15. package/dist/api/container/routes.js.map +1 -1
  16. package/dist/api/container/types.d.ts +2 -4
  17. package/dist/api/container/types.js.map +1 -1
  18. package/dist/api/handlers/ChatHandler.d.ts +1 -1
  19. package/dist/api/handlers/ChatHandler.js +1 -1
  20. package/dist/api/handlers/ChatHandler.js.map +1 -1
  21. package/dist/api/handlers/EdgeNodeSignalHandler.js +3 -1
  22. package/dist/api/handlers/EdgeNodeSignalHandler.js.map +1 -1
  23. package/dist/api/handlers/PodManagementHandler.d.ts +2 -0
  24. package/dist/api/handlers/PodManagementHandler.js +114 -12
  25. package/dist/api/handlers/PodManagementHandler.js.map +1 -1
  26. package/dist/api/handlers/ProvisionHandler.d.ts +27 -0
  27. package/dist/api/handlers/ProvisionHandler.js +339 -32
  28. package/dist/api/handlers/ProvisionHandler.js.map +1 -1
  29. package/dist/api/handlers/QuotaHandler.js +0 -12
  30. package/dist/api/handlers/QuotaHandler.js.map +1 -1
  31. package/dist/api/handlers/index.d.ts +0 -1
  32. package/dist/api/handlers/index.js +0 -1
  33. package/dist/api/handlers/index.js.map +1 -1
  34. package/dist/api/runtime.js +3 -3
  35. package/dist/api/runtime.js.map +1 -1
  36. package/dist/components/context.jsonld +12 -0
  37. package/dist/edge/EdgeNodeAgent.d.ts +1 -1
  38. package/dist/edge/EdgeNodeAgent.js +1 -1
  39. package/dist/edge/EdgeNodeAgent.js.map +1 -1
  40. package/dist/edge/EdgeNodeDnsCoordinator.d.ts +1 -0
  41. package/dist/edge/EdgeNodeDnsCoordinator.js +9 -3
  42. package/dist/edge/EdgeNodeDnsCoordinator.js.map +1 -1
  43. package/dist/edge/EdgeNodeDnsCoordinator.jsonld +4 -0
  44. package/dist/edge/EdgeNodeHealthProbeService.d.ts +3 -0
  45. package/dist/edge/EdgeNodeHealthProbeService.js +22 -2
  46. package/dist/edge/EdgeNodeHealthProbeService.js.map +1 -1
  47. package/dist/edge/EdgeNodeHealthProbeService.jsonld +12 -0
  48. package/dist/http/ClusterIngressRouter.js +6 -3
  49. package/dist/http/ClusterIngressRouter.js.map +1 -1
  50. package/dist/http/ClusterWebSocketConfigurator.js +6 -2
  51. package/dist/http/ClusterWebSocketConfigurator.js.map +1 -1
  52. package/dist/http/EdgeNodeDirectDebugHttpHandler.d.ts +2 -0
  53. package/dist/http/EdgeNodeDirectDebugHttpHandler.js +18 -3
  54. package/dist/http/EdgeNodeDirectDebugHttpHandler.js.map +1 -1
  55. package/dist/http/EdgeNodeDirectDebugHttpHandler.jsonld +8 -0
  56. package/dist/http/EdgeNodeProxyHttpHandler.js +6 -2
  57. package/dist/http/EdgeNodeProxyHttpHandler.js.map +1 -1
  58. package/dist/http/cluster/PodMigrationHttpHandler.d.ts +2 -2
  59. package/dist/http/cluster/PodMigrationHttpHandler.js +2 -2
  60. package/dist/http/cluster/PodMigrationHttpHandler.js.map +1 -1
  61. package/dist/http/quota/QuotaAdminHttpHandler.js +27 -21
  62. package/dist/http/quota/QuotaAdminHttpHandler.js.map +1 -1
  63. package/dist/identity/drizzle/AccountRepository.d.ts +4 -22
  64. package/dist/identity/drizzle/AccountRepository.js +9 -113
  65. package/dist/identity/drizzle/AccountRepository.js.map +1 -1
  66. package/dist/identity/drizzle/AccountRoleRepository.d.ts +5 -5
  67. package/dist/identity/drizzle/AccountRoleRepository.js +204 -97
  68. package/dist/identity/drizzle/AccountRoleRepository.js.map +1 -1
  69. package/dist/identity/drizzle/DdnsRepository.d.ts +5 -20
  70. package/dist/identity/drizzle/DdnsRepository.js +13 -49
  71. package/dist/identity/drizzle/DdnsRepository.js.map +1 -1
  72. package/dist/identity/drizzle/EdgeNodeRepository.d.ts +13 -6
  73. package/dist/identity/drizzle/EdgeNodeRepository.js +167 -66
  74. package/dist/identity/drizzle/EdgeNodeRepository.js.map +1 -1
  75. package/dist/identity/drizzle/PodLookupRepository.d.ts +7 -36
  76. package/dist/identity/drizzle/PodLookupRepository.js +103 -126
  77. package/dist/identity/drizzle/PodLookupRepository.js.map +1 -1
  78. package/dist/identity/drizzle/ServiceTokenRepository.d.ts +13 -1
  79. package/dist/identity/drizzle/ServiceTokenRepository.js +7 -0
  80. package/dist/identity/drizzle/ServiceTokenRepository.js.map +1 -1
  81. package/dist/identity/drizzle/db.d.ts +2 -1
  82. package/dist/identity/drizzle/db.js +173 -297
  83. package/dist/identity/drizzle/db.js.map +1 -1
  84. package/dist/identity/drizzle/schema.pg.d.ts +3 -11
  85. package/dist/identity/drizzle/schema.pg.js +10 -45
  86. package/dist/identity/drizzle/schema.pg.js.map +1 -1
  87. package/dist/identity/drizzle/schema.sqlite.d.ts +88 -531
  88. package/dist/identity/drizzle/schema.sqlite.js +13 -46
  89. package/dist/identity/drizzle/schema.sqlite.js.map +1 -1
  90. package/dist/identity/oidc/ScopedPickWebIdHandler.d.ts +3 -0
  91. package/dist/identity/oidc/ScopedPickWebIdHandler.js +18 -6
  92. package/dist/identity/oidc/ScopedPickWebIdHandler.js.map +1 -1
  93. package/dist/identity/oidc/ScopedPickWebIdHandler.jsonld +22 -0
  94. package/dist/provision/ProvisionCodeCodec.js +10 -1
  95. package/dist/provision/ProvisionCodeCodec.js.map +1 -1
  96. package/dist/provision/ProvisionPodCreator.d.ts +8 -2
  97. package/dist/provision/ProvisionPodCreator.js +134 -41
  98. package/dist/provision/ProvisionPodCreator.js.map +1 -1
  99. package/dist/provision/ProvisionPodCreator.jsonld +38 -3
  100. package/dist/quota/DrizzleQuotaService.d.ts +0 -4
  101. package/dist/quota/DrizzleQuotaService.js +1 -21
  102. package/dist/quota/DrizzleQuotaService.js.map +1 -1
  103. package/dist/quota/DrizzleQuotaService.jsonld +0 -16
  104. package/dist/quota/NoopQuotaService.d.ts +0 -4
  105. package/dist/quota/NoopQuotaService.js +0 -8
  106. package/dist/quota/NoopQuotaService.js.map +1 -1
  107. package/dist/quota/NoopQuotaService.jsonld +0 -16
  108. package/dist/quota/QuotaService.d.ts +0 -4
  109. package/dist/quota/QuotaService.js.map +1 -1
  110. package/dist/quota/QuotaService.jsonld +0 -16
  111. package/dist/service/EdgeNodeSignalClient.d.ts +0 -2
  112. package/dist/service/EdgeNodeSignalClient.js +0 -4
  113. package/dist/service/EdgeNodeSignalClient.js.map +1 -1
  114. package/dist/service/PodMigrationService.d.ts +2 -2
  115. package/dist/service/PodMigrationService.js +4 -4
  116. package/dist/service/PodMigrationService.js.map +1 -1
  117. package/dist/setup/LocalSetupServiceTokenRepository.d.ts +22 -0
  118. package/dist/setup/LocalSetupServiceTokenRepository.js +68 -0
  119. package/dist/setup/LocalSetupServiceTokenRepository.js.map +1 -0
  120. package/dist/storage/quota/PerAccountQuotaStrategy.js +2 -2
  121. package/dist/storage/quota/PerAccountQuotaStrategy.js.map +1 -1
  122. package/dist/storage/quota/UsageRepository.d.ts +10 -32
  123. package/dist/storage/quota/UsageRepository.js +84 -281
  124. package/dist/storage/quota/UsageRepository.js.map +1 -1
  125. package/dist/subdomain/SubdomainService.d.ts +1 -1
  126. package/dist/subdomain/SubdomainService.js +1 -1
  127. package/dist/subdomain/SubdomainService.js.map +1 -1
  128. package/dist/subdomain/SubdomainService.jsonld +1 -1
  129. package/package.json +1 -1
  130. package/dist/api/handlers/ApiKeyHandler.d.ts +0 -15
  131. package/dist/api/handlers/ApiKeyHandler.js +0 -153
  132. package/dist/api/handlers/ApiKeyHandler.js.map +0 -1
  133. package/dist/api/store/DrizzleClientCredentialsStore.d.ts +0 -51
  134. package/dist/api/store/DrizzleClientCredentialsStore.js +0 -115
  135. package/dist/api/store/DrizzleClientCredentialsStore.js.map +0 -1
@@ -63,6 +63,6 @@ export interface ChatHandlerOptions {
63
63
  * POST /v1/messages - Create a message (Anthropic/OpenAI Threads compatible)
64
64
  * GET /v1/models - List available models
65
65
  *
66
- * Supports both Solid Token (frontend) and API Key (third-party)
66
+ * Supports both Solid Token (frontend) and CSS client credentials (third-party)
67
67
  */
68
68
  export declare function registerChatRoutes(server: ApiServer, options: ChatHandlerOptions): void;
@@ -11,7 +11,7 @@ const AuthContext_1 = require("../auth/AuthContext");
11
11
  * POST /v1/messages - Create a message (Anthropic/OpenAI Threads compatible)
12
12
  * GET /v1/models - List available models
13
13
  *
14
- * Supports both Solid Token (frontend) and API Key (third-party)
14
+ * Supports both Solid Token (frontend) and CSS client credentials (third-party)
15
15
  */
16
16
  function registerChatRoutes(server, options) {
17
17
  const logger = (0, global_logger_factory_1.getLoggerFor)('ChatHandler');
@@ -1 +1 @@
1
- {"version":3,"file":"ChatHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/ChatHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport type { ApiServer } from '../ApiServer';\nimport type { AuthContext } from '../auth/AuthContext';\nimport { getWebId, getAccountId, getDisplayName } from '../auth/AuthContext';\n\n/**\n * Chat completion request (OpenAI-compatible)\n */\nexport interface ChatCompletionRequest {\n model: string;\n messages: Array<Record<string, unknown> & {\n role: string;\n content?: unknown;\n }>;\n temperature?: number;\n max_tokens?: number;\n stream?: boolean;\n tools?: unknown[];\n tool_choice?: unknown;\n [key: string]: unknown;\n}\n\n/**\n * Chat completion response (OpenAI-compatible)\n */\nexport interface ChatCompletionResponse {\n id: string;\n object: 'chat.completion';\n created: number;\n model: string;\n choices: Array<{\n index: number;\n message: {\n role: 'assistant';\n content: string | null;\n tool_calls?: unknown[];\n };\n finish_reason: 'stop' | 'length' | 'content_filter' | 'tool_calls' | null;\n }>;\n usage: {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n };\n}\n\nexport interface ChatHandlerOptions {\n /**\n * Backend chat service to delegate to (e.g., OpenAI, local model)\n */\n chatService?: {\n complete(request: ChatCompletionRequest, auth: AuthContext): Promise<ChatCompletionResponse>;\n stream(request: ChatCompletionRequest, auth: AuthContext): Promise<any>;\n responses?(body: any, auth: AuthContext): Promise<any>;\n messages?(body: any, auth: AuthContext): Promise<any>;\n listModels(auth?: AuthContext): Promise<any[]>;\n };\n /**\n * Pod base URL for storage\n */\n podBaseUrl?: string;\n}\n\n/**\n * Handler for chat completions API (OpenAI-compatible)\n * \n * POST /v1/chat/completions - Create a chat completion\n * POST /v1/responses - Create a response (OpenAI Responses API)\n * POST /v1/messages - Create a message (Anthropic/OpenAI Threads compatible)\n * GET /v1/models - List available models\n * \n * Supports both Solid Token (frontend) and API Key (third-party)\n */\nexport function registerChatRoutes(server: ApiServer, options: ChatHandlerOptions): void {\n const logger = getLoggerFor('ChatHandler');\n const chatService = options.chatService;\n\n // POST /api/chat/completions\n server.post('/v1/chat/completions', async (request, response, _params) => {\n const auth = request.auth!;\n const body = await readJsonBody(request);\n\n if (!body || typeof body !== 'object') {\n sendJson(response, 400, {\n error: {\n message: 'Request body must be a JSON object',\n type: 'invalid_request_error',\n code: 'invalid_body',\n },\n });\n return;\n }\n\n const payload = body as Record<string, unknown>;\n\n // Validate required fields\n if (!payload.model || typeof payload.model !== 'string') {\n sendJson(response, 400, {\n error: {\n message: 'model is required',\n type: 'invalid_request_error',\n code: 'missing_model',\n },\n });\n return;\n }\n\n if (!Array.isArray(payload.messages) || payload.messages.length === 0) {\n sendJson(response, 400, {\n error: {\n message: 'messages array is required and must not be empty',\n type: 'invalid_request_error',\n code: 'missing_messages',\n },\n });\n return;\n }\n\n // Get user identifier for rate limiting / logging\n const userId = getWebId(auth) ?? getAccountId(auth) ?? 'anonymous';\n const displayName = getDisplayName(auth) || userId;\n const accountId = getAccountId(auth);\n\n\n // Check if service is available\n if (!chatService) {\n sendJson(response, 503, {\n error: {\n message: 'Chat service is not configured',\n type: 'service_unavailable',\n code: 'service_not_configured',\n },\n });\n return;\n }\n\n try {\n const messages = payload.messages as ChatCompletionRequest['messages'];\n const completionRequest: ChatCompletionRequest = {\n ...payload,\n model: payload.model as string,\n messages,\n };\n\n // Handle streaming\n if (completionRequest.stream === true) {\n const streamResult = await chatService.stream(completionRequest, auth);\n // Vercel AI SDK v6 uses toTextStreamResponse (not toDataStreamResponse)\n const webResponse = streamResult.toTextStreamResponse();\n\n // Copy headers (Content-Type: text/plain; charset=utf-8, X-Vercel-AI-Data-Stream: v1)\n webResponse.headers.forEach((value: string, key: string) => {\n response.setHeader(key, value);\n });\n response.statusCode = webResponse.status;\n\n // Pipe Web Stream to Node Response\n if (webResponse.body) {\n const reader = webResponse.body.getReader();\n const pump = async () => {\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n response.write(value);\n }\n } catch (e) {\n logger.error(`Stream write error: ${e}`);\n } finally {\n response.end();\n }\n };\n pump();\n } else {\n response.end();\n }\n return;\n }\n\n logger.info(`Chat completion request from ${displayName} (acc: ${accountId}), model: ${completionRequest.model}`);\n\n const result = await chatService.complete(completionRequest, auth);\n sendJson(response, 200, result);\n } catch (error: any) {\n if (error?.code === 'model_not_configured') {\n sendJson(response, 400, {\n error: {\n message: error.message || 'Model is not configured',\n type: 'invalid_request_error',\n code: 'model_not_configured',\n },\n });\n return;\n }\n logger.error(`Chat completion error: ${error}`);\n sendJson(response, 500, {\n error: {\n message: error.message || 'Internal server error',\n stack: error.stack,\n type: 'internal_error',\n code: 'internal_error',\n },\n });\n }\n });\n\n // POST /v1/responses - Create a response\n server.post('/v1/responses', async (request, response, _params) => {\n const auth = request.auth!;\n const body = await readJsonBody(request);\n const userId = getWebId(auth) ?? getAccountId(auth) ?? 'anonymous';\n const displayName = getDisplayName(auth) || userId;\n const accountId = getAccountId(auth);\n\n if (!chatService || !chatService.responses) {\n sendJson(response, 501, { error: 'Responses API not implemented or configured' });\n return;\n }\n\n try {\n logger.info(`Responses API request from ${displayName} (acc: ${accountId})`);\n const result = await chatService.responses(body, auth);\n sendJson(response, 200, result);\n } catch (error: any) {\n logger.error(`Responses API error: ${error}`);\n sendJson(response, 500, { error: error.message || 'Internal server error' });\n }\n });\n\n // POST /v1/messages - Create a message\n server.post('/v1/messages', async (request, response, _params) => {\n const auth = request.auth!;\n const body = await readJsonBody(request);\n const userId = getWebId(auth) ?? getAccountId(auth) ?? 'anonymous';\n const displayName = getDisplayName(auth) || userId;\n const accountId = getAccountId(auth);\n\n if (!chatService || !chatService.messages) {\n sendJson(response, 501, { error: 'Messages API not implemented or configured' });\n return;\n }\n\n try {\n logger.info(`Messages API request from ${displayName} (acc: ${accountId})`);\n const result = await chatService.messages(body, auth);\n sendJson(response, 200, result);\n } catch (error: any) {\n logger.error(`Messages API error: ${error}`);\n sendJson(response, 500, { error: error.message || 'Internal server error' });\n }\n });\n\n // GET /v1/models - List available models (OpenAI-compatible)\n server.get('/v1/models', async (request, response, _params) => {\n if (!chatService) {\n sendJson(response, 503, { error: 'Chat service not configured' });\n return;\n }\n\n try {\n const auth = request.auth;\n const models = await chatService.listModels(auth);\n sendJson(response, 200, {\n object: 'list',\n data: models,\n });\n } catch (error) {\n logger.error(`Failed to list models: ${error}`);\n sendJson(response, 500, { error: 'Failed to list models' });\n }\n });\n}\n\nasync function readJsonBody(request: AuthenticatedRequest): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(undefined);\n }\n });\n request.on('error', reject);\n });\n}\n\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n"]}
1
+ {"version":3,"file":"ChatHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/ChatHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport type { ApiServer } from '../ApiServer';\nimport type { AuthContext } from '../auth/AuthContext';\nimport { getWebId, getAccountId, getDisplayName } from '../auth/AuthContext';\n\n/**\n * Chat completion request (OpenAI-compatible)\n */\nexport interface ChatCompletionRequest {\n model: string;\n messages: Array<Record<string, unknown> & {\n role: string;\n content?: unknown;\n }>;\n temperature?: number;\n max_tokens?: number;\n stream?: boolean;\n tools?: unknown[];\n tool_choice?: unknown;\n [key: string]: unknown;\n}\n\n/**\n * Chat completion response (OpenAI-compatible)\n */\nexport interface ChatCompletionResponse {\n id: string;\n object: 'chat.completion';\n created: number;\n model: string;\n choices: Array<{\n index: number;\n message: {\n role: 'assistant';\n content: string | null;\n tool_calls?: unknown[];\n };\n finish_reason: 'stop' | 'length' | 'content_filter' | 'tool_calls' | null;\n }>;\n usage: {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n };\n}\n\nexport interface ChatHandlerOptions {\n /**\n * Backend chat service to delegate to (e.g., OpenAI, local model)\n */\n chatService?: {\n complete(request: ChatCompletionRequest, auth: AuthContext): Promise<ChatCompletionResponse>;\n stream(request: ChatCompletionRequest, auth: AuthContext): Promise<any>;\n responses?(body: any, auth: AuthContext): Promise<any>;\n messages?(body: any, auth: AuthContext): Promise<any>;\n listModels(auth?: AuthContext): Promise<any[]>;\n };\n /**\n * Pod base URL for storage\n */\n podBaseUrl?: string;\n}\n\n/**\n * Handler for chat completions API (OpenAI-compatible)\n * \n * POST /v1/chat/completions - Create a chat completion\n * POST /v1/responses - Create a response (OpenAI Responses API)\n * POST /v1/messages - Create a message (Anthropic/OpenAI Threads compatible)\n * GET /v1/models - List available models\n * \n * Supports both Solid Token (frontend) and CSS client credentials (third-party)\n */\nexport function registerChatRoutes(server: ApiServer, options: ChatHandlerOptions): void {\n const logger = getLoggerFor('ChatHandler');\n const chatService = options.chatService;\n\n // POST /api/chat/completions\n server.post('/v1/chat/completions', async (request, response, _params) => {\n const auth = request.auth!;\n const body = await readJsonBody(request);\n\n if (!body || typeof body !== 'object') {\n sendJson(response, 400, {\n error: {\n message: 'Request body must be a JSON object',\n type: 'invalid_request_error',\n code: 'invalid_body',\n },\n });\n return;\n }\n\n const payload = body as Record<string, unknown>;\n\n // Validate required fields\n if (!payload.model || typeof payload.model !== 'string') {\n sendJson(response, 400, {\n error: {\n message: 'model is required',\n type: 'invalid_request_error',\n code: 'missing_model',\n },\n });\n return;\n }\n\n if (!Array.isArray(payload.messages) || payload.messages.length === 0) {\n sendJson(response, 400, {\n error: {\n message: 'messages array is required and must not be empty',\n type: 'invalid_request_error',\n code: 'missing_messages',\n },\n });\n return;\n }\n\n // Get user identifier for rate limiting / logging\n const userId = getWebId(auth) ?? getAccountId(auth) ?? 'anonymous';\n const displayName = getDisplayName(auth) || userId;\n const accountId = getAccountId(auth);\n\n\n // Check if service is available\n if (!chatService) {\n sendJson(response, 503, {\n error: {\n message: 'Chat service is not configured',\n type: 'service_unavailable',\n code: 'service_not_configured',\n },\n });\n return;\n }\n\n try {\n const messages = payload.messages as ChatCompletionRequest['messages'];\n const completionRequest: ChatCompletionRequest = {\n ...payload,\n model: payload.model as string,\n messages,\n };\n\n // Handle streaming\n if (completionRequest.stream === true) {\n const streamResult = await chatService.stream(completionRequest, auth);\n // Vercel AI SDK v6 uses toTextStreamResponse (not toDataStreamResponse)\n const webResponse = streamResult.toTextStreamResponse();\n\n // Copy headers (Content-Type: text/plain; charset=utf-8, X-Vercel-AI-Data-Stream: v1)\n webResponse.headers.forEach((value: string, key: string) => {\n response.setHeader(key, value);\n });\n response.statusCode = webResponse.status;\n\n // Pipe Web Stream to Node Response\n if (webResponse.body) {\n const reader = webResponse.body.getReader();\n const pump = async () => {\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n response.write(value);\n }\n } catch (e) {\n logger.error(`Stream write error: ${e}`);\n } finally {\n response.end();\n }\n };\n pump();\n } else {\n response.end();\n }\n return;\n }\n\n logger.info(`Chat completion request from ${displayName} (acc: ${accountId}), model: ${completionRequest.model}`);\n\n const result = await chatService.complete(completionRequest, auth);\n sendJson(response, 200, result);\n } catch (error: any) {\n if (error?.code === 'model_not_configured') {\n sendJson(response, 400, {\n error: {\n message: error.message || 'Model is not configured',\n type: 'invalid_request_error',\n code: 'model_not_configured',\n },\n });\n return;\n }\n logger.error(`Chat completion error: ${error}`);\n sendJson(response, 500, {\n error: {\n message: error.message || 'Internal server error',\n stack: error.stack,\n type: 'internal_error',\n code: 'internal_error',\n },\n });\n }\n });\n\n // POST /v1/responses - Create a response\n server.post('/v1/responses', async (request, response, _params) => {\n const auth = request.auth!;\n const body = await readJsonBody(request);\n const userId = getWebId(auth) ?? getAccountId(auth) ?? 'anonymous';\n const displayName = getDisplayName(auth) || userId;\n const accountId = getAccountId(auth);\n\n if (!chatService || !chatService.responses) {\n sendJson(response, 501, { error: 'Responses API not implemented or configured' });\n return;\n }\n\n try {\n logger.info(`Responses API request from ${displayName} (acc: ${accountId})`);\n const result = await chatService.responses(body, auth);\n sendJson(response, 200, result);\n } catch (error: any) {\n logger.error(`Responses API error: ${error}`);\n sendJson(response, 500, { error: error.message || 'Internal server error' });\n }\n });\n\n // POST /v1/messages - Create a message\n server.post('/v1/messages', async (request, response, _params) => {\n const auth = request.auth!;\n const body = await readJsonBody(request);\n const userId = getWebId(auth) ?? getAccountId(auth) ?? 'anonymous';\n const displayName = getDisplayName(auth) || userId;\n const accountId = getAccountId(auth);\n\n if (!chatService || !chatService.messages) {\n sendJson(response, 501, { error: 'Messages API not implemented or configured' });\n return;\n }\n\n try {\n logger.info(`Messages API request from ${displayName} (acc: ${accountId})`);\n const result = await chatService.messages(body, auth);\n sendJson(response, 200, result);\n } catch (error: any) {\n logger.error(`Messages API error: ${error}`);\n sendJson(response, 500, { error: error.message || 'Internal server error' });\n }\n });\n\n // GET /v1/models - List available models (OpenAI-compatible)\n server.get('/v1/models', async (request, response, _params) => {\n if (!chatService) {\n sendJson(response, 503, { error: 'Chat service not configured' });\n return;\n }\n\n try {\n const auth = request.auth;\n const models = await chatService.listModels(auth);\n sendJson(response, 200, {\n object: 'list',\n data: models,\n });\n } catch (error) {\n logger.error(`Failed to list models: ${error}`);\n sendJson(response, 500, { error: 'Failed to list models' });\n }\n });\n}\n\nasync function readJsonBody(request: AuthenticatedRequest): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(undefined);\n }\n });\n request.on('error', reject);\n });\n}\n\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n"]}
@@ -106,13 +106,15 @@ function mergeMetadata(previous, payload, now) {
106
106
  }
107
107
  };
108
108
  copyIfPresent('baseUrl');
109
- copyIfPresent('publicAddress');
110
109
  copyIfPresent('hostname');
111
110
  copyIfPresent('ipv4');
112
111
  copyIfPresent('ipv6');
113
112
  copyIfPresent('version');
114
113
  copyIfPresent('status');
115
114
  copyIfPresent('capabilities');
115
+ copyIfPresent('directCandidates');
116
+ copyIfPresent('tunnel');
117
+ copyIfPresent('certificate');
116
118
  copyIfPresent('metrics');
117
119
  return next;
118
120
  }
@@ -1 +1 @@
1
- {"version":3,"file":"EdgeNodeSignalHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/EdgeNodeSignalHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport type { ApiServer } from '../ApiServer';\nimport type { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';\nimport type { EdgeNodeDnsCoordinator } from '../../edge/EdgeNodeDnsCoordinator';\nimport type { EdgeNodeHealthProbeService } from '../../edge/EdgeNodeHealthProbeService';\nimport { isNodeAuth, getNodeId } from '../auth/AuthContext';\n\nexport interface EdgeNodeSignalHandlerOptions {\n repository: EdgeNodeRepository;\n dnsCoordinator?: EdgeNodeDnsCoordinator;\n healthProbeService?: EdgeNodeHealthProbeService;\n}\n\n/**\n * Handler for edge node signaling API\n *\n * POST /v1/signal - Edge node heartbeat/signal\n *\n * Requires API authentication and a nodeId in the request body.\n */\nexport function registerEdgeNodeSignalRoutes(server: ApiServer, options: EdgeNodeSignalHandlerOptions): void {\n const logger = getLoggerFor('EdgeNodeSignalHandler');\n const repo = options.repository;\n const { dnsCoordinator, healthProbeService } = options;\n\n // POST /v1/signal - authenticated via nodeToken, API key, or Solid token\n server.post('/v1/signal', async (request, response, _params) => {\n const auth = request.auth;\n if (!auth) {\n sendJson(response, 401, { error: 'Authentication required' });\n return;\n }\n\n const body = await readJsonBody(request);\n if (!body || typeof body !== 'object') {\n sendJson(response, 400, { error: 'Request body must be a JSON object' });\n return;\n }\n const payload = body as Record<string, unknown>;\n\n // Resolve nodeId based on auth type\n let nodeId: string;\n\n if (isNodeAuth(auth)) {\n // nodeToken 认证:nodeId 来自认证结果,无需 owner 检查\n nodeId = getNodeId(auth)!;\n } else {\n sendJson(response, 501, { error: 'WebID/API-key based node signaling is temporarily unavailable' });\n return;\n }\n\n const now = new Date();\n\n try {\n // Get current metadata to merge\n const existing = await repo.getNodeMetadata(nodeId);\n let metadata = mergeMetadata(existing?.metadata ?? {}, payload, now);\n\n // 从 DB connectivity 列注入 subdomain/ipv4,供 dnsCoordinator 使用\n const connectivityInfo = await repo.getNodeConnectivityInfo(nodeId);\n if (connectivityInfo) {\n if (connectivityInfo.subdomain && !metadata.subdomain) {\n metadata.subdomain = connectivityInfo.subdomain;\n }\n if (connectivityInfo.ipv4 && !metadata.ipv4) {\n metadata.ipv4 = connectivityInfo.ipv4;\n }\n if (connectivityInfo.connectivityStatus && !metadata.connectivityStatus) {\n metadata.connectivityStatus = connectivityInfo.connectivityStatus;\n }\n }\n\n // Update heartbeat\n await repo.updateNodeHeartbeat(nodeId, metadata, now);\n\n // Update pods if provided\n if (Array.isArray(payload.pods)) {\n await repo.replaceNodePods(nodeId, payload.pods as string[]);\n }\n\n // 健康检查 → DNS 同步\n if (healthProbeService) {\n await healthProbeService.probeNode(nodeId);\n // 健康检查结果写入了 DB metadata,重新读取\n const freshMeta = await repo.getNodeMetadata(nodeId);\n if (freshMeta?.metadata) {\n const reachability = (freshMeta.metadata as Record<string, unknown>).reachability;\n if (reachability) {\n metadata.reachability = reachability;\n const status = (reachability as Record<string, unknown>).status;\n if (typeof status === 'string') {\n metadata.connectivityStatus = status === 'unreachable' ? 'unreachable' : 'reachable';\n }\n }\n }\n }\n\n if (dnsCoordinator) {\n await dnsCoordinator.synchronize(nodeId, metadata);\n }\n\n logger.debug(`Signal received from node ${nodeId}`);\n\n sendJson(response, 200, {\n status: 'ok',\n nodeId,\n lastSeen: now.toISOString(),\n metadata,\n });\n } catch (error) {\n logger.error(`Signal handling error for node ${nodeId}:`, error);\n if (error instanceof Error) {\n logger.error(`Stack trace: ${error.stack}`);\n }\n sendJson(response, 500, { error: 'Failed to process signal' });\n }\n });\n}\n\nfunction mergeMetadata(\n previous: Record<string, unknown>,\n payload: Record<string, unknown>,\n now: Date,\n): Record<string, unknown> {\n const next: Record<string, unknown> = { ...previous };\n next.lastHeartbeatAt = now.toISOString();\n\n const copyIfPresent = (key: string) => {\n if (payload[key] !== undefined) {\n next[key] = payload[key];\n }\n };\n\n copyIfPresent('baseUrl');\n copyIfPresent('publicAddress');\n copyIfPresent('hostname');\n copyIfPresent('ipv4');\n copyIfPresent('ipv6');\n copyIfPresent('version');\n copyIfPresent('status');\n copyIfPresent('capabilities');\n copyIfPresent('metrics');\n\n return next;\n}\n\nasync function readJsonBody(request: AuthenticatedRequest): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(undefined);\n }\n });\n request.on('error', reject);\n });\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n"]}
1
+ {"version":3,"file":"EdgeNodeSignalHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/EdgeNodeSignalHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { AuthenticatedRequest } from '../middleware/AuthMiddleware';\nimport type { ApiServer } from '../ApiServer';\nimport type { EdgeNodeRepository } from '../../identity/drizzle/EdgeNodeRepository';\nimport type { EdgeNodeDnsCoordinator } from '../../edge/EdgeNodeDnsCoordinator';\nimport type { EdgeNodeHealthProbeService } from '../../edge/EdgeNodeHealthProbeService';\nimport { isNodeAuth, getNodeId } from '../auth/AuthContext';\n\nexport interface EdgeNodeSignalHandlerOptions {\n repository: EdgeNodeRepository;\n dnsCoordinator?: EdgeNodeDnsCoordinator;\n healthProbeService?: EdgeNodeHealthProbeService;\n}\n\n/**\n * Handler for edge node signaling API\n *\n * POST /v1/signal - Edge node heartbeat/signal\n *\n * Requires API authentication and a nodeId in the request body.\n */\nexport function registerEdgeNodeSignalRoutes(server: ApiServer, options: EdgeNodeSignalHandlerOptions): void {\n const logger = getLoggerFor('EdgeNodeSignalHandler');\n const repo = options.repository;\n const { dnsCoordinator, healthProbeService } = options;\n\n // POST /v1/signal - authenticated via nodeToken, API key, or Solid token\n server.post('/v1/signal', async (request, response, _params) => {\n const auth = request.auth;\n if (!auth) {\n sendJson(response, 401, { error: 'Authentication required' });\n return;\n }\n\n const body = await readJsonBody(request);\n if (!body || typeof body !== 'object') {\n sendJson(response, 400, { error: 'Request body must be a JSON object' });\n return;\n }\n const payload = body as Record<string, unknown>;\n\n // Resolve nodeId based on auth type\n let nodeId: string;\n\n if (isNodeAuth(auth)) {\n // nodeToken 认证:nodeId 来自认证结果,无需 owner 检查\n nodeId = getNodeId(auth)!;\n } else {\n sendJson(response, 501, { error: 'WebID/API-key based node signaling is temporarily unavailable' });\n return;\n }\n\n const now = new Date();\n\n try {\n // Get current metadata to merge\n const existing = await repo.getNodeMetadata(nodeId);\n let metadata = mergeMetadata(existing?.metadata ?? {}, payload, now);\n\n // 从 DB connectivity 列注入 subdomain/ipv4,供 dnsCoordinator 使用\n const connectivityInfo = await repo.getNodeConnectivityInfo(nodeId);\n if (connectivityInfo) {\n if (connectivityInfo.subdomain && !metadata.subdomain) {\n metadata.subdomain = connectivityInfo.subdomain;\n }\n if (connectivityInfo.ipv4 && !metadata.ipv4) {\n metadata.ipv4 = connectivityInfo.ipv4;\n }\n if (connectivityInfo.connectivityStatus && !metadata.connectivityStatus) {\n metadata.connectivityStatus = connectivityInfo.connectivityStatus;\n }\n }\n\n // Update heartbeat\n await repo.updateNodeHeartbeat(nodeId, metadata, now);\n\n // Update pods if provided\n if (Array.isArray(payload.pods)) {\n await repo.replaceNodePods(nodeId, payload.pods as string[]);\n }\n\n // 健康检查 → DNS 同步\n if (healthProbeService) {\n await healthProbeService.probeNode(nodeId);\n // 健康检查结果写入了 DB metadata,重新读取\n const freshMeta = await repo.getNodeMetadata(nodeId);\n if (freshMeta?.metadata) {\n const reachability = (freshMeta.metadata as Record<string, unknown>).reachability;\n if (reachability) {\n metadata.reachability = reachability;\n const status = (reachability as Record<string, unknown>).status;\n if (typeof status === 'string') {\n metadata.connectivityStatus = status === 'unreachable' ? 'unreachable' : 'reachable';\n }\n }\n }\n }\n\n if (dnsCoordinator) {\n await dnsCoordinator.synchronize(nodeId, metadata);\n }\n\n logger.debug(`Signal received from node ${nodeId}`);\n\n sendJson(response, 200, {\n status: 'ok',\n nodeId,\n lastSeen: now.toISOString(),\n metadata,\n });\n } catch (error) {\n logger.error(`Signal handling error for node ${nodeId}:`, error);\n if (error instanceof Error) {\n logger.error(`Stack trace: ${error.stack}`);\n }\n sendJson(response, 500, { error: 'Failed to process signal' });\n }\n });\n}\n\nfunction mergeMetadata(\n previous: Record<string, unknown>,\n payload: Record<string, unknown>,\n now: Date,\n): Record<string, unknown> {\n const next: Record<string, unknown> = { ...previous };\n next.lastHeartbeatAt = now.toISOString();\n\n const copyIfPresent = (key: string) => {\n if (payload[key] !== undefined) {\n next[key] = payload[key];\n }\n };\n\n copyIfPresent('baseUrl');\n copyIfPresent('hostname');\n copyIfPresent('ipv4');\n copyIfPresent('ipv6');\n copyIfPresent('version');\n copyIfPresent('status');\n copyIfPresent('capabilities');\n copyIfPresent('directCandidates');\n copyIfPresent('tunnel');\n copyIfPresent('certificate');\n copyIfPresent('metrics');\n\n return next;\n}\n\nasync function readJsonBody(request: AuthenticatedRequest): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve(undefined);\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch {\n resolve(undefined);\n }\n });\n request.on('error', reject);\n });\n}\n\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n"]}
@@ -15,6 +15,8 @@ export interface PodManagementHandlerOptions {
15
15
  };
16
16
  /** SP-local Pod lookup used by Cloud consent to scope account WebIDs. */
17
17
  podLookupRepository?: Pick<PodLookupRepository, 'findByWebIds'>;
18
+ /** Canonical storage root for this SP; lookup responses are fail-closed to this root. */
19
+ storageProviderBaseUrl?: string;
18
20
  }
19
21
  export interface CreatePodRequest {
20
22
  /** Pod 名称(通常是用户名) */
@@ -43,6 +43,7 @@ const global_logger_factory_1 = require("global-logger-factory");
43
43
  function registerPodManagementRoutes(server, options) {
44
44
  const logger = (0, global_logger_factory_1.getLoggerFor)('PodManagementHandler');
45
45
  const { rootDir, verifyServiceToken, podNameRegex = /^[a-zA-Z0-9_-]+$/, provisioningService, podLookupRepository } = options;
46
+ const storageProviderRoot = normalizeStorageRoot(options.storageProviderBaseUrl);
46
47
  /**
47
48
  * 验证 service token
48
49
  */
@@ -107,6 +108,18 @@ function registerPodManagementRoutes(server, options) {
107
108
  const podPath = `${rootDir}/${podName}`;
108
109
  try {
109
110
  const exists = await fileExists(podPath);
111
+ if (exists && provisioningService) {
112
+ const existing = await findExistingProvisionedPod(body, podName);
113
+ if (existing) {
114
+ logger.info(`Pod ${podName} already exists for ${body.webId}; returning existing SP-local pod`);
115
+ sendJson(response, 200, {
116
+ success: true,
117
+ podUrl: existing.storageUrl,
118
+ message: `Pod ${podName} already exists for this WebID`,
119
+ });
120
+ return;
121
+ }
122
+ }
110
123
  if (exists) {
111
124
  sendJson(response, 409, { error: 'Conflict', message: `Pod ${podName} already exists` });
112
125
  return;
@@ -168,19 +181,44 @@ function registerPodManagementRoutes(server, options) {
168
181
  try {
169
182
  const webIds = body.webIds;
170
183
  const pods = await podLookupRepository.findByWebIds(webIds);
171
- const entries = pods
172
- .map((pod) => {
184
+ const entries = [];
185
+ const seenEntries = new Set();
186
+ if (!storageProviderRoot) {
187
+ logger.warn('Refusing to expose provisioned WebIDs because storageProviderBaseUrl is not configured');
188
+ sendJson(response, 200, { entries });
189
+ return;
190
+ }
191
+ for (const pod of pods) {
192
+ const rawStorageUrl = pod.storageUrl ?? pod.baseUrl;
193
+ if (!storageUrlBelongsToRoot(rawStorageUrl, storageProviderRoot)) {
194
+ continue;
195
+ }
196
+ const podName = getRootRelativePodName(rawStorageUrl, storageProviderRoot);
197
+ if (!podName || !validatePodName(podName)) {
198
+ logger.warn(`Refusing to expose provisioned WebID for invalid SP-local Pod URL: ${rawStorageUrl}`);
199
+ continue;
200
+ }
201
+ const podPath = `${rootDir}/${podName}`;
202
+ if (!await fileExists(podPath)) {
203
+ logger.warn(`Refusing to expose stale provisioned WebID for missing Pod directory: ${podPath}`);
204
+ continue;
205
+ }
173
206
  const webId = resolveMatchedWebId(pod.webId, pod.webIds, webIds);
174
- const storageUrl = ensureTrailingSlash(pod.storageUrl ?? pod.baseUrl);
175
- return webId
176
- ? {
177
- webId,
178
- podUrl: storageUrl,
179
- storageUrl,
180
- }
181
- : undefined;
182
- })
183
- .filter((entry) => Boolean(entry));
207
+ if (!webId) {
208
+ continue;
209
+ }
210
+ const storageUrl = ensureTrailingSlash(rawStorageUrl);
211
+ const entryKey = `${webId}\u0000${storageUrl}`;
212
+ if (seenEntries.has(entryKey)) {
213
+ continue;
214
+ }
215
+ seenEntries.add(entryKey);
216
+ entries.push({
217
+ webId,
218
+ podUrl: storageUrl,
219
+ storageUrl,
220
+ });
221
+ }
184
222
  sendJson(response, 200, { entries });
185
223
  }
186
224
  catch (error) {
@@ -188,6 +226,27 @@ function registerPodManagementRoutes(server, options) {
188
226
  sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to lookup provisioned WebIDs' });
189
227
  }
190
228
  }, { public: true });
229
+ async function findExistingProvisionedPod(body, podName) {
230
+ if (!podLookupRepository || !storageProviderRoot || !body.webId) {
231
+ return undefined;
232
+ }
233
+ try {
234
+ const requestedWebId = body.webId;
235
+ const expectedPodUrl = buildPodUrl(storageProviderRoot, podName);
236
+ const pods = await podLookupRepository.findByWebIds([requestedWebId]);
237
+ const match = pods.find((pod) => {
238
+ const storageUrl = ensureTrailingSlash(pod.storageUrl ?? pod.baseUrl);
239
+ return storageUrl === expectedPodUrl &&
240
+ storageUrlBelongsToRoot(storageUrl, storageProviderRoot) &&
241
+ Boolean(resolveMatchedWebId(pod.webId, pod.webIds, [requestedWebId]));
242
+ });
243
+ return match ? { storageUrl: expectedPodUrl } : undefined;
244
+ }
245
+ catch (error) {
246
+ logger.warn(`Failed to verify existing pod ownership for ${podName}: ${error.message}`);
247
+ return undefined;
248
+ }
249
+ }
191
250
  /**
192
251
  * DELETE /provision/pods/:podName
193
252
  *
@@ -328,6 +387,49 @@ function normalizeUrl(value) {
328
387
  function ensureTrailingSlash(url) {
329
388
  return url.replace(/\/+$/u, '') + '/';
330
389
  }
390
+ function normalizeStorageRoot(url) {
391
+ if (!url) {
392
+ return undefined;
393
+ }
394
+ try {
395
+ return ensureTrailingSlash(new URL(url).toString());
396
+ }
397
+ catch {
398
+ return undefined;
399
+ }
400
+ }
401
+ function storageUrlBelongsToRoot(storageUrl, storageRoot) {
402
+ if (!storageUrl) {
403
+ return false;
404
+ }
405
+ try {
406
+ return ensureTrailingSlash(new URL(storageUrl).toString()).startsWith(storageRoot);
407
+ }
408
+ catch {
409
+ return false;
410
+ }
411
+ }
412
+ function buildPodUrl(storageRoot, podName) {
413
+ return ensureTrailingSlash(new URL(`${encodeURIComponent(podName)}/`, storageRoot).toString());
414
+ }
415
+ function getRootRelativePodName(storageUrl, storageRoot) {
416
+ if (!storageUrl) {
417
+ return undefined;
418
+ }
419
+ try {
420
+ const normalizedStorageUrl = ensureTrailingSlash(new URL(storageUrl).toString());
421
+ const normalizedRoot = ensureTrailingSlash(new URL(storageRoot).toString());
422
+ if (!normalizedStorageUrl.startsWith(normalizedRoot)) {
423
+ return undefined;
424
+ }
425
+ const relativePath = normalizedStorageUrl.slice(normalizedRoot.length);
426
+ const segment = relativePath.split('/').find(Boolean);
427
+ return segment ? decodeURIComponent(segment) : undefined;
428
+ }
429
+ catch {
430
+ return undefined;
431
+ }
432
+ }
331
433
  /**
332
434
  * 检查文件/目录是否存在
333
435
  */
@@ -1 +1 @@
1
- {"version":3,"file":"PodManagementHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/PodManagementHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse, IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { ApiServer } from '../ApiServer';\nimport type { PodLookupRepository } from '../../identity/drizzle/PodLookupRepository';\n\nexport interface PodManagementHandlerOptions {\n /** Pod 存储根目录 */\n rootDir: string;\n /** 验证 IdP service token */\n verifyServiceToken: (token: string) => Promise<boolean>;\n /** 可选:限制允许的 pod 名称正则 */\n podNameRegex?: RegExp;\n /** 可选:创建 CSS-compatible Pod 数据,而不是只创建裸目录 */\n provisioningService?: {\n createPod(input: CreatePodRequest): Promise<{ podUrl: string }>;\n };\n /** SP-local Pod lookup used by Cloud consent to scope account WebIDs. */\n podLookupRepository?: Pick<PodLookupRepository, 'findByWebIds'>;\n}\n\nexport interface CreatePodRequest {\n /** Pod 名称(通常是用户名) */\n podName: string;\n /** Owner WebID,Cloud IDP + Local SP 时应为 Cloud WebID */\n webId?: string;\n /** 可选:初始资源 */\n initialResources?: Record<string, string>;\n}\n\nexport interface CreatePodResponse {\n success: boolean;\n podUrl: string;\n message: string;\n}\n\nexport interface DeletePodResponse {\n success: boolean;\n message: string;\n}\n\ninterface LookupWebIdsRequest {\n webIds?: unknown;\n}\n\ninterface LookupWebIdsResponse {\n entries: Array<{\n webId: string;\n podUrl: string;\n storageUrl: string;\n }>;\n}\n\n/**\n * Pod Management Handler\n *\n * SP (Storage Provider) 端供 IdP 调用的 API。\n * 用于创建/删除/查询 Pod 目录。\n *\n * 端点 (Solid Storage Provision Protocol):\n * - POST /provision/pods - 创建 Pod\n * - GET /provision/pods/:podName - 查询 Pod\n * - DELETE /provision/pods/:podName - 删除 Pod\n *\n * 认证:\n * - 使用 IdP service token (Bearer)\n * - 验证 token 是否来自信任的 IdP\n */\nexport function registerPodManagementRoutes(\n server: ApiServer,\n options: PodManagementHandlerOptions\n): void {\n const logger = getLoggerFor('PodManagementHandler');\n const { rootDir, verifyServiceToken, podNameRegex = /^[a-zA-Z0-9_-]+$/, provisioningService, podLookupRepository } = options;\n\n /**\n * 验证 service token\n */\n async function authenticate(request: IncomingMessage): Promise<boolean> {\n const authHeader = request.headers.authorization;\n if (!authHeader || !authHeader.startsWith('Bearer ')) {\n return false;\n }\n const token = authHeader.slice(7);\n return verifyServiceToken(token);\n }\n\n /**\n * 验证 pod 名称\n */\n function validatePodName(podName: string): boolean {\n if (!podName || podName.length < 1 || podName.length > 64) {\n return false;\n }\n return podNameRegex.test(podName);\n }\n\n /**\n * POST /provision/pods\n *\n * 创建 Pod 目录\n *\n * Request:\n * Authorization: Bearer {service_token}\n * Content-Type: application/json\n * Body: { podName: \"alice\", initialResources?: {...} }\n *\n * Response:\n * 201: { success: true, podUrl: \"https://node1.pods.site/alice/\" }\n * 400: { error: \"Invalid pod name\" }\n * 401: { error: \"Unauthorized\" }\n * 409: { error: \"Pod already exists\" }\n */\n server.post('/provision/pods', async (request, response) => {\n // 1. 认证\n if (!await authenticate(request)) {\n sendJson(response, 401, { error: 'Unauthorized', message: 'Invalid or missing service token' });\n return;\n }\n\n // 2. 解析请求体\n let body: CreatePodRequest;\n try {\n body = await readJsonBody(request) as CreatePodRequest;\n } catch (error) {\n sendJson(response, 400, { error: 'Bad Request', message: 'Invalid JSON body' });\n return;\n }\n\n const { podName, initialResources } = body;\n\n // 3. 验证 pod 名称\n if (!validatePodName(podName)) {\n sendJson(response, 400, {\n error: 'Bad Request',\n message: `Invalid pod name: ${podName}. Must match ${podNameRegex.toString()}`\n });\n return;\n }\n\n // 4. 检查是否已存在\n const podPath = `${rootDir}/${podName}`;\n try {\n const exists = await fileExists(podPath);\n if (exists) {\n sendJson(response, 409, { error: 'Conflict', message: `Pod ${podName} already exists` });\n return;\n }\n } catch (error) {\n logger.error(`Error checking pod existence: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to check pod existence' });\n return;\n }\n\n // 5. 创建 Pod 目录\n try {\n const result = provisioningService\n ? await provisioningService.createPod(body)\n : await createPodDirectory(podPath, initialResources).then(() => undefined);\n logger.info(`Created pod: ${podName} at ${podPath}`);\n\n // 构建 pod URL (基于请求的 host)\n const host = request.headers.host || 'localhost';\n const podUrl = result?.podUrl || `https://${host}/${podName}/`;\n\n sendJson(response, 201, {\n success: true,\n podUrl,\n message: `Pod ${podName} created successfully`\n });\n } catch (error) {\n logger.error(`Failed to create pod: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to create pod' });\n }\n }, { public: true }); // Service token auth handled internally\n\n /**\n * POST /provision/webids\n *\n * Lookup account-linked WebIDs against this SP's Pod facts. Cloud OIDC uses\n * this during Cloud IDP + Local SP consent so the picker cannot offer Pods\n * from a different storage provider.\n */\n server.post('/provision/webids', async (request, response) => {\n if (!await authenticate(request)) {\n sendJson(response, 401, { error: 'Unauthorized', message: 'Invalid or missing service token' });\n return;\n }\n\n if (!podLookupRepository) {\n sendJson(response, 503, { error: 'Unavailable', message: 'Pod lookup repository is not configured' });\n return;\n }\n\n let body: LookupWebIdsRequest;\n try {\n body = await readJsonBody(request) as LookupWebIdsRequest;\n } catch {\n sendJson(response, 400, { error: 'Bad Request', message: 'Invalid JSON body' });\n return;\n }\n\n if (!Array.isArray(body.webIds) || body.webIds.some((webId) => typeof webId !== 'string')) {\n sendJson(response, 400, { error: 'Bad Request', message: 'webIds must be a string array' });\n return;\n }\n\n try {\n const webIds = body.webIds as string[];\n const pods = await podLookupRepository.findByWebIds(webIds);\n const entries = pods\n .map((pod) => {\n const webId = resolveMatchedWebId(pod.webId, pod.webIds, webIds);\n const storageUrl = ensureTrailingSlash(pod.storageUrl ?? pod.baseUrl);\n return webId\n ? {\n webId,\n podUrl: storageUrl,\n storageUrl,\n }\n : undefined;\n })\n .filter((entry): entry is LookupWebIdsResponse['entries'][number] => Boolean(entry));\n\n sendJson(response, 200, { entries });\n } catch (error) {\n logger.error(`Failed to lookup provisioned WebIDs: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to lookup provisioned WebIDs' });\n }\n }, { public: true });\n\n /**\n * DELETE /provision/pods/:podName\n *\n * 删除 Pod 目录\n *\n * Request:\n * Authorization: Bearer {service_token}\n *\n * Response:\n * 200: { success: true }\n * 401: { error: \"Unauthorized\" }\n * 404: { error: \"Pod not found\" }\n */\n server.delete('/provision/pods/:podName', async (request, response, params) => {\n // 1. 认证\n if (!await authenticate(request)) {\n sendJson(response, 401, { error: 'Unauthorized', message: 'Invalid or missing service token' });\n return;\n }\n\n const podName = decodeURIComponent(params.podName);\n\n // 2. 验证 pod 名称\n if (!validatePodName(podName)) {\n sendJson(response, 400, { error: 'Bad Request', message: `Invalid pod name: ${podName}` });\n return;\n }\n\n // 3. 检查是否存在\n const podPath = `${rootDir}/${podName}`;\n try {\n const exists = await fileExists(podPath);\n if (!exists) {\n sendJson(response, 404, { error: 'Not Found', message: `Pod ${podName} not found` });\n return;\n }\n } catch (error) {\n logger.error(`Error checking pod existence: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to check pod existence' });\n return;\n }\n\n // 4. 删除 Pod 目录\n try {\n await deletePodDirectory(podPath);\n logger.info(`Deleted pod: ${podName}`);\n\n sendJson(response, 200, {\n success: true,\n message: `Pod ${podName} deleted successfully`\n });\n } catch (error) {\n logger.error(`Failed to delete pod: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to delete pod' });\n }\n }, { public: true });\n\n /**\n * GET /provision/pods/:podName\n *\n * 获取 Pod 信息(存在性检查)\n */\n server.get('/provision/pods/:podName', async (request, response, params) => {\n // 1. 认证\n if (!await authenticate(request)) {\n sendJson(response, 401, { error: 'Unauthorized', message: 'Invalid or missing service token' });\n return;\n }\n\n const podName = decodeURIComponent(params.podName);\n const podPath = `${rootDir}/${podName}`;\n\n try {\n const exists = await fileExists(podPath);\n if (!exists) {\n sendJson(response, 404, { error: 'Not Found', message: `Pod ${podName} not found` });\n return;\n }\n\n const host = request.headers.host || 'localhost';\n const podUrl = `https://${host}/${podName}/`;\n\n sendJson(response, 200, {\n exists: true,\n podName,\n podUrl,\n storagePath: podPath\n });\n } catch (error) {\n logger.error(`Error getting pod info: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to get pod info' });\n }\n }, { public: true });\n\n logger.info(`Pod management routes registered with rootDir: ${rootDir}`);\n}\n\n/**\n * 读取 JSON 请求体\n */\nasync function readJsonBody(request: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve({});\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch (error) {\n reject(error);\n }\n });\n request.on('error', reject);\n });\n}\n\n/**\n * 发送 JSON 响应\n */\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n\nfunction resolveMatchedWebId(webId: string | undefined, webIds: string[] | undefined, requested: string[]): string | undefined {\n const candidates = [\n webId,\n ...(webIds ?? []),\n ].filter((value): value is string => typeof value === 'string');\n const requestedSet = new Set(requested.map(normalizeUrl));\n return candidates.find((candidate) => requestedSet.has(normalizeUrl(candidate)));\n}\n\nfunction normalizeUrl(value: string): string {\n try {\n return new URL(value).toString();\n } catch {\n return value;\n }\n}\n\nfunction ensureTrailingSlash(url: string): string {\n return url.replace(/\\/+$/u, '') + '/';\n}\n\n/**\n * 检查文件/目录是否存在\n */\nasync function fileExists(path: string): Promise<boolean> {\n const { stat } = await import('node:fs/promises');\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * 创建 Pod 目录\n */\nasync function createPodDirectory(\n podPath: string,\n initialResources?: Record<string, string>\n): Promise<void> {\n const { mkdir, writeFile } = await import('node:fs/promises');\n const { join } = await import('node:path');\n\n // 创建目录\n await mkdir(podPath, { recursive: true });\n\n // 创建初始资源\n if (initialResources) {\n for (const [filename, content] of Object.entries(initialResources)) {\n const filePath = join(podPath, filename);\n await writeFile(filePath, content, 'utf8');\n }\n }\n}\n\n/**\n * 删除 Pod 目录\n */\nasync function deletePodDirectory(podPath: string): Promise<void> {\n const { rm } = await import('node:fs/promises');\n await rm(podPath, { recursive: true, force: true });\n}\n"]}
1
+ {"version":3,"file":"PodManagementHandler.js","sourceRoot":"","sources":["../../../src/api/handlers/PodManagementHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServerResponse, IncomingMessage } from 'node:http';\nimport { getLoggerFor } from 'global-logger-factory';\nimport type { ApiServer } from '../ApiServer';\nimport type { PodLookupRepository } from '../../identity/drizzle/PodLookupRepository';\n\nexport interface PodManagementHandlerOptions {\n /** Pod 存储根目录 */\n rootDir: string;\n /** 验证 IdP service token */\n verifyServiceToken: (token: string) => Promise<boolean>;\n /** 可选:限制允许的 pod 名称正则 */\n podNameRegex?: RegExp;\n /** 可选:创建 CSS-compatible Pod 数据,而不是只创建裸目录 */\n provisioningService?: {\n createPod(input: CreatePodRequest): Promise<{ podUrl: string }>;\n };\n /** SP-local Pod lookup used by Cloud consent to scope account WebIDs. */\n podLookupRepository?: Pick<PodLookupRepository, 'findByWebIds'>;\n /** Canonical storage root for this SP; lookup responses are fail-closed to this root. */\n storageProviderBaseUrl?: string;\n}\n\nexport interface CreatePodRequest {\n /** Pod 名称(通常是用户名) */\n podName: string;\n /** Owner WebID,Cloud IDP + Local SP 时应为 Cloud WebID */\n webId?: string;\n /** 可选:初始资源 */\n initialResources?: Record<string, string>;\n}\n\nexport interface CreatePodResponse {\n success: boolean;\n podUrl: string;\n message: string;\n}\n\nexport interface DeletePodResponse {\n success: boolean;\n message: string;\n}\n\ninterface LookupWebIdsRequest {\n webIds?: unknown;\n}\n\ninterface LookupWebIdsResponse {\n entries: Array<{\n webId: string;\n podUrl: string;\n storageUrl: string;\n }>;\n}\n\n/**\n * Pod Management Handler\n *\n * SP (Storage Provider) 端供 IdP 调用的 API。\n * 用于创建/删除/查询 Pod 目录。\n *\n * 端点 (Solid Storage Provision Protocol):\n * - POST /provision/pods - 创建 Pod\n * - GET /provision/pods/:podName - 查询 Pod\n * - DELETE /provision/pods/:podName - 删除 Pod\n *\n * 认证:\n * - 使用 IdP service token (Bearer)\n * - 验证 token 是否来自信任的 IdP\n */\nexport function registerPodManagementRoutes(\n server: ApiServer,\n options: PodManagementHandlerOptions\n): void {\n const logger = getLoggerFor('PodManagementHandler');\n const { rootDir, verifyServiceToken, podNameRegex = /^[a-zA-Z0-9_-]+$/, provisioningService, podLookupRepository } = options;\n const storageProviderRoot = normalizeStorageRoot(options.storageProviderBaseUrl);\n\n /**\n * 验证 service token\n */\n async function authenticate(request: IncomingMessage): Promise<boolean> {\n const authHeader = request.headers.authorization;\n if (!authHeader || !authHeader.startsWith('Bearer ')) {\n return false;\n }\n const token = authHeader.slice(7);\n return verifyServiceToken(token);\n }\n\n /**\n * 验证 pod 名称\n */\n function validatePodName(podName: string): boolean {\n if (!podName || podName.length < 1 || podName.length > 64) {\n return false;\n }\n return podNameRegex.test(podName);\n }\n\n /**\n * POST /provision/pods\n *\n * 创建 Pod 目录\n *\n * Request:\n * Authorization: Bearer {service_token}\n * Content-Type: application/json\n * Body: { podName: \"alice\", initialResources?: {...} }\n *\n * Response:\n * 201: { success: true, podUrl: \"https://node1.pods.site/alice/\" }\n * 400: { error: \"Invalid pod name\" }\n * 401: { error: \"Unauthorized\" }\n * 409: { error: \"Pod already exists\" }\n */\n server.post('/provision/pods', async (request, response) => {\n // 1. 认证\n if (!await authenticate(request)) {\n sendJson(response, 401, { error: 'Unauthorized', message: 'Invalid or missing service token' });\n return;\n }\n\n // 2. 解析请求体\n let body: CreatePodRequest;\n try {\n body = await readJsonBody(request) as CreatePodRequest;\n } catch (error) {\n sendJson(response, 400, { error: 'Bad Request', message: 'Invalid JSON body' });\n return;\n }\n\n const { podName, initialResources } = body;\n\n // 3. 验证 pod 名称\n if (!validatePodName(podName)) {\n sendJson(response, 400, {\n error: 'Bad Request',\n message: `Invalid pod name: ${podName}. Must match ${podNameRegex.toString()}`\n });\n return;\n }\n\n // 4. 检查是否已存在\n const podPath = `${rootDir}/${podName}`;\n try {\n const exists = await fileExists(podPath);\n if (exists && provisioningService) {\n const existing = await findExistingProvisionedPod(body, podName);\n if (existing) {\n logger.info(`Pod ${podName} already exists for ${body.webId}; returning existing SP-local pod`);\n sendJson(response, 200, {\n success: true,\n podUrl: existing.storageUrl,\n message: `Pod ${podName} already exists for this WebID`,\n });\n return;\n }\n }\n\n if (exists) {\n sendJson(response, 409, { error: 'Conflict', message: `Pod ${podName} already exists` });\n return;\n }\n } catch (error) {\n logger.error(`Error checking pod existence: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to check pod existence' });\n return;\n }\n\n // 5. 创建 Pod 目录\n try {\n const result = provisioningService\n ? await provisioningService.createPod(body)\n : await createPodDirectory(podPath, initialResources).then(() => undefined);\n logger.info(`Created pod: ${podName} at ${podPath}`);\n\n // 构建 pod URL (基于请求的 host)\n const host = request.headers.host || 'localhost';\n const podUrl = result?.podUrl || `https://${host}/${podName}/`;\n\n sendJson(response, 201, {\n success: true,\n podUrl,\n message: `Pod ${podName} created successfully`\n });\n } catch (error) {\n logger.error(`Failed to create pod: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to create pod' });\n }\n }, { public: true }); // Service token auth handled internally\n\n /**\n * POST /provision/webids\n *\n * Lookup account-linked WebIDs against this SP's Pod facts. Cloud OIDC uses\n * this during Cloud IDP + Local SP consent so the picker cannot offer Pods\n * from a different storage provider.\n */\n server.post('/provision/webids', async (request, response) => {\n if (!await authenticate(request)) {\n sendJson(response, 401, { error: 'Unauthorized', message: 'Invalid or missing service token' });\n return;\n }\n\n if (!podLookupRepository) {\n sendJson(response, 503, { error: 'Unavailable', message: 'Pod lookup repository is not configured' });\n return;\n }\n\n let body: LookupWebIdsRequest;\n try {\n body = await readJsonBody(request) as LookupWebIdsRequest;\n } catch {\n sendJson(response, 400, { error: 'Bad Request', message: 'Invalid JSON body' });\n return;\n }\n\n if (!Array.isArray(body.webIds) || body.webIds.some((webId) => typeof webId !== 'string')) {\n sendJson(response, 400, { error: 'Bad Request', message: 'webIds must be a string array' });\n return;\n }\n\n try {\n const webIds = body.webIds as string[];\n const pods = await podLookupRepository.findByWebIds(webIds);\n const entries: LookupWebIdsResponse['entries'] = [];\n const seenEntries = new Set<string>();\n\n if (!storageProviderRoot) {\n logger.warn('Refusing to expose provisioned WebIDs because storageProviderBaseUrl is not configured');\n sendJson(response, 200, { entries });\n return;\n }\n\n for (const pod of pods) {\n const rawStorageUrl = pod.storageUrl ?? pod.baseUrl;\n if (!storageUrlBelongsToRoot(rawStorageUrl, storageProviderRoot)) {\n continue;\n }\n\n const podName = getRootRelativePodName(rawStorageUrl, storageProviderRoot);\n if (!podName || !validatePodName(podName)) {\n logger.warn(`Refusing to expose provisioned WebID for invalid SP-local Pod URL: ${rawStorageUrl}`);\n continue;\n }\n\n const podPath = `${rootDir}/${podName}`;\n if (!await fileExists(podPath)) {\n logger.warn(`Refusing to expose stale provisioned WebID for missing Pod directory: ${podPath}`);\n continue;\n }\n\n const webId = resolveMatchedWebId(pod.webId, pod.webIds, webIds);\n if (!webId) {\n continue;\n }\n\n const storageUrl = ensureTrailingSlash(rawStorageUrl);\n const entryKey = `${webId}\\u0000${storageUrl}`;\n if (seenEntries.has(entryKey)) {\n continue;\n }\n seenEntries.add(entryKey);\n entries.push({\n webId,\n podUrl: storageUrl,\n storageUrl,\n });\n }\n\n sendJson(response, 200, { entries });\n } catch (error) {\n logger.error(`Failed to lookup provisioned WebIDs: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to lookup provisioned WebIDs' });\n }\n }, { public: true });\n\n async function findExistingProvisionedPod(\n body: CreatePodRequest,\n podName: string,\n ): Promise<{ storageUrl: string } | undefined> {\n if (!podLookupRepository || !storageProviderRoot || !body.webId) {\n return undefined;\n }\n\n try {\n const requestedWebId = body.webId;\n const expectedPodUrl = buildPodUrl(storageProviderRoot, podName);\n const pods = await podLookupRepository.findByWebIds([requestedWebId]);\n const match = pods.find((pod) => {\n const storageUrl = ensureTrailingSlash(pod.storageUrl ?? pod.baseUrl);\n return storageUrl === expectedPodUrl &&\n storageUrlBelongsToRoot(storageUrl, storageProviderRoot) &&\n Boolean(resolveMatchedWebId(pod.webId, pod.webIds, [requestedWebId]));\n });\n return match ? { storageUrl: expectedPodUrl } : undefined;\n } catch (error) {\n logger.warn(`Failed to verify existing pod ownership for ${podName}: ${(error as Error).message}`);\n return undefined;\n }\n }\n\n /**\n * DELETE /provision/pods/:podName\n *\n * 删除 Pod 目录\n *\n * Request:\n * Authorization: Bearer {service_token}\n *\n * Response:\n * 200: { success: true }\n * 401: { error: \"Unauthorized\" }\n * 404: { error: \"Pod not found\" }\n */\n server.delete('/provision/pods/:podName', async (request, response, params) => {\n // 1. 认证\n if (!await authenticate(request)) {\n sendJson(response, 401, { error: 'Unauthorized', message: 'Invalid or missing service token' });\n return;\n }\n\n const podName = decodeURIComponent(params.podName);\n\n // 2. 验证 pod 名称\n if (!validatePodName(podName)) {\n sendJson(response, 400, { error: 'Bad Request', message: `Invalid pod name: ${podName}` });\n return;\n }\n\n // 3. 检查是否存在\n const podPath = `${rootDir}/${podName}`;\n try {\n const exists = await fileExists(podPath);\n if (!exists) {\n sendJson(response, 404, { error: 'Not Found', message: `Pod ${podName} not found` });\n return;\n }\n } catch (error) {\n logger.error(`Error checking pod existence: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to check pod existence' });\n return;\n }\n\n // 4. 删除 Pod 目录\n try {\n await deletePodDirectory(podPath);\n logger.info(`Deleted pod: ${podName}`);\n\n sendJson(response, 200, {\n success: true,\n message: `Pod ${podName} deleted successfully`\n });\n } catch (error) {\n logger.error(`Failed to delete pod: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to delete pod' });\n }\n }, { public: true });\n\n /**\n * GET /provision/pods/:podName\n *\n * 获取 Pod 信息(存在性检查)\n */\n server.get('/provision/pods/:podName', async (request, response, params) => {\n // 1. 认证\n if (!await authenticate(request)) {\n sendJson(response, 401, { error: 'Unauthorized', message: 'Invalid or missing service token' });\n return;\n }\n\n const podName = decodeURIComponent(params.podName);\n const podPath = `${rootDir}/${podName}`;\n\n try {\n const exists = await fileExists(podPath);\n if (!exists) {\n sendJson(response, 404, { error: 'Not Found', message: `Pod ${podName} not found` });\n return;\n }\n\n const host = request.headers.host || 'localhost';\n const podUrl = `https://${host}/${podName}/`;\n\n sendJson(response, 200, {\n exists: true,\n podName,\n podUrl,\n storagePath: podPath\n });\n } catch (error) {\n logger.error(`Error getting pod info: ${(error as Error).message}`);\n sendJson(response, 500, { error: 'Internal Server Error', message: 'Failed to get pod info' });\n }\n }, { public: true });\n\n logger.info(`Pod management routes registered with rootDir: ${rootDir}`);\n}\n\n/**\n * 读取 JSON 请求体\n */\nasync function readJsonBody(request: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n let data = '';\n request.setEncoding('utf8');\n request.on('data', (chunk: string) => {\n data += chunk;\n });\n request.on('end', () => {\n if (!data) {\n resolve({});\n return;\n }\n try {\n resolve(JSON.parse(data));\n } catch (error) {\n reject(error);\n }\n });\n request.on('error', reject);\n });\n}\n\n/**\n * 发送 JSON 响应\n */\nfunction sendJson(response: ServerResponse, status: number, data: unknown): void {\n response.statusCode = status;\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(data));\n}\n\nfunction resolveMatchedWebId(webId: string | undefined, webIds: string[] | undefined, requested: string[]): string | undefined {\n const candidates = [\n webId,\n ...(webIds ?? []),\n ].filter((value): value is string => typeof value === 'string');\n const requestedSet = new Set(requested.map(normalizeUrl));\n return candidates.find((candidate) => requestedSet.has(normalizeUrl(candidate)));\n}\n\nfunction normalizeUrl(value: string): string {\n try {\n return new URL(value).toString();\n } catch {\n return value;\n }\n}\n\nfunction ensureTrailingSlash(url: string): string {\n return url.replace(/\\/+$/u, '') + '/';\n}\n\nfunction normalizeStorageRoot(url: string | undefined): string | undefined {\n if (!url) {\n return undefined;\n }\n try {\n return ensureTrailingSlash(new URL(url).toString());\n } catch {\n return undefined;\n }\n}\n\nfunction storageUrlBelongsToRoot(storageUrl: string | undefined, storageRoot: string): boolean {\n if (!storageUrl) {\n return false;\n }\n try {\n return ensureTrailingSlash(new URL(storageUrl).toString()).startsWith(storageRoot);\n } catch {\n return false;\n }\n}\n\nfunction buildPodUrl(storageRoot: string, podName: string): string {\n return ensureTrailingSlash(new URL(`${encodeURIComponent(podName)}/`, storageRoot).toString());\n}\n\nfunction getRootRelativePodName(storageUrl: string | undefined, storageRoot: string): string | undefined {\n if (!storageUrl) {\n return undefined;\n }\n\n try {\n const normalizedStorageUrl = ensureTrailingSlash(new URL(storageUrl).toString());\n const normalizedRoot = ensureTrailingSlash(new URL(storageRoot).toString());\n if (!normalizedStorageUrl.startsWith(normalizedRoot)) {\n return undefined;\n }\n\n const relativePath = normalizedStorageUrl.slice(normalizedRoot.length);\n const segment = relativePath.split('/').find(Boolean);\n return segment ? decodeURIComponent(segment) : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * 检查文件/目录是否存在\n */\nasync function fileExists(path: string): Promise<boolean> {\n const { stat } = await import('node:fs/promises');\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * 创建 Pod 目录\n */\nasync function createPodDirectory(\n podPath: string,\n initialResources?: Record<string, string>\n): Promise<void> {\n const { mkdir, writeFile } = await import('node:fs/promises');\n const { join } = await import('node:path');\n\n // 创建目录\n await mkdir(podPath, { recursive: true });\n\n // 创建初始资源\n if (initialResources) {\n for (const [filename, content] of Object.entries(initialResources)) {\n const filePath = join(podPath, filename);\n await writeFile(filePath, content, 'utf8');\n }\n }\n}\n\n/**\n * 删除 Pod 目录\n */\nasync function deletePodDirectory(podPath: string): Promise<void> {\n const { rm } = await import('node:fs/promises');\n await rm(podPath, { recursive: true, force: true });\n}\n"]}
@@ -38,11 +38,38 @@ export interface ProvisionStatusOptions {
38
38
  cloudUrl?: string;
39
39
  /** 节点 ID */
40
40
  nodeId?: string;
41
+ /** 节点 Token */
42
+ nodeToken?: string;
43
+ /** SP service token,刷新 provisionCode 时回传给 Cloud */
44
+ serviceToken?: string;
45
+ /** 当前 SP canonical public URL */
46
+ publicUrl?: string;
41
47
  /** SP 子域名 */
42
48
  spDomain?: string;
49
+ /** 本地端口,供 Cloud 管理 tunnel 元数据 */
50
+ localPort?: number;
51
+ /** tunnel token,供 Cloud 维护托管域名连通性 */
52
+ tunnelToken?: string;
43
53
  /** Cloud baseUrl,用于拼 provisionUrl */
44
54
  cloudBaseUrl?: string;
45
55
  /** provisionCode(可选,由环境变量传入) */
46
56
  provisionCode?: string;
57
+ /** Persist refreshed local-only setup state to the single local setup file. */
58
+ persistState?: (state: ProvisionStatusStateUpdate) => Promise<void> | void;
59
+ /** 测试/调试注入 */
60
+ fetchImpl?: typeof fetch;
61
+ now?: () => number;
62
+ refreshGraceSeconds?: number;
63
+ }
64
+ export interface ProvisionStatusStateUpdate {
65
+ nodeId: string;
66
+ nodeToken: string;
67
+ serviceToken: string;
68
+ provisionCode: string;
69
+ publicUrl?: string;
70
+ spDomain?: string;
71
+ cloudUrl?: string;
72
+ cloudBaseUrl?: string;
47
73
  }
48
74
  export declare function registerProvisionStatusRoute(server: ApiServer, options: ProvisionStatusOptions): void;
75
+ export declare function createLocalSetupProvisionStateWriter(setupPath: string | undefined, providerId: string | undefined): ProvisionStatusOptions['persistState'] | undefined;