@undefineds.co/xpod 0.3.55 → 0.3.57

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 (60) hide show
  1. package/dist/api/container/index.js +3 -0
  2. package/dist/api/container/index.js.map +1 -1
  3. package/dist/api/container/local.js +53 -13
  4. package/dist/api/container/local.js.map +1 -1
  5. package/dist/api/container/types.d.ts +6 -0
  6. package/dist/api/container/types.js.map +1 -1
  7. package/dist/api/handlers/AdminHandler.d.ts +15 -0
  8. package/dist/api/handlers/AdminHandler.js +128 -7
  9. package/dist/api/handlers/AdminHandler.js.map +1 -1
  10. package/dist/cli/commands/obj.d.ts +36 -0
  11. package/dist/cli/commands/obj.js +235 -30
  12. package/dist/cli/commands/obj.js.map +1 -1
  13. package/dist/components/components.jsonld +1 -0
  14. package/dist/components/context.jsonld +36 -0
  15. package/dist/edge/DdnsManager.d.ts +1 -1
  16. package/dist/edge/DdnsManager.js.map +1 -1
  17. package/dist/edge/reachability/P2PRealnetAcceptance.js +4 -3
  18. package/dist/edge/reachability/P2PRealnetAcceptance.js.map +1 -1
  19. package/dist/edge/reachability/TcpP2PSignalingSession.js +26 -17
  20. package/dist/edge/reachability/TcpP2PSignalingSession.js.map +1 -1
  21. package/dist/index.d.ts +2 -1
  22. package/dist/index.js +3 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/tunnel/NgrokTunnelProvider.d.ts +46 -0
  25. package/dist/tunnel/NgrokTunnelProvider.js +344 -0
  26. package/dist/tunnel/NgrokTunnelProvider.js.map +1 -0
  27. package/dist/tunnel/NgrokTunnelProvider.jsonld +215 -0
  28. package/dist/tunnel/TunnelProvider.d.ts +2 -2
  29. package/dist/tunnel/TunnelProvider.js.map +1 -1
  30. package/dist/tunnel/index.d.ts +1 -0
  31. package/dist/tunnel/index.js +3 -1
  32. package/dist/tunnel/index.js.map +1 -1
  33. package/node_modules/@undefineds.co/drizzle-solid/dist/core/pod-database.d.ts +62 -0
  34. package/node_modules/@undefineds.co/drizzle-solid/dist/core/pod-database.d.ts.map +1 -1
  35. package/node_modules/@undefineds.co/drizzle-solid/dist/core/pod-database.js +197 -0
  36. package/node_modules/@undefineds.co/drizzle-solid/dist/core/pod-database.js.map +1 -1
  37. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/pod-database.d.ts +62 -0
  38. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/pod-database.d.ts.map +1 -1
  39. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/pod-database.js +197 -0
  40. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/pod-database.js.map +1 -1
  41. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/pod.d.ts.map +1 -1
  42. package/node_modules/@undefineds.co/drizzle-solid/dist/pod.d.ts.map +1 -1
  43. package/node_modules/@undefineds.co/drizzle-solid/package.json +1 -1
  44. package/package.json +10 -6
  45. package/static/app/assets/{index-AaQ1qxhy.js → index-C1yksyT4.js} +28 -28
  46. package/static/app/assets/inrupt-smoke.js +2 -2
  47. package/static/app/assets/main.css +1 -1
  48. package/static/app/assets/main.js +10 -50
  49. package/static/app/auth.html +1 -1
  50. package/static/dashboard/assets/dashboard-BssaVKTn.css +1 -0
  51. package/static/dashboard/assets/dashboard-Ckuadl3C.js +54 -0
  52. package/static/dashboard/auth.html +1 -1
  53. package/static/dashboard/dashboard.html +2 -2
  54. package/static/dashboard/reachability.html +221 -0
  55. package/static/dashboard/reachability.svg +7 -0
  56. package/static/dashboard/reachability.webmanifest +18 -0
  57. package/static/dashboard/signal-pod.html +293 -0
  58. package/static/landing/index.html +1 -1
  59. package/static/dashboard/assets/dashboard-CTk9cn1-.js +0 -52
  60. package/static/dashboard/assets/dashboard-VQRsvXx9.css +0 -1
@@ -0,0 +1,46 @@
1
+ import type { TunnelProvider, TunnelConfig, TunnelSetupOptions, TunnelStatus } from './TunnelProvider';
2
+ export interface NgrokTunnelProviderOptions {
3
+ /** ngrok authtoken. Prefer local env/config; do not persist it from xpod. */
4
+ authtoken?: string;
5
+ /** Fixed ngrok endpoint/custom domain passed to `ngrok http --url`. */
6
+ url?: string;
7
+ /** ngrok executable path. */
8
+ ngrokPath?: string;
9
+ /** ngrok local Agent API, used to discover generated dev domains. */
10
+ agentApiUrl?: string;
11
+ /** Startup wait timeout. */
12
+ connectTimeoutMs?: number;
13
+ }
14
+ /**
15
+ * Starts a user-owned ngrok agent for Local SP access.
16
+ *
17
+ * This provider does not create ngrok resources or write Xpod DNS. A configured
18
+ * url can be a fixed ngrok dev domain or a paid custom domain already owned by
19
+ * the user's ngrok account. When url is omitted, the endpoint is discovered from
20
+ * the local ngrok Agent API after `ngrok http` starts.
21
+ */
22
+ export declare class NgrokTunnelProvider implements TunnelProvider {
23
+ readonly name = "ngrok";
24
+ private readonly logger;
25
+ private readonly authtoken?;
26
+ private readonly configuredUrl?;
27
+ private readonly ngrokPath;
28
+ private readonly agentApiUrl;
29
+ private readonly connectTimeoutMs;
30
+ private process;
31
+ private status;
32
+ private currentConfig;
33
+ private managedByUs;
34
+ constructor(options?: NgrokTunnelProviderOptions);
35
+ setup(options: TunnelSetupOptions): Promise<TunnelConfig>;
36
+ start(config?: TunnelConfig): Promise<void>;
37
+ stop(): Promise<void>;
38
+ getStatus(): TunnelStatus;
39
+ getEndpoint(): string | undefined;
40
+ cleanup(_config: TunnelConfig): Promise<void>;
41
+ isManagedByUs(): boolean;
42
+ private handleOutput;
43
+ private markConnected;
44
+ private waitForConnection;
45
+ private discoverEndpointFromAgentApi;
46
+ }
@@ -0,0 +1,344 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NgrokTunnelProvider = void 0;
4
+ const node_child_process_1 = require("node:child_process");
5
+ const global_logger_factory_1 = require("global-logger-factory");
6
+ /**
7
+ * Starts a user-owned ngrok agent for Local SP access.
8
+ *
9
+ * This provider does not create ngrok resources or write Xpod DNS. A configured
10
+ * url can be a fixed ngrok dev domain or a paid custom domain already owned by
11
+ * the user's ngrok account. When url is omitted, the endpoint is discovered from
12
+ * the local ngrok Agent API after `ngrok http` starts.
13
+ */
14
+ class NgrokTunnelProvider {
15
+ constructor(options = {}) {
16
+ this.name = 'ngrok';
17
+ this.logger = (0, global_logger_factory_1.getLoggerFor)(this);
18
+ this.process = null;
19
+ this.status = {
20
+ running: false,
21
+ connected: false,
22
+ };
23
+ this.currentConfig = null;
24
+ this.managedByUs = false;
25
+ this.authtoken = options.authtoken;
26
+ this.configuredUrl = normalizeEndpointForCli(options.url);
27
+ this.ngrokPath = options.ngrokPath ?? 'ngrok';
28
+ this.agentApiUrl = options.agentApiUrl ?? 'http://127.0.0.1:4040';
29
+ this.connectTimeoutMs = options.connectTimeoutMs ?? 30_000;
30
+ }
31
+ async setup(options) {
32
+ const localProtocol = options.localProtocol ?? 'http';
33
+ const originUrl = `${localProtocol}://127.0.0.1:${options.localPort}`;
34
+ const config = {
35
+ subdomain: options.subdomain,
36
+ provider: 'ngrok',
37
+ endpoint: normalizeEndpointForConfig(this.configuredUrl) ?? '',
38
+ originUrl,
39
+ };
40
+ this.currentConfig = config;
41
+ return config;
42
+ }
43
+ async start(config) {
44
+ const actualConfig = config ?? this.currentConfig;
45
+ if (!actualConfig?.originUrl) {
46
+ throw new Error('ngrok originUrl is required');
47
+ }
48
+ if (this.process) {
49
+ this.logger.info('ngrok already running (managed by us)');
50
+ return;
51
+ }
52
+ const endpointForCli = normalizeEndpointForCli(actualConfig.endpoint) ?? this.configuredUrl;
53
+ const args = [
54
+ 'http',
55
+ '--log', 'stdout',
56
+ '--log-format', 'json',
57
+ ];
58
+ if (endpointForCli) {
59
+ args.push('--url', endpointForCli);
60
+ }
61
+ args.push(actualConfig.originUrl);
62
+ this.status = {
63
+ running: true,
64
+ connected: false,
65
+ endpoint: normalizeEndpointForConfig(endpointForCli),
66
+ };
67
+ this.currentConfig = {
68
+ ...actualConfig,
69
+ endpoint: this.status.endpoint ?? actualConfig.endpoint,
70
+ };
71
+ this.managedByUs = true;
72
+ const env = {
73
+ ...process.env,
74
+ ...(this.authtoken ? { NGROK_AUTHTOKEN: this.authtoken } : {}),
75
+ };
76
+ this.process = (0, node_child_process_1.spawn)(this.ngrokPath, args, {
77
+ stdio: ['ignore', 'pipe', 'pipe'],
78
+ env,
79
+ });
80
+ this.process.stdout?.on('data', (data) => this.handleOutput(data.toString()));
81
+ this.process.stderr?.on('data', (data) => this.handleOutput(data.toString()));
82
+ this.process.on('exit', (code) => {
83
+ this.logger.info(`ngrok exited with code ${code}`);
84
+ this.status = {
85
+ running: false,
86
+ connected: false,
87
+ endpoint: this.status.endpoint,
88
+ error: this.status.error ?? (code === 0 ? undefined : `ngrok exited with code ${code}`),
89
+ };
90
+ this.process = null;
91
+ this.managedByUs = false;
92
+ });
93
+ this.process.on('error', (error) => {
94
+ this.logger.error(`Failed to start ngrok: ${error.message}`);
95
+ this.status = {
96
+ running: false,
97
+ connected: false,
98
+ endpoint: this.status.endpoint,
99
+ error: error.message,
100
+ };
101
+ this.process = null;
102
+ this.managedByUs = false;
103
+ });
104
+ await this.waitForConnection();
105
+ }
106
+ async stop() {
107
+ if (!this.managedByUs) {
108
+ this.status = { running: false, connected: false, endpoint: this.status.endpoint };
109
+ return;
110
+ }
111
+ if (this.process) {
112
+ this.process.kill('SIGTERM');
113
+ await new Promise((resolve) => {
114
+ const timeout = setTimeout(() => {
115
+ this.process?.kill('SIGKILL');
116
+ resolve();
117
+ }, 5_000);
118
+ this.process?.on('exit', () => {
119
+ clearTimeout(timeout);
120
+ resolve();
121
+ });
122
+ });
123
+ this.process = null;
124
+ }
125
+ this.status = { running: false, connected: false, endpoint: this.status.endpoint };
126
+ this.managedByUs = false;
127
+ }
128
+ getStatus() {
129
+ return { ...this.status };
130
+ }
131
+ getEndpoint() {
132
+ return this.currentConfig?.endpoint || this.status.endpoint;
133
+ }
134
+ async cleanup(_config) {
135
+ await this.stop();
136
+ this.currentConfig = null;
137
+ }
138
+ isManagedByUs() {
139
+ return this.managedByUs;
140
+ }
141
+ handleOutput(raw) {
142
+ for (const line of raw.split('\n')) {
143
+ const output = line.trim();
144
+ if (!output) {
145
+ continue;
146
+ }
147
+ const endpoint = extractEndpoint(output);
148
+ if (endpoint) {
149
+ this.markConnected(endpoint);
150
+ }
151
+ else if (this.status.endpoint && isConnectionLine(output)) {
152
+ this.markConnected(this.status.endpoint);
153
+ }
154
+ const error = extractError(output);
155
+ if (error) {
156
+ this.status.error = mergeError(this.status.error, error);
157
+ }
158
+ }
159
+ }
160
+ markConnected(endpoint) {
161
+ const normalized = normalizeEndpointForConfig(endpoint);
162
+ if (!normalized) {
163
+ return;
164
+ }
165
+ this.status.connected = true;
166
+ this.status.endpoint = normalized;
167
+ this.status.lastHeartbeat = new Date();
168
+ this.currentConfig = {
169
+ ...(this.currentConfig ?? { subdomain: 'local', provider: 'ngrok', endpoint: normalized }),
170
+ provider: 'ngrok',
171
+ endpoint: normalized,
172
+ };
173
+ }
174
+ async waitForConnection() {
175
+ const startedAt = Date.now();
176
+ while (Date.now() - startedAt < this.connectTimeoutMs) {
177
+ if (this.status.connected) {
178
+ return;
179
+ }
180
+ if (!this.status.running && this.status.error) {
181
+ throw new Error(`ngrok failed to start: ${this.status.error}`);
182
+ }
183
+ const endpoint = await this.discoverEndpointFromAgentApi();
184
+ if (endpoint) {
185
+ this.markConnected(endpoint);
186
+ return;
187
+ }
188
+ await new Promise((resolve) => setTimeout(resolve, Math.min(250, Math.max(25, this.connectTimeoutMs))));
189
+ }
190
+ if (!this.status.running && this.status.error) {
191
+ throw new Error(`ngrok failed to start: ${this.status.error}`);
192
+ }
193
+ if (this.status.endpoint) {
194
+ this.logger.warn('ngrok connection was not confirmed before timeout; keeping configured endpoint as degraded.');
195
+ this.status.connected = false;
196
+ return;
197
+ }
198
+ throw new Error('ngrok connection timeout');
199
+ }
200
+ async discoverEndpointFromAgentApi() {
201
+ const base = this.agentApiUrl.replace(/\/+$/u, '');
202
+ return await readNgrokAgentEndpoint(`${base}/api/tunnels`)
203
+ ?? await readNgrokAgentEndpoint(`${base}/api/endpoints`);
204
+ }
205
+ }
206
+ exports.NgrokTunnelProvider = NgrokTunnelProvider;
207
+ async function readNgrokAgentEndpoint(url) {
208
+ try {
209
+ const response = await fetch(url);
210
+ if (!response.ok) {
211
+ return undefined;
212
+ }
213
+ const body = await response.json();
214
+ return extractEndpointFromAgentBody(body);
215
+ }
216
+ catch {
217
+ return undefined;
218
+ }
219
+ }
220
+ function extractEndpointFromAgentBody(body) {
221
+ if (!body || typeof body !== 'object') {
222
+ return undefined;
223
+ }
224
+ const record = body;
225
+ const lists = [record.tunnels, record.endpoints].filter(Array.isArray);
226
+ for (const list of lists) {
227
+ for (const item of list) {
228
+ if (!item || typeof item !== 'object') {
229
+ continue;
230
+ }
231
+ const value = item;
232
+ const publicUrl = typeof value.public_url === 'string' ? value.public_url : undefined;
233
+ const url = typeof value.url === 'string' ? value.url : undefined;
234
+ const endpoint = normalizeEndpointForConfig(publicUrl ?? url);
235
+ if (endpoint?.startsWith('https://')) {
236
+ return endpoint;
237
+ }
238
+ }
239
+ }
240
+ return undefined;
241
+ }
242
+ function extractEndpoint(output) {
243
+ try {
244
+ const parsed = JSON.parse(output);
245
+ const direct = typeof parsed.url === 'string' ? parsed.url : undefined;
246
+ const publicUrl = typeof parsed.public_url === 'string' ? parsed.public_url : undefined;
247
+ const msg = typeof parsed.msg === 'string' ? parsed.msg : undefined;
248
+ const endpoint = normalizeEndpointForConfig(direct ?? publicUrl) ?? (msg ? extractEndpointFromText(msg) : undefined);
249
+ if (endpoint) {
250
+ return endpoint;
251
+ }
252
+ }
253
+ catch {
254
+ // Fall through to text parsing.
255
+ }
256
+ return extractEndpointFromText(output);
257
+ }
258
+ function extractEndpointFromText(value) {
259
+ const match = value.match(/https:\/\/[^\s"'<>]+/u);
260
+ return normalizeEndpointForConfig(match?.[0]);
261
+ }
262
+ function isConnectionLine(output) {
263
+ const lower = output.toLowerCase();
264
+ return lower.includes('started tunnel')
265
+ || lower.includes('tunnel started')
266
+ || lower.includes('client session established')
267
+ || lower.includes('started');
268
+ }
269
+ function extractError(output) {
270
+ try {
271
+ const parsed = JSON.parse(output);
272
+ if (typeof parsed.err === 'string') {
273
+ const error = normalizeNgrokError(parsed.err);
274
+ if (error) {
275
+ return error;
276
+ }
277
+ }
278
+ const level = typeof parsed.lvl === 'string' ? parsed.lvl.toLowerCase() : '';
279
+ const message = typeof parsed.msg === 'string' ? parsed.msg.trim() : '';
280
+ if ((level === 'eror' || level === 'crit') && message) {
281
+ return message;
282
+ }
283
+ return undefined;
284
+ }
285
+ catch {
286
+ // Fall through to text parsing.
287
+ }
288
+ const lower = output.toLowerCase();
289
+ if (lower.includes('error') || lower.includes('failed') || lower.includes('err ') || lower.includes('err_')) {
290
+ return output;
291
+ }
292
+ return undefined;
293
+ }
294
+ function normalizeNgrokError(value) {
295
+ const trimmed = value.trim();
296
+ if (!trimmed) {
297
+ return undefined;
298
+ }
299
+ const lower = trimmed.toLowerCase();
300
+ if (lower === '<nil>' || lower === 'nil' || lower === 'null' || lower === 'undefined') {
301
+ return undefined;
302
+ }
303
+ return trimmed;
304
+ }
305
+ function mergeError(previous, next) {
306
+ if (!previous) {
307
+ return next;
308
+ }
309
+ if (/ERR_[A-Z0-9_]+/u.test(previous) && !/ERR_[A-Z0-9_]+/u.test(next)) {
310
+ return previous;
311
+ }
312
+ if (next.trim() === 'ERROR:' && previous.trim().length > next.trim().length) {
313
+ return previous;
314
+ }
315
+ return next.length > previous.length ? next : previous;
316
+ }
317
+ function normalizeEndpointForConfig(value) {
318
+ if (!value?.trim()) {
319
+ return undefined;
320
+ }
321
+ try {
322
+ const url = new URL(value.trim());
323
+ if (url.protocol !== 'https:' && url.protocol !== 'http:') {
324
+ return undefined;
325
+ }
326
+ if (isNgrokNonTunnelUrl(url)) {
327
+ return undefined;
328
+ }
329
+ return url.toString().replace(/\/+$/u, '') + '/';
330
+ }
331
+ catch {
332
+ return undefined;
333
+ }
334
+ }
335
+ function isNgrokNonTunnelUrl(url) {
336
+ return (url.hostname === 'ngrok.com' && url.pathname.startsWith('/docs/errors/'))
337
+ || url.hostname === 'dashboard.ngrok.com'
338
+ || url.hostname.endsWith('.ngrok-agent.com');
339
+ }
340
+ function normalizeEndpointForCli(value) {
341
+ const normalized = normalizeEndpointForConfig(value);
342
+ return normalized ? normalized.replace(/\/+$/u, '') : undefined;
343
+ }
344
+ //# sourceMappingURL=NgrokTunnelProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NgrokTunnelProvider.js","sourceRoot":"","sources":["../../src/tunnel/NgrokTunnelProvider.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn, 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\nexport interface NgrokTunnelProviderOptions {\n /** ngrok authtoken. Prefer local env/config; do not persist it from xpod. */\n authtoken?: string;\n\n /** Fixed ngrok endpoint/custom domain passed to `ngrok http --url`. */\n url?: string;\n\n /** ngrok executable path. */\n ngrokPath?: string;\n\n /** ngrok local Agent API, used to discover generated dev domains. */\n agentApiUrl?: string;\n\n /** Startup wait timeout. */\n connectTimeoutMs?: number;\n}\n\n/**\n * Starts a user-owned ngrok agent for Local SP access.\n *\n * This provider does not create ngrok resources or write Xpod DNS. A configured\n * url can be a fixed ngrok dev domain or a paid custom domain already owned by\n * the user's ngrok account. When url is omitted, the endpoint is discovered from\n * the local ngrok Agent API after `ngrok http` starts.\n */\nexport class NgrokTunnelProvider implements TunnelProvider {\n public readonly name = 'ngrok';\n private readonly logger = getLoggerFor(this);\n\n private readonly authtoken?: string;\n private readonly configuredUrl?: string;\n private readonly ngrokPath: string;\n private readonly agentApiUrl: string;\n private readonly connectTimeoutMs: number;\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 private managedByUs = false;\n\n public constructor(options: NgrokTunnelProviderOptions = {}) {\n this.authtoken = options.authtoken;\n this.configuredUrl = normalizeEndpointForCli(options.url);\n this.ngrokPath = options.ngrokPath ?? 'ngrok';\n this.agentApiUrl = options.agentApiUrl ?? 'http://127.0.0.1:4040';\n this.connectTimeoutMs = options.connectTimeoutMs ?? 30_000;\n }\n\n public async setup(options: TunnelSetupOptions): Promise<TunnelConfig> {\n const localProtocol = options.localProtocol ?? 'http';\n const originUrl = `${localProtocol}://127.0.0.1:${options.localPort}`;\n const config: TunnelConfig = {\n subdomain: options.subdomain,\n provider: 'ngrok',\n endpoint: normalizeEndpointForConfig(this.configuredUrl) ?? '',\n originUrl,\n };\n\n this.currentConfig = config;\n return config;\n }\n\n public async start(config?: TunnelConfig): Promise<void> {\n const actualConfig = config ?? this.currentConfig;\n if (!actualConfig?.originUrl) {\n throw new Error('ngrok originUrl is required');\n }\n\n if (this.process) {\n this.logger.info('ngrok already running (managed by us)');\n return;\n }\n\n const endpointForCli = normalizeEndpointForCli(actualConfig.endpoint) ?? this.configuredUrl;\n const args = [\n 'http',\n '--log', 'stdout',\n '--log-format', 'json',\n ];\n if (endpointForCli) {\n args.push('--url', endpointForCli);\n }\n args.push(actualConfig.originUrl);\n\n this.status = {\n running: true,\n connected: false,\n endpoint: normalizeEndpointForConfig(endpointForCli),\n };\n this.currentConfig = {\n ...actualConfig,\n endpoint: this.status.endpoint ?? actualConfig.endpoint,\n };\n this.managedByUs = true;\n\n const env = {\n ...process.env,\n ...(this.authtoken ? { NGROK_AUTHTOKEN: this.authtoken } : {}),\n };\n\n this.process = spawn(this.ngrokPath, args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n env,\n });\n\n this.process.stdout?.on('data', (data: Buffer) => this.handleOutput(data.toString()));\n this.process.stderr?.on('data', (data: Buffer) => this.handleOutput(data.toString()));\n\n this.process.on('exit', (code) => {\n this.logger.info(`ngrok exited with code ${code}`);\n this.status = {\n running: false,\n connected: false,\n endpoint: this.status.endpoint,\n error: this.status.error ?? (code === 0 ? undefined : `ngrok exited with code ${code}`),\n };\n this.process = null;\n this.managedByUs = false;\n });\n\n this.process.on('error', (error) => {\n this.logger.error(`Failed to start ngrok: ${error.message}`);\n this.status = {\n running: false,\n connected: false,\n endpoint: this.status.endpoint,\n error: error.message,\n };\n this.process = null;\n this.managedByUs = false;\n });\n\n await this.waitForConnection();\n }\n\n public async stop(): Promise<void> {\n if (!this.managedByUs) {\n this.status = { running: false, connected: false, endpoint: this.status.endpoint };\n return;\n }\n\n if (this.process) {\n this.process.kill('SIGTERM');\n await new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n this.process?.kill('SIGKILL');\n resolve();\n }, 5_000);\n this.process?.on('exit', () => {\n clearTimeout(timeout);\n resolve();\n });\n });\n this.process = null;\n }\n\n this.status = { running: false, connected: false, endpoint: this.status.endpoint };\n this.managedByUs = false;\n }\n\n public getStatus(): TunnelStatus {\n return { ...this.status };\n }\n\n public getEndpoint(): string | undefined {\n return this.currentConfig?.endpoint || this.status.endpoint;\n }\n\n public async cleanup(_config: TunnelConfig): Promise<void> {\n await this.stop();\n this.currentConfig = null;\n }\n\n public isManagedByUs(): boolean {\n return this.managedByUs;\n }\n\n private handleOutput(raw: string): void {\n for (const line of raw.split('\\n')) {\n const output = line.trim();\n if (!output) {\n continue;\n }\n\n const endpoint = extractEndpoint(output);\n if (endpoint) {\n this.markConnected(endpoint);\n } else if (this.status.endpoint && isConnectionLine(output)) {\n this.markConnected(this.status.endpoint);\n }\n\n const error = extractError(output);\n if (error) {\n this.status.error = mergeError(this.status.error, error);\n }\n }\n }\n\n private markConnected(endpoint: string): void {\n const normalized = normalizeEndpointForConfig(endpoint);\n if (!normalized) {\n return;\n }\n this.status.connected = true;\n this.status.endpoint = normalized;\n this.status.lastHeartbeat = new Date();\n this.currentConfig = {\n ...(this.currentConfig ?? { subdomain: 'local', provider: 'ngrok' as const, endpoint: normalized }),\n provider: 'ngrok',\n endpoint: normalized,\n };\n }\n\n private async waitForConnection(): Promise<void> {\n const startedAt = Date.now();\n while (Date.now() - startedAt < this.connectTimeoutMs) {\n if (this.status.connected) {\n return;\n }\n if (!this.status.running && this.status.error) {\n throw new Error(`ngrok failed to start: ${this.status.error}`);\n }\n\n const endpoint = await this.discoverEndpointFromAgentApi();\n if (endpoint) {\n this.markConnected(endpoint);\n return;\n }\n\n await new Promise((resolve) => setTimeout(resolve, Math.min(250, Math.max(25, this.connectTimeoutMs))));\n }\n\n if (!this.status.running && this.status.error) {\n throw new Error(`ngrok failed to start: ${this.status.error}`);\n }\n\n if (this.status.endpoint) {\n this.logger.warn('ngrok connection was not confirmed before timeout; keeping configured endpoint as degraded.');\n this.status.connected = false;\n return;\n }\n\n throw new Error('ngrok connection timeout');\n }\n\n private async discoverEndpointFromAgentApi(): Promise<string | undefined> {\n const base = this.agentApiUrl.replace(/\\/+$/u, '');\n return await readNgrokAgentEndpoint(`${base}/api/tunnels`)\n ?? await readNgrokAgentEndpoint(`${base}/api/endpoints`);\n }\n}\n\nasync function readNgrokAgentEndpoint(url: string): Promise<string | undefined> {\n try {\n const response = await fetch(url);\n if (!response.ok) {\n return undefined;\n }\n const body = await response.json() as unknown;\n return extractEndpointFromAgentBody(body);\n } catch {\n return undefined;\n }\n}\n\nfunction extractEndpointFromAgentBody(body: unknown): string | undefined {\n if (!body || typeof body !== 'object') {\n return undefined;\n }\n const record = body as Record<string, unknown>;\n const lists = [record.tunnels, record.endpoints].filter(Array.isArray) as unknown[][];\n for (const list of lists) {\n for (const item of list) {\n if (!item || typeof item !== 'object') {\n continue;\n }\n const value = item as Record<string, unknown>;\n const publicUrl = typeof value.public_url === 'string' ? value.public_url : undefined;\n const url = typeof value.url === 'string' ? value.url : undefined;\n const endpoint = normalizeEndpointForConfig(publicUrl ?? url);\n if (endpoint?.startsWith('https://')) {\n return endpoint;\n }\n }\n }\n return undefined;\n}\n\nfunction extractEndpoint(output: string): string | undefined {\n try {\n const parsed = JSON.parse(output) as Record<string, unknown>;\n const direct = typeof parsed.url === 'string' ? parsed.url : undefined;\n const publicUrl = typeof parsed.public_url === 'string' ? parsed.public_url : undefined;\n const msg = typeof parsed.msg === 'string' ? parsed.msg : undefined;\n const endpoint = normalizeEndpointForConfig(direct ?? publicUrl) ?? (msg ? extractEndpointFromText(msg) : undefined);\n if (endpoint) {\n return endpoint;\n }\n } catch {\n // Fall through to text parsing.\n }\n return extractEndpointFromText(output);\n}\n\nfunction extractEndpointFromText(value: string): string | undefined {\n const match = value.match(/https:\\/\\/[^\\s\"'<>]+/u);\n return normalizeEndpointForConfig(match?.[0]);\n}\n\nfunction isConnectionLine(output: string): boolean {\n const lower = output.toLowerCase();\n return lower.includes('started tunnel')\n || lower.includes('tunnel started')\n || lower.includes('client session established')\n || lower.includes('started');\n}\n\nfunction extractError(output: string): string | undefined {\n try {\n const parsed = JSON.parse(output) as Record<string, unknown>;\n if (typeof parsed.err === 'string') {\n const error = normalizeNgrokError(parsed.err);\n if (error) {\n return error;\n }\n }\n const level = typeof parsed.lvl === 'string' ? parsed.lvl.toLowerCase() : '';\n const message = typeof parsed.msg === 'string' ? parsed.msg.trim() : '';\n if ((level === 'eror' || level === 'crit') && message) {\n return message;\n }\n return undefined;\n } catch {\n // Fall through to text parsing.\n }\n\n const lower = output.toLowerCase();\n if (lower.includes('error') || lower.includes('failed') || lower.includes('err ') || lower.includes('err_')) {\n return output;\n }\n return undefined;\n}\n\nfunction normalizeNgrokError(value: string): string | undefined {\n const trimmed = value.trim();\n if (!trimmed) {\n return undefined;\n }\n const lower = trimmed.toLowerCase();\n if (lower === '<nil>' || lower === 'nil' || lower === 'null' || lower === 'undefined') {\n return undefined;\n }\n return trimmed;\n}\n\nfunction mergeError(previous: string | undefined, next: string): string {\n if (!previous) {\n return next;\n }\n if (/ERR_[A-Z0-9_]+/u.test(previous) && !/ERR_[A-Z0-9_]+/u.test(next)) {\n return previous;\n }\n if (next.trim() === 'ERROR:' && previous.trim().length > next.trim().length) {\n return previous;\n }\n return next.length > previous.length ? next : previous;\n}\n\nfunction normalizeEndpointForConfig(value: string | undefined): string | undefined {\n if (!value?.trim()) {\n return undefined;\n }\n try {\n const url = new URL(value.trim());\n if (url.protocol !== 'https:' && url.protocol !== 'http:') {\n return undefined;\n }\n if (isNgrokNonTunnelUrl(url)) {\n return undefined;\n }\n return url.toString().replace(/\\/+$/u, '') + '/';\n } catch {\n return undefined;\n }\n}\n\n\nfunction isNgrokNonTunnelUrl(url: URL): boolean {\n return (url.hostname === 'ngrok.com' && url.pathname.startsWith('/docs/errors/'))\n || url.hostname === 'dashboard.ngrok.com'\n || url.hostname.endsWith('.ngrok-agent.com');\n}\n\nfunction normalizeEndpointForCli(value: string | undefined): string | undefined {\n const normalized = normalizeEndpointForConfig(value);\n return normalized ? normalized.replace(/\\/+$/u, '') : undefined;\n}\n"]}
@@ -0,0 +1,215 @@
1
+ {
2
+ "@context": [
3
+ "https://linkedsoftwaredependencies.org/bundles/npm/@undefineds.co/xpod/^0.0.0/components/context.jsonld"
4
+ ],
5
+ "@id": "npmd:@undefineds.co/xpod",
6
+ "components": [
7
+ {
8
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider",
9
+ "@type": "Class",
10
+ "requireElement": "NgrokTunnelProvider",
11
+ "extends": [
12
+ "undefineds:dist/tunnel/TunnelProvider.jsonld#TunnelProvider"
13
+ ],
14
+ "comment": "Starts a user-owned ngrok agent for Local SP access. This provider does not create ngrok resources or write Xpod DNS. A configured url can be a fixed ngrok dev domain or a paid custom domain already owned by the user's ngrok account. When url is omitted, the endpoint is discovered from the local ngrok Agent API after `ngrok http` starts.",
15
+ "parameters": [
16
+ {
17
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider_options_authtoken",
18
+ "range": {
19
+ "@type": "ParameterRangeUnion",
20
+ "parameterRangeElements": [
21
+ "xsd:string",
22
+ {
23
+ "@type": "ParameterRangeUndefined"
24
+ }
25
+ ]
26
+ },
27
+ "comment": "ngrok authtoken. Prefer local env/config; do not persist it from xpod."
28
+ },
29
+ {
30
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider_options_url",
31
+ "range": {
32
+ "@type": "ParameterRangeUnion",
33
+ "parameterRangeElements": [
34
+ "xsd:string",
35
+ {
36
+ "@type": "ParameterRangeUndefined"
37
+ }
38
+ ]
39
+ },
40
+ "comment": "Fixed ngrok endpoint/custom domain passed to `ngrok http --url`."
41
+ },
42
+ {
43
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider_options_ngrokPath",
44
+ "range": {
45
+ "@type": "ParameterRangeUnion",
46
+ "parameterRangeElements": [
47
+ "xsd:string",
48
+ {
49
+ "@type": "ParameterRangeUndefined"
50
+ }
51
+ ]
52
+ },
53
+ "comment": "ngrok executable path."
54
+ },
55
+ {
56
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider_options_agentApiUrl",
57
+ "range": {
58
+ "@type": "ParameterRangeUnion",
59
+ "parameterRangeElements": [
60
+ "xsd:string",
61
+ {
62
+ "@type": "ParameterRangeUndefined"
63
+ }
64
+ ]
65
+ },
66
+ "comment": "ngrok local Agent API, used to discover generated dev domains."
67
+ },
68
+ {
69
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider_options_connectTimeoutMs",
70
+ "range": {
71
+ "@type": "ParameterRangeUnion",
72
+ "parameterRangeElements": [
73
+ "xsd:number",
74
+ {
75
+ "@type": "ParameterRangeUndefined"
76
+ }
77
+ ]
78
+ },
79
+ "comment": "Startup wait timeout."
80
+ }
81
+ ],
82
+ "memberFields": [
83
+ {
84
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_name",
85
+ "memberFieldName": "name"
86
+ },
87
+ {
88
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_logger",
89
+ "memberFieldName": "logger"
90
+ },
91
+ {
92
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_authtoken",
93
+ "memberFieldName": "authtoken"
94
+ },
95
+ {
96
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_configuredUrl",
97
+ "memberFieldName": "configuredUrl"
98
+ },
99
+ {
100
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_ngrokPath",
101
+ "memberFieldName": "ngrokPath"
102
+ },
103
+ {
104
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_agentApiUrl",
105
+ "memberFieldName": "agentApiUrl"
106
+ },
107
+ {
108
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_connectTimeoutMs",
109
+ "memberFieldName": "connectTimeoutMs"
110
+ },
111
+ {
112
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_process",
113
+ "memberFieldName": "process"
114
+ },
115
+ {
116
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_status",
117
+ "memberFieldName": "status"
118
+ },
119
+ {
120
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_currentConfig",
121
+ "memberFieldName": "currentConfig"
122
+ },
123
+ {
124
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_managedByUs",
125
+ "memberFieldName": "managedByUs"
126
+ },
127
+ {
128
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_constructor",
129
+ "memberFieldName": "constructor"
130
+ },
131
+ {
132
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_setup",
133
+ "memberFieldName": "setup"
134
+ },
135
+ {
136
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_start",
137
+ "memberFieldName": "start"
138
+ },
139
+ {
140
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_stop",
141
+ "memberFieldName": "stop"
142
+ },
143
+ {
144
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_getStatus",
145
+ "memberFieldName": "getStatus"
146
+ },
147
+ {
148
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_getEndpoint",
149
+ "memberFieldName": "getEndpoint"
150
+ },
151
+ {
152
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_cleanup",
153
+ "memberFieldName": "cleanup"
154
+ },
155
+ {
156
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_isManagedByUs",
157
+ "memberFieldName": "isManagedByUs"
158
+ },
159
+ {
160
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_handleOutput",
161
+ "memberFieldName": "handleOutput"
162
+ },
163
+ {
164
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_markConnected",
165
+ "memberFieldName": "markConnected"
166
+ },
167
+ {
168
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_waitForConnection",
169
+ "memberFieldName": "waitForConnection"
170
+ },
171
+ {
172
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider__member_discoverEndpointFromAgentApi",
173
+ "memberFieldName": "discoverEndpointFromAgentApi"
174
+ }
175
+ ],
176
+ "constructorArguments": [
177
+ {
178
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider_options__constructorArgument",
179
+ "fields": [
180
+ {
181
+ "keyRaw": "authtoken",
182
+ "value": {
183
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider_options_authtoken"
184
+ }
185
+ },
186
+ {
187
+ "keyRaw": "url",
188
+ "value": {
189
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider_options_url"
190
+ }
191
+ },
192
+ {
193
+ "keyRaw": "ngrokPath",
194
+ "value": {
195
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider_options_ngrokPath"
196
+ }
197
+ },
198
+ {
199
+ "keyRaw": "agentApiUrl",
200
+ "value": {
201
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider_options_agentApiUrl"
202
+ }
203
+ },
204
+ {
205
+ "keyRaw": "connectTimeoutMs",
206
+ "value": {
207
+ "@id": "undefineds:dist/tunnel/NgrokTunnelProvider.jsonld#NgrokTunnelProvider_options_connectTimeoutMs"
208
+ }
209
+ }
210
+ ]
211
+ }
212
+ ]
213
+ }
214
+ ]
215
+ }
@@ -12,12 +12,12 @@ export interface TunnelConfig {
12
12
  /** 子域名 (如 mynode,完整域名为 mynode.pods.undefieds.co) */
13
13
  subdomain: string;
14
14
  /** 隧道类型 */
15
- provider: 'cloudflare' | 'frp' | 'sakura-frp';
15
+ provider: 'cloudflare' | 'frp' | 'sakura-frp' | 'ngrok';
16
16
  /** 公网访问端点 (如 https://mynode.pods.undefieds.co) */
17
17
  endpoint: string;
18
18
  /** 本地 origin 地址 (如 http://localhost:5737),供隧道客户端转发 */
19
19
  originUrl?: string;
20
- /** Cloudflare Tunnel Token (cloudflare 专用) */
20
+ /** Provider token when the provider exposes a launch token (Cloudflare/SakuraFRP); ngrok authtoken is kept in local options, not persisted here. */
21
21
  tunnelToken?: string;
22
22
  /** Cloudflare Tunnel ID (cloudflare 专用) */
23
23
  tunnelId?: string;