@hooklink/cli 0.1.68 → 0.1.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/agent/lease.test.d.ts +2 -0
- package/dist/__tests__/agent/lease.test.d.ts.map +1 -0
- package/dist/__tests__/agent/lease.test.js +31 -0
- package/dist/__tests__/agent/lease.test.js.map +1 -0
- package/dist/__tests__/agent/local-server.test.d.ts +9 -0
- package/dist/__tests__/agent/local-server.test.d.ts.map +1 -0
- package/dist/__tests__/agent/local-server.test.js +218 -0
- package/dist/__tests__/agent/local-server.test.js.map +1 -0
- package/dist/__tests__/agent/local-token.test.d.ts +2 -0
- package/dist/__tests__/agent/local-token.test.d.ts.map +1 -0
- package/dist/__tests__/agent/local-token.test.js +42 -0
- package/dist/__tests__/agent/local-token.test.js.map +1 -0
- package/dist/__tests__/agent/state.test.d.ts +2 -0
- package/dist/__tests__/agent/state.test.d.ts.map +1 -0
- package/dist/__tests__/agent/state.test.js +63 -0
- package/dist/__tests__/agent/state.test.js.map +1 -0
- package/dist/__tests__/agent/supervisor.test.d.ts +2 -0
- package/dist/__tests__/agent/supervisor.test.d.ts.map +1 -0
- package/dist/__tests__/agent/supervisor.test.js +126 -0
- package/dist/__tests__/agent/supervisor.test.js.map +1 -0
- package/dist/__tests__/json-events.test.d.ts +2 -0
- package/dist/__tests__/json-events.test.d.ts.map +1 -0
- package/dist/__tests__/json-events.test.js +101 -0
- package/dist/__tests__/json-events.test.js.map +1 -0
- package/dist/agent/endpoint-resolver.d.ts +35 -0
- package/dist/agent/endpoint-resolver.d.ts.map +1 -0
- package/dist/agent/endpoint-resolver.js +181 -0
- package/dist/agent/endpoint-resolver.js.map +1 -0
- package/dist/agent/lease.d.ts +18 -0
- package/dist/agent/lease.d.ts.map +1 -0
- package/dist/agent/lease.js +40 -0
- package/dist/agent/lease.js.map +1 -0
- package/dist/agent/local-server.d.ts +69 -0
- package/dist/agent/local-server.d.ts.map +1 -0
- package/dist/agent/local-server.js +426 -0
- package/dist/agent/local-server.js.map +1 -0
- package/dist/agent/local-token.d.ts +18 -0
- package/dist/agent/local-token.d.ts.map +1 -0
- package/dist/agent/local-token.js +65 -0
- package/dist/agent/local-token.js.map +1 -0
- package/dist/agent/paths.d.ts +12 -0
- package/dist/agent/paths.d.ts.map +1 -0
- package/dist/agent/paths.js +37 -0
- package/dist/agent/paths.js.map +1 -0
- package/dist/agent/preflight.d.ts +18 -0
- package/dist/agent/preflight.d.ts.map +1 -0
- package/dist/agent/preflight.js +87 -0
- package/dist/agent/preflight.js.map +1 -0
- package/dist/agent/resolve-bin.d.ts +26 -0
- package/dist/agent/resolve-bin.d.ts.map +1 -0
- package/dist/agent/resolve-bin.js +55 -0
- package/dist/agent/resolve-bin.js.map +1 -0
- package/dist/agent/service/darwin.d.ts +9 -0
- package/dist/agent/service/darwin.d.ts.map +1 -0
- package/dist/agent/service/darwin.js +85 -0
- package/dist/agent/service/darwin.js.map +1 -0
- package/dist/agent/service/index.d.ts +24 -0
- package/dist/agent/service/index.d.ts.map +1 -0
- package/dist/agent/service/index.js +71 -0
- package/dist/agent/service/index.js.map +1 -0
- package/dist/agent/service/linux.d.ts +10 -0
- package/dist/agent/service/linux.d.ts.map +1 -0
- package/dist/agent/service/linux.js +75 -0
- package/dist/agent/service/linux.js.map +1 -0
- package/dist/agent/service/win32.d.ts +9 -0
- package/dist/agent/service/win32.d.ts.map +1 -0
- package/dist/agent/service/win32.js +109 -0
- package/dist/agent/service/win32.js.map +1 -0
- package/dist/agent/state.d.ts +25 -0
- package/dist/agent/state.d.ts.map +1 -0
- package/dist/agent/state.js +63 -0
- package/dist/agent/state.js.map +1 -0
- package/dist/agent/supervisor.d.ts +116 -0
- package/dist/agent/supervisor.d.ts.map +1 -0
- package/dist/agent/supervisor.js +353 -0
- package/dist/agent/supervisor.js.map +1 -0
- package/dist/agent/zombie-cleanup.d.ts +20 -0
- package/dist/agent/zombie-cleanup.d.ts.map +1 -0
- package/dist/agent/zombie-cleanup.js +124 -0
- package/dist/agent/zombie-cleanup.js.map +1 -0
- package/dist/commands/agent.d.ts +12 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +173 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +106 -8
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +103 -4
- package/dist/commands/tunnel.js.map +1 -1
- package/dist/core/websocket-client.d.ts +2 -0
- package/dist/core/websocket-client.d.ts.map +1 -1
- package/dist/core/websocket-client.js +6 -0
- package/dist/core/websocket-client.js.map +1 -1
- package/dist/core/websocket-pool.d.ts +1 -0
- package/dist/core/websocket-pool.d.ts.map +1 -1
- package/dist/core/websocket-pool.js +3 -0
- package/dist/core/websocket-pool.js.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/json-events.d.ts +32 -0
- package/dist/utils/json-events.d.ts.map +1 -0
- package/dist/utils/json-events.js +71 -0
- package/dist/utils/json-events.js.map +1 -0
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +30 -0
- package/dist/utils/logger.js.map +1 -1
- package/package.json +17 -13
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const json_events_1 = require("../utils/json-events");
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
5
|
+
describe('json-events', () => {
|
|
6
|
+
let writeSpy;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
9
|
+
});
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
(0, json_events_1.setJsonMode)(false);
|
|
12
|
+
(0, json_events_1.stopAliveHeartbeat)();
|
|
13
|
+
writeSpy.mockRestore();
|
|
14
|
+
logger_1.Logger.setSilent(false);
|
|
15
|
+
});
|
|
16
|
+
it('is off by default', () => {
|
|
17
|
+
expect((0, json_events_1.isJsonMode)()).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
it('no-ops emit when json mode is off', () => {
|
|
20
|
+
(0, json_events_1.emitEvent)('starting');
|
|
21
|
+
expect(writeSpy).not.toHaveBeenCalled();
|
|
22
|
+
});
|
|
23
|
+
it('emits NDJSON with timestamp and event name when enabled', () => {
|
|
24
|
+
(0, json_events_1.setJsonMode)(true);
|
|
25
|
+
(0, json_events_1.emitEvent)('starting', { mode: 'connect' });
|
|
26
|
+
expect(writeSpy).toHaveBeenCalledTimes(1);
|
|
27
|
+
const line = writeSpy.mock.calls[0][0];
|
|
28
|
+
expect(line.endsWith('\n')).toBe(true);
|
|
29
|
+
const parsed = JSON.parse(line.trim());
|
|
30
|
+
expect(parsed.event).toBe('starting');
|
|
31
|
+
expect(parsed.mode).toBe('connect');
|
|
32
|
+
expect(typeof parsed.timestamp).toBe('string');
|
|
33
|
+
expect(Number.isNaN(Date.parse(parsed.timestamp))).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
it('silences Logger when json mode is on', () => {
|
|
36
|
+
const logSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
37
|
+
(0, json_events_1.setJsonMode)(true);
|
|
38
|
+
logger_1.Logger.info('hidden');
|
|
39
|
+
logger_1.Logger.success('hidden');
|
|
40
|
+
logger_1.Logger.log('hidden');
|
|
41
|
+
expect(logSpy).not.toHaveBeenCalled();
|
|
42
|
+
logSpy.mockRestore();
|
|
43
|
+
});
|
|
44
|
+
it('restores Logger output when json mode is turned off', () => {
|
|
45
|
+
const logSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
46
|
+
(0, json_events_1.setJsonMode)(true);
|
|
47
|
+
(0, json_events_1.setJsonMode)(false);
|
|
48
|
+
logger_1.Logger.info('visible');
|
|
49
|
+
expect(logSpy).toHaveBeenCalled();
|
|
50
|
+
logSpy.mockRestore();
|
|
51
|
+
});
|
|
52
|
+
it('startAliveHeartbeat emits alive on the interval', () => {
|
|
53
|
+
jest.useFakeTimers();
|
|
54
|
+
(0, json_events_1.setJsonMode)(true);
|
|
55
|
+
(0, json_events_1.startAliveHeartbeat)(50);
|
|
56
|
+
// No alive until the first tick
|
|
57
|
+
expect(writeSpy).not.toHaveBeenCalled();
|
|
58
|
+
jest.advanceTimersByTime(50);
|
|
59
|
+
expect(writeSpy).toHaveBeenCalledTimes(1);
|
|
60
|
+
const parsed = JSON.parse(writeSpy.mock.calls[0][0].trim());
|
|
61
|
+
expect(parsed.event).toBe('alive');
|
|
62
|
+
jest.advanceTimersByTime(50);
|
|
63
|
+
expect(writeSpy).toHaveBeenCalledTimes(2);
|
|
64
|
+
(0, json_events_1.stopAliveHeartbeat)();
|
|
65
|
+
jest.advanceTimersByTime(200);
|
|
66
|
+
expect(writeSpy).toHaveBeenCalledTimes(2);
|
|
67
|
+
jest.useRealTimers();
|
|
68
|
+
});
|
|
69
|
+
it('startAliveHeartbeat with extra callback emits usage alongside alive', () => {
|
|
70
|
+
jest.useFakeTimers();
|
|
71
|
+
(0, json_events_1.setJsonMode)(true);
|
|
72
|
+
(0, json_events_1.startAliveHeartbeat)(50, () => ({ requests: 7 }));
|
|
73
|
+
jest.advanceTimersByTime(50);
|
|
74
|
+
expect(writeSpy).toHaveBeenCalledTimes(2);
|
|
75
|
+
const first = JSON.parse(writeSpy.mock.calls[0][0].trim());
|
|
76
|
+
const second = JSON.parse(writeSpy.mock.calls[1][0].trim());
|
|
77
|
+
expect(first.event).toBe('alive');
|
|
78
|
+
expect(second.event).toBe('usage');
|
|
79
|
+
expect(second.requests).toBe(7);
|
|
80
|
+
(0, json_events_1.stopAliveHeartbeat)();
|
|
81
|
+
jest.useRealTimers();
|
|
82
|
+
});
|
|
83
|
+
it('does not start a second heartbeat while one is running', () => {
|
|
84
|
+
jest.useFakeTimers();
|
|
85
|
+
(0, json_events_1.setJsonMode)(true);
|
|
86
|
+
(0, json_events_1.startAliveHeartbeat)(50);
|
|
87
|
+
(0, json_events_1.startAliveHeartbeat)(50); // second call is a no-op
|
|
88
|
+
jest.advanceTimersByTime(50);
|
|
89
|
+
expect(writeSpy).toHaveBeenCalledTimes(1);
|
|
90
|
+
(0, json_events_1.stopAliveHeartbeat)();
|
|
91
|
+
jest.useRealTimers();
|
|
92
|
+
});
|
|
93
|
+
it('ignores startAliveHeartbeat when json mode is off', () => {
|
|
94
|
+
jest.useFakeTimers();
|
|
95
|
+
(0, json_events_1.startAliveHeartbeat)(50);
|
|
96
|
+
jest.advanceTimersByTime(200);
|
|
97
|
+
expect(writeSpy).not.toHaveBeenCalled();
|
|
98
|
+
jest.useRealTimers();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
//# sourceMappingURL=json-events.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-events.test.js","sourceRoot":"","sources":["../../src/__tests__/json-events.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find-or-create a tunnel-type endpoint against the Hooklink REST API
|
|
3
|
+
* on behalf of a paying user. The agent calls this when a plugin
|
|
4
|
+
* sends `POST /v1/tunnels` with an API key header.
|
|
5
|
+
*
|
|
6
|
+
* Matching key: `local_target_url` + `endpoint_type === 'tunnel'` +
|
|
7
|
+
* `is_active`. Simpler than tracking endpoint IDs and survives
|
|
8
|
+
* plugin reinstalls. See docs/planning/WP_TUNNEL_PLUGIN.md §
|
|
9
|
+
* "Internal endpoint management".
|
|
10
|
+
*/
|
|
11
|
+
import { AxiosInstance } from 'axios';
|
|
12
|
+
export interface ResolvedEndpoint {
|
|
13
|
+
id: string;
|
|
14
|
+
keyword: string;
|
|
15
|
+
local_target_url: string;
|
|
16
|
+
endpoint_type: string;
|
|
17
|
+
reused: boolean;
|
|
18
|
+
}
|
|
19
|
+
export type ResolveErrorCode = 'hooklink_auth_failed' | 'quota_exceeded' | 'keyword_collision' | 'target_port_rejected' | 'hooklink_unreachable' | 'invalid_request';
|
|
20
|
+
export declare class ResolveError extends Error {
|
|
21
|
+
code: ResolveErrorCode;
|
|
22
|
+
extra: Record<string, unknown>;
|
|
23
|
+
constructor(code: ResolveErrorCode, message: string, extra?: Record<string, unknown>);
|
|
24
|
+
}
|
|
25
|
+
export interface ResolveOptions {
|
|
26
|
+
apiKey: string;
|
|
27
|
+
apiUrl: string;
|
|
28
|
+
localTarget: string;
|
|
29
|
+
/** Override axios instance for tests. */
|
|
30
|
+
client?: AxiosInstance;
|
|
31
|
+
/** Max attempts to retry on transient network failures. */
|
|
32
|
+
maxRetries?: number;
|
|
33
|
+
}
|
|
34
|
+
export declare function resolveEndpoint(opts: ResolveOptions): Promise<ResolvedEndpoint>;
|
|
35
|
+
//# sourceMappingURL=endpoint-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-resolver.d.ts","sourceRoot":"","sources":["../../src/agent/endpoint-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAc,EAAc,aAAa,EAAE,MAAM,OAAO,CAAC;AAGzD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,gBAAgB,GACxB,sBAAsB,GACtB,gBAAgB,GAChB,mBAAmB,GACnB,sBAAsB,GACtB,sBAAsB,GACtB,iBAAiB,CAAC;AAEtB,qBAAa,YAAa,SAAQ,KAAK;IAE5B,IAAI,EAAE,gBAAgB;IAEtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBAF9B,IAAI,EAAE,gBAAgB,EAC7B,OAAO,EAAE,MAAM,EACR,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;CAK7C;AA+CD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,eAAe,CACnC,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,gBAAgB,CAAC,CAqE3B"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Find-or-create a tunnel-type endpoint against the Hooklink REST API
|
|
4
|
+
* on behalf of a paying user. The agent calls this when a plugin
|
|
5
|
+
* sends `POST /v1/tunnels` with an API key header.
|
|
6
|
+
*
|
|
7
|
+
* Matching key: `local_target_url` + `endpoint_type === 'tunnel'` +
|
|
8
|
+
* `is_active`. Simpler than tracking endpoint IDs and survives
|
|
9
|
+
* plugin reinstalls. See docs/planning/WP_TUNNEL_PLUGIN.md §
|
|
10
|
+
* "Internal endpoint management".
|
|
11
|
+
*/
|
|
12
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
|
+
};
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.ResolveError = void 0;
|
|
17
|
+
exports.resolveEndpoint = resolveEndpoint;
|
|
18
|
+
const axios_1 = __importDefault(require("axios"));
|
|
19
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
20
|
+
class ResolveError extends Error {
|
|
21
|
+
constructor(code, message, extra = {}) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.extra = extra;
|
|
25
|
+
this.name = 'ResolveError';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.ResolveError = ResolveError;
|
|
29
|
+
function randomBase32Suffix(len = 5) {
|
|
30
|
+
// Crockford-ish alphabet (no ambiguous 0/O, 1/I/L).
|
|
31
|
+
const alphabet = 'abcdefghjkmnpqrstuvwxyz23456789';
|
|
32
|
+
const bytes = crypto_1.default.randomBytes(len);
|
|
33
|
+
let out = '';
|
|
34
|
+
for (let i = 0; i < len; i++) {
|
|
35
|
+
out += alphabet[bytes[i] % alphabet.length];
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
function generateKeyword() {
|
|
40
|
+
return `wp-${randomBase32Suffix(5)}`;
|
|
41
|
+
}
|
|
42
|
+
function humanizeTarget(target) {
|
|
43
|
+
try {
|
|
44
|
+
const u = new URL(target);
|
|
45
|
+
const host = u.hostname;
|
|
46
|
+
const port = u.port ? `:${u.port}` : '';
|
|
47
|
+
return `Hooklink: ${host}${port}`;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return `Hooklink: ${target}`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function portFromTarget(target) {
|
|
54
|
+
try {
|
|
55
|
+
const u = new URL(target);
|
|
56
|
+
if (u.port)
|
|
57
|
+
return parseInt(u.port, 10);
|
|
58
|
+
return u.protocol === 'https:' ? 443 : 80;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return 80;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function resolveEndpoint(opts) {
|
|
65
|
+
const client = opts.client ??
|
|
66
|
+
axios_1.default.create({
|
|
67
|
+
baseURL: `${opts.apiUrl}/api`,
|
|
68
|
+
headers: {
|
|
69
|
+
'Content-Type': 'application/json',
|
|
70
|
+
Authorization: `Bearer ${opts.apiKey}`,
|
|
71
|
+
},
|
|
72
|
+
timeout: 10000,
|
|
73
|
+
});
|
|
74
|
+
// 1. Look up existing tunnel endpoints.
|
|
75
|
+
const existing = await withRetry(() => listTunnelEndpoints(client, opts.localTarget), opts.maxRetries ?? 2);
|
|
76
|
+
if (existing.length > 0) {
|
|
77
|
+
// Multiple matches: pick the most recently updated.
|
|
78
|
+
existing.sort((a, b) => (b.updated_at ?? '').localeCompare(a.updated_at ?? ''));
|
|
79
|
+
const match = existing[0];
|
|
80
|
+
return {
|
|
81
|
+
id: match.id,
|
|
82
|
+
keyword: match.keyword,
|
|
83
|
+
local_target_url: match.local_target_url ?? opts.localTarget,
|
|
84
|
+
endpoint_type: match.endpoint_type,
|
|
85
|
+
reused: true,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// 2. Create. Retry once on 409 (keyword collision).
|
|
89
|
+
const port = portFromTarget(opts.localTarget);
|
|
90
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
91
|
+
try {
|
|
92
|
+
const keyword = generateKeyword();
|
|
93
|
+
const payload = {
|
|
94
|
+
keyword,
|
|
95
|
+
name: humanizeTarget(opts.localTarget),
|
|
96
|
+
description: 'hooklink-agent',
|
|
97
|
+
local_target_url: opts.localTarget,
|
|
98
|
+
endpoint_type: 'tunnel',
|
|
99
|
+
allowed_ports: [port],
|
|
100
|
+
};
|
|
101
|
+
const { data } = await client.post('/endpoints', payload);
|
|
102
|
+
const ep = data.endpoint;
|
|
103
|
+
return {
|
|
104
|
+
id: ep.id,
|
|
105
|
+
keyword: ep.keyword,
|
|
106
|
+
local_target_url: ep.local_target_url ?? opts.localTarget,
|
|
107
|
+
endpoint_type: ep.endpoint_type,
|
|
108
|
+
reused: false,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
const mapped = mapError(err);
|
|
113
|
+
if (mapped.code === 'keyword_collision' && attempt === 0) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
throw mapped;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
throw new ResolveError('keyword_collision', 'Persistent keyword collision when creating endpoint.');
|
|
120
|
+
}
|
|
121
|
+
async function listTunnelEndpoints(client, localTarget) {
|
|
122
|
+
try {
|
|
123
|
+
const { data } = await client.get('/endpoints');
|
|
124
|
+
return (data.endpoints ?? []).filter((ep) => ep.endpoint_type === 'tunnel' &&
|
|
125
|
+
ep.is_active &&
|
|
126
|
+
ep.local_target_url === localTarget);
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
throw mapError(err);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function withRetry(fn, maxRetries) {
|
|
133
|
+
let lastErr;
|
|
134
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
135
|
+
try {
|
|
136
|
+
return await fn();
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
lastErr = err;
|
|
140
|
+
if (!(err instanceof ResolveError) || err.code !== 'hooklink_unreachable') {
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
if (attempt < maxRetries) {
|
|
144
|
+
await new Promise((r) => setTimeout(r, 300 * (attempt + 1)));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
throw lastErr;
|
|
149
|
+
}
|
|
150
|
+
function mapError(err) {
|
|
151
|
+
const axiosErr = err;
|
|
152
|
+
if (!axiosErr.response) {
|
|
153
|
+
return new ResolveError('hooklink_unreachable', axiosErr.message || 'Failed to reach Hooklink backend');
|
|
154
|
+
}
|
|
155
|
+
const status = axiosErr.response.status;
|
|
156
|
+
const body = axiosErr.response.data ?? {};
|
|
157
|
+
if (status === 401) {
|
|
158
|
+
return new ResolveError('hooklink_auth_failed', 'API key rejected');
|
|
159
|
+
}
|
|
160
|
+
if (status === 403) {
|
|
161
|
+
if (body.code === 'ENDPOINT_LIMIT_REACHED') {
|
|
162
|
+
return new ResolveError('quota_exceeded', body.message || 'Endpoint limit reached', { upstream_code: body.code });
|
|
163
|
+
}
|
|
164
|
+
return new ResolveError('quota_exceeded', body.message || body.error || 'Forbidden');
|
|
165
|
+
}
|
|
166
|
+
if (status === 409) {
|
|
167
|
+
return new ResolveError('keyword_collision', body.message || body.error || 'Keyword already taken');
|
|
168
|
+
}
|
|
169
|
+
if (status === 400) {
|
|
170
|
+
const msg = body.message || body.error || '';
|
|
171
|
+
if (/port/i.test(msg)) {
|
|
172
|
+
return new ResolveError('target_port_rejected', msg);
|
|
173
|
+
}
|
|
174
|
+
return new ResolveError('invalid_request', msg || 'Bad request');
|
|
175
|
+
}
|
|
176
|
+
if (status >= 500) {
|
|
177
|
+
return new ResolveError('hooklink_unreachable', body.message || `Backend returned ${status}`);
|
|
178
|
+
}
|
|
179
|
+
return new ResolveError('invalid_request', body.message || body.error || `Unexpected status ${status}`);
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=endpoint-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-resolver.js","sourceRoot":"","sources":["../../src/agent/endpoint-resolver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-tunnel lease with a 30s TTL refreshed on every `GET /v1/status`
|
|
3
|
+
* from the owner. If a plugin stops polling (tab closed, deactivation,
|
|
4
|
+
* network flap) the lease expires and the supervisor terminates the
|
|
5
|
+
* child. This is how the agent collects garbage without needing
|
|
6
|
+
* explicit DELETE calls.
|
|
7
|
+
*/
|
|
8
|
+
export declare class LeaseManager {
|
|
9
|
+
private leases;
|
|
10
|
+
private ttlMs;
|
|
11
|
+
constructor(ttlMs?: number);
|
|
12
|
+
refresh(tunnelId: string, now?: number): void;
|
|
13
|
+
release(tunnelId: string): void;
|
|
14
|
+
isAlive(tunnelId: string, now?: number): boolean;
|
|
15
|
+
getExpired(now?: number): string[];
|
|
16
|
+
has(tunnelId: string): boolean;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=lease.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lease.d.ts","sourceRoot":"","sources":["../../src/agent/lease.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,KAAK,CAAS;gBAEV,KAAK,SAAiB;IAIlC,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,IAAI;IAIjD,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAI/B,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,OAAO;IAKpD,UAAU,CAAC,GAAG,SAAa,GAAG,MAAM,EAAE;IAQtC,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;CAG/B"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Per-tunnel lease with a 30s TTL refreshed on every `GET /v1/status`
|
|
4
|
+
* from the owner. If a plugin stops polling (tab closed, deactivation,
|
|
5
|
+
* network flap) the lease expires and the supervisor terminates the
|
|
6
|
+
* child. This is how the agent collects garbage without needing
|
|
7
|
+
* explicit DELETE calls.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.LeaseManager = void 0;
|
|
11
|
+
const DEFAULT_TTL_MS = 30000;
|
|
12
|
+
class LeaseManager {
|
|
13
|
+
constructor(ttlMs = DEFAULT_TTL_MS) {
|
|
14
|
+
this.leases = new Map();
|
|
15
|
+
this.ttlMs = ttlMs;
|
|
16
|
+
}
|
|
17
|
+
refresh(tunnelId, now = Date.now()) {
|
|
18
|
+
this.leases.set(tunnelId, now + this.ttlMs);
|
|
19
|
+
}
|
|
20
|
+
release(tunnelId) {
|
|
21
|
+
this.leases.delete(tunnelId);
|
|
22
|
+
}
|
|
23
|
+
isAlive(tunnelId, now = Date.now()) {
|
|
24
|
+
const expires = this.leases.get(tunnelId);
|
|
25
|
+
return expires !== undefined && expires > now;
|
|
26
|
+
}
|
|
27
|
+
getExpired(now = Date.now()) {
|
|
28
|
+
const expired = [];
|
|
29
|
+
for (const [id, expires] of this.leases) {
|
|
30
|
+
if (expires <= now)
|
|
31
|
+
expired.push(id);
|
|
32
|
+
}
|
|
33
|
+
return expired;
|
|
34
|
+
}
|
|
35
|
+
has(tunnelId) {
|
|
36
|
+
return this.leases.has(tunnelId);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.LeaseManager = LeaseManager;
|
|
40
|
+
//# sourceMappingURL=lease.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lease.js","sourceRoot":"","sources":["../../src/agent/lease.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAEH,MAAM,cAAc,GAAG,KAAM,CAAC;AAE9B,MAAa,YAAY;IAIvB,YAAY,KAAK,GAAG,cAAc;QAH1B,WAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;QAIzC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,OAAO,CAAC,QAAgB,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QACxC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,CAAC,QAAgB;QACtB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,CAAC,QAAgB,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QACxC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC1C,OAAO,OAAO,KAAK,SAAS,IAAI,OAAO,GAAG,GAAG,CAAC;IAChD,CAAC;IAED,UAAU,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QACzB,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,KAAK,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACxC,IAAI,OAAO,IAAI,GAAG;gBAAE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACvC,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,GAAG,CAAC,QAAgB;QAClB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;CACF;AAhCD,oCAgCC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agent's localhost HTTP surface. Binds 127.0.0.1:7878 by default
|
|
3
|
+
* (override via HOOKLINK_AGENT_PORT). On port collision the server
|
|
4
|
+
* exits with an actionable error rather than silently falling back to
|
|
5
|
+
* a random port, because the WP plugin probes by fixed port and a
|
|
6
|
+
* random fallback would leave it unable to find us.
|
|
7
|
+
*
|
|
8
|
+
* Routes:
|
|
9
|
+
* GET /v1/health (no auth; used for discovery)
|
|
10
|
+
* GET /v1/status (local token)
|
|
11
|
+
* POST /v1/tunnels (local token)
|
|
12
|
+
* DELETE /v1/tunnels (local token)
|
|
13
|
+
* DELETE /v1/tunnels/:id (local token)
|
|
14
|
+
* GET /v1/tunnels/:id/recent (local token)
|
|
15
|
+
*
|
|
16
|
+
* `Authorization: Bearer <local-token>` gates every non-health route.
|
|
17
|
+
* Identity for tunnel ownership comes from either
|
|
18
|
+
* - `X-Hooklink-Api-Key` header (authenticated tunnels), or
|
|
19
|
+
* - `X-Hooklink-Owner-Id` header (trial tunnels, plugin-generated ULID).
|
|
20
|
+
*/
|
|
21
|
+
import { Supervisor } from './supervisor';
|
|
22
|
+
import { LeaseManager } from './lease';
|
|
23
|
+
import { resolveEndpoint } from './endpoint-resolver';
|
|
24
|
+
export interface LocalServerOptions {
|
|
25
|
+
port?: number;
|
|
26
|
+
host?: string;
|
|
27
|
+
localToken: string;
|
|
28
|
+
supervisor: Supervisor;
|
|
29
|
+
apiUrl: string;
|
|
30
|
+
version: string;
|
|
31
|
+
/** Inject a custom resolver for tests. */
|
|
32
|
+
resolver?: typeof resolveEndpoint;
|
|
33
|
+
}
|
|
34
|
+
export declare class LocalServer {
|
|
35
|
+
private server;
|
|
36
|
+
private port;
|
|
37
|
+
private host;
|
|
38
|
+
private supervisor;
|
|
39
|
+
private lease;
|
|
40
|
+
private localToken;
|
|
41
|
+
private apiUrl;
|
|
42
|
+
private version;
|
|
43
|
+
private resolver;
|
|
44
|
+
/** Track public_url per tunnel so authenticated-mode responses can
|
|
45
|
+
* include it even before the subprocess emits tunnel_ready. */
|
|
46
|
+
private publicUrlById;
|
|
47
|
+
constructor(opts: LocalServerOptions);
|
|
48
|
+
listen(): Promise<number>;
|
|
49
|
+
close(): Promise<void>;
|
|
50
|
+
private handle;
|
|
51
|
+
private authorize;
|
|
52
|
+
private ownerKey;
|
|
53
|
+
private isOwner;
|
|
54
|
+
private handleStatus;
|
|
55
|
+
private handleCreateTunnel;
|
|
56
|
+
private sendSpawnError;
|
|
57
|
+
private handleDeleteOne;
|
|
58
|
+
private handleDeleteAll;
|
|
59
|
+
private handleRecent;
|
|
60
|
+
private findExistingTunnel;
|
|
61
|
+
private sweepExpired;
|
|
62
|
+
private publicUrlFor;
|
|
63
|
+
private publicTunnel;
|
|
64
|
+
private sendJson;
|
|
65
|
+
/** Test-only hooks. */
|
|
66
|
+
getLeaseManager(): LeaseManager;
|
|
67
|
+
getPort(): number;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=local-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-server.d.ts","sourceRoot":"","sources":["../../src/agent/local-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,OAAO,EAAE,UAAU,EAAc,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGvC,OAAO,EACL,eAAe,EAGhB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,OAAO,eAAe,CAAC;CACnC;AAID,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAyB;IACzC;oEACgE;IAChE,OAAO,CAAC,aAAa,CAA6B;gBAEtC,IAAI,EAAE,kBAAkB;IA6B9B,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAsBzB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAId,MAAM;IAoCpB,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,QAAQ;IAchB,OAAO,CAAC,OAAO;IAMf,OAAO,CAAC,YAAY;YAaN,kBAAkB;IA+EhC,OAAO,CAAC,cAAc;YA6CR,eAAe;YAwBf,eAAe;IAgB7B,OAAO,CAAC,YAAY;IAwBpB,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,YAAY;IAkCpB,OAAO,CAAC,QAAQ;IAUhB,uBAAuB;IACvB,eAAe,IAAI,YAAY;IAG/B,OAAO,IAAI,MAAM;CAGlB"}
|