tslocal 0.1.0
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/LICENSE +28 -0
- package/README.md +104 -0
- package/dist/index.cjs +695 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +744 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +744 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +651 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +43 -0
- package/ts/src/client.ts +191 -0
- package/ts/src/errors.ts +68 -0
- package/ts/src/index.ts +28 -0
- package/ts/src/json.ts +20 -0
- package/ts/src/safesocket.ts +93 -0
- package/ts/src/transport.ts +104 -0
- package/ts/src/types.ts +1009 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as http from "node:http";
|
|
2
|
+
import {
|
|
3
|
+
CURRENT_CAP_VERSION,
|
|
4
|
+
LOCAL_API_HOST,
|
|
5
|
+
type PortAndToken,
|
|
6
|
+
defaultSocketPath,
|
|
7
|
+
localTcpPortAndToken,
|
|
8
|
+
} from "./safesocket.js";
|
|
9
|
+
|
|
10
|
+
export interface TransportOptions {
|
|
11
|
+
socketPath?: string;
|
|
12
|
+
useSocketOnly?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Discover TCP port and token for this request.
|
|
17
|
+
*/
|
|
18
|
+
async function resolvePortAndToken(useSocketOnly: boolean): Promise<PortAndToken | undefined> {
|
|
19
|
+
if (useSocketOnly) return undefined;
|
|
20
|
+
return localTcpPortAndToken();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* HTTP transport that connects to tailscaled.
|
|
25
|
+
* Reuses connections via Node.js http.Agent keep-alive.
|
|
26
|
+
* Port and token are discovered per-request (matching Go's behavior),
|
|
27
|
+
* so the client adapts to daemon restarts and late starts.
|
|
28
|
+
*/
|
|
29
|
+
export class Transport {
|
|
30
|
+
private readonly socketPath: string;
|
|
31
|
+
private readonly useSocketOnly: boolean;
|
|
32
|
+
private readonly agent: http.Agent;
|
|
33
|
+
|
|
34
|
+
constructor(opts: TransportOptions = {}) {
|
|
35
|
+
this.socketPath = opts.socketPath ?? defaultSocketPath();
|
|
36
|
+
this.useSocketOnly = opts.useSocketOnly ?? false;
|
|
37
|
+
|
|
38
|
+
// Single agent with keep-alive — pools connections by host:port key
|
|
39
|
+
this.agent = new http.Agent({
|
|
40
|
+
keepAlive: true,
|
|
41
|
+
keepAliveMsecs: 60_000,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async request(
|
|
46
|
+
method: string,
|
|
47
|
+
path: string,
|
|
48
|
+
body?: Buffer | string,
|
|
49
|
+
extraHeaders?: Record<string, string>,
|
|
50
|
+
): Promise<{ status: number; body: Buffer; headers: http.IncomingHttpHeaders }> {
|
|
51
|
+
const portAndToken = await resolvePortAndToken(this.useSocketOnly);
|
|
52
|
+
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const headers: Record<string, string> = {
|
|
55
|
+
Host: LOCAL_API_HOST,
|
|
56
|
+
"Tailscale-Cap": String(CURRENT_CAP_VERSION),
|
|
57
|
+
...extraHeaders,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
if (portAndToken) {
|
|
61
|
+
const cred = Buffer.from(`:${portAndToken.token}`).toString("base64");
|
|
62
|
+
headers["Authorization"] = `Basic ${cred}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const options: http.RequestOptions = {
|
|
66
|
+
method,
|
|
67
|
+
path,
|
|
68
|
+
headers,
|
|
69
|
+
agent: this.agent,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (portAndToken) {
|
|
73
|
+
options.host = "127.0.0.1";
|
|
74
|
+
options.port = portAndToken.port;
|
|
75
|
+
} else {
|
|
76
|
+
options.socketPath = this.socketPath;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const req = http.request(options, (res) => {
|
|
80
|
+
const chunks: Buffer[] = [];
|
|
81
|
+
res.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
82
|
+
res.on("end", () => {
|
|
83
|
+
resolve({
|
|
84
|
+
status: res.statusCode ?? 0,
|
|
85
|
+
body: Buffer.concat(chunks),
|
|
86
|
+
headers: res.headers,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
res.on("error", reject);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
req.on("error", reject);
|
|
93
|
+
|
|
94
|
+
if (body !== undefined) {
|
|
95
|
+
req.write(body);
|
|
96
|
+
}
|
|
97
|
+
req.end();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
destroy(): void {
|
|
102
|
+
this.agent.destroy();
|
|
103
|
+
}
|
|
104
|
+
}
|