pipenet 1.3.0 → 1.4.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/README.md +43 -3
- package/dist/server/ClientManager.d.ts +11 -1
- package/dist/server/ClientManager.d.ts.map +1 -1
- package/dist/server/ClientManager.js +18 -1
- package/dist/server/ClientManager.js.map +1 -1
- package/dist/server/ClientManager.spec.js +8 -8
- package/dist/server/ClientManager.spec.js.map +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/server.d.ts +27 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +29 -7
- package/dist/server/server.js.map +1 -1
- package/dist/server/server.spec.js +104 -1
- package/dist/server/server.spec.js.map +1 -1
- package/package.json +1 -1
- package/src/server/ClientManager.spec.ts +8 -8
- package/src/server/ClientManager.ts +33 -1
- package/src/server/index.ts +2 -2
- package/src/server/server.spec.ts +133 -1
- package/src/server/server.ts +64 -8
package/README.md
CHANGED
|
@@ -6,8 +6,6 @@ Expose your local server to the public internet instantly
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install pipenet
|
|
9
|
-
# or
|
|
10
|
-
pnpm add pipenet
|
|
11
9
|
```
|
|
12
10
|
|
|
13
11
|
## CLI Usage
|
|
@@ -104,11 +102,22 @@ npx pipenet server --port 3000 --tunnel-port 3001 --domain tunnel.example.com
|
|
|
104
102
|
import { createServer } from 'pipenet/server';
|
|
105
103
|
|
|
106
104
|
const server = createServer({
|
|
107
|
-
domains: ['tunnel.example.com'],
|
|
105
|
+
domains: ['tunnel.example.com'], // Optional: custom domain(s)
|
|
108
106
|
secure: false, // Optional: require HTTPS
|
|
109
107
|
landing: 'https://pipenet.dev', // Optional: landing page URL
|
|
110
108
|
maxTcpSockets: 10, // Optional: max sockets per client
|
|
111
109
|
tunnelPort: 3001, // Optional: shared tunnel port for cloud deployments
|
|
110
|
+
|
|
111
|
+
// Lifecycle hooks for tracking tunnels and requests
|
|
112
|
+
onTunnelCreated: (tunnel) => {
|
|
113
|
+
console.log(`Tunnel created: ${tunnel.id} at ${tunnel.url}`);
|
|
114
|
+
},
|
|
115
|
+
onTunnelClosed: (tunnel) => {
|
|
116
|
+
console.log(`Tunnel closed: ${tunnel.id}`);
|
|
117
|
+
},
|
|
118
|
+
onRequest: (request) => {
|
|
119
|
+
console.log(`Request: ${request.method} ${request.path} via ${request.tunnelId}`);
|
|
120
|
+
},
|
|
112
121
|
});
|
|
113
122
|
|
|
114
123
|
// Start tunnel server if using shared tunnel port
|
|
@@ -129,6 +138,18 @@ server.listen(3000, () => {
|
|
|
129
138
|
- `maxTcpSockets` (number) Maximum number of TCP sockets per client (default: 10)
|
|
130
139
|
- `tunnelPort` (number) Shared tunnel port for cloud deployments (enables single-port mode)
|
|
131
140
|
|
|
141
|
+
### Server Hooks
|
|
142
|
+
|
|
143
|
+
The server supports lifecycle hooks for tracking tunnels and requests:
|
|
144
|
+
|
|
145
|
+
- `onTunnelCreated(tunnel)` - Called when a new tunnel is created. Receives `{ id, url, domain }`.
|
|
146
|
+
- `onTunnelClosed(tunnel)` - Called when a tunnel is closed. Receives `{ id, url, domain }`.
|
|
147
|
+
- `onRequest(request)` - Called when a request is proxied through a tunnel. Receives `{ method, path, tunnelId, headers, remoteAddress }`.
|
|
148
|
+
|
|
149
|
+
The `domain` field identifies which configured domain was used when the tunnel was created, which is useful when running a server with multiple domains.
|
|
150
|
+
|
|
151
|
+
The `onRequest` hook provides access to request headers and the client's remote IP address, useful for logging, rate limiting, or authentication.
|
|
152
|
+
|
|
132
153
|
### Server API Endpoints
|
|
133
154
|
|
|
134
155
|
- `GET /api/status` - Server status and tunnel count
|
|
@@ -172,6 +193,25 @@ pipenet was developed by [glama.ai](https://glama.ai) to enable local [Model Con
|
|
|
172
193
|
|
|
173
194
|
This capability is now integrated into [mcp-proxy](https://github.com/punkpeye/mcp-proxy).
|
|
174
195
|
|
|
196
|
+
## pipenet vs localtunnel
|
|
197
|
+
|
|
198
|
+
pipenet is a modernized fork of [localtunnel](https://github.com/localtunnel/localtunnel) with several improvements:
|
|
199
|
+
|
|
200
|
+
| Feature | pipenet | localtunnel |
|
|
201
|
+
| ------- | ------- | ----------- |
|
|
202
|
+
| Cloud deployment support | ✅ Single-port mode via `--tunnel-port` | ❌ Requires random ports |
|
|
203
|
+
| Multiple domains | ✅ `--domain` can be specified multiple times | ❌ Single domain only |
|
|
204
|
+
| TypeScript | ✅ Written in TypeScript with full type definitions | ❌ JavaScript only |
|
|
205
|
+
| ESM support | ✅ Native ES modules | ❌ CommonJS only |
|
|
206
|
+
| Active maintenance | ✅ Actively maintained | ⚠️ Limited maintenance |
|
|
207
|
+
| WebSocket support | ✅ Full WebSocket proxying | ✅ Full WebSocket proxying |
|
|
208
|
+
|
|
209
|
+
### Key Differences
|
|
210
|
+
|
|
211
|
+
**Cloud Deployment Support**: localtunnel creates a random TCP port for each tunnel client, which doesn't work in containerized environments like Docker, fly.io, or Kubernetes where only specific ports are exposed. pipenet solves this with the `--tunnel-port` option, enabling all clients to connect through a single shared port.
|
|
212
|
+
|
|
213
|
+
**Modern JavaScript**: pipenet uses ES modules and is written in TypeScript, providing better IDE support, type safety, and compatibility with modern JavaScript tooling.
|
|
214
|
+
|
|
175
215
|
## Acknowledgments
|
|
176
216
|
|
|
177
217
|
pipenet is based on [localtunnel](https://github.com/localtunnel/localtunnel).
|
|
@@ -2,24 +2,34 @@ import type { TunnelServer } from './TunnelServer.js';
|
|
|
2
2
|
import { Client } from './Client.js';
|
|
3
3
|
export interface ClientManagerOptions {
|
|
4
4
|
maxTcpSockets?: number;
|
|
5
|
+
onTunnelClosed?: (tunnel: TunnelInfo) => void;
|
|
6
|
+
onTunnelCreated?: (tunnel: TunnelInfo) => void;
|
|
5
7
|
tunnelServer?: TunnelServer;
|
|
6
8
|
}
|
|
7
9
|
export interface NewClientInfo {
|
|
10
|
+
domain: string;
|
|
8
11
|
id: string;
|
|
9
12
|
maxConnCount?: number;
|
|
10
13
|
port: number;
|
|
14
|
+
url: string;
|
|
15
|
+
}
|
|
16
|
+
export interface TunnelInfo {
|
|
17
|
+
domain: string;
|
|
18
|
+
id: string;
|
|
19
|
+
url: string;
|
|
11
20
|
}
|
|
12
21
|
export declare class ClientManager {
|
|
13
22
|
stats: {
|
|
14
23
|
tunnels: number;
|
|
15
24
|
};
|
|
16
25
|
private clients;
|
|
26
|
+
private clientTunnelInfo;
|
|
17
27
|
private log;
|
|
18
28
|
private opt;
|
|
19
29
|
constructor(opt?: ClientManagerOptions);
|
|
20
30
|
getClient(id: string): Client | undefined;
|
|
21
31
|
hasClient(id: string): boolean;
|
|
22
|
-
newClient(requestedId
|
|
32
|
+
newClient(requestedId: string | undefined, url: string, domain: string): Promise<NewClientInfo>;
|
|
23
33
|
removeClient(id: string): void;
|
|
24
34
|
}
|
|
25
35
|
//# sourceMappingURL=ClientManager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClientManager.d.ts","sourceRoot":"","sources":["../../src/server/ClientManager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ClientManager.d.ts","sourceRoot":"","sources":["../../src/server/ClientManager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IAC9C,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IAC/C,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACb;AAED,qBAAa,aAAa;IACjB,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAClC,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,gBAAgB,CAA+C;IACvE,OAAO,CAAC,GAAG,CAAiB;IAC5B,OAAO,CAAC,GAAG,CAAuB;gBAEtB,GAAG,GAAE,oBAAyB;IAQ1C,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIzC,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAIxB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IA8CrG,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;CAmB/B"}
|
|
@@ -5,11 +5,13 @@ import { TunnelAgent } from './TunnelAgent.js';
|
|
|
5
5
|
export class ClientManager {
|
|
6
6
|
stats;
|
|
7
7
|
clients;
|
|
8
|
+
clientTunnelInfo;
|
|
8
9
|
log;
|
|
9
10
|
opt;
|
|
10
11
|
constructor(opt = {}) {
|
|
11
12
|
this.opt = opt;
|
|
12
13
|
this.clients = new Map();
|
|
14
|
+
this.clientTunnelInfo = new Map();
|
|
13
15
|
this.stats = { tunnels: 0 };
|
|
14
16
|
this.log = debug('lt:ClientManager');
|
|
15
17
|
}
|
|
@@ -19,7 +21,7 @@ export class ClientManager {
|
|
|
19
21
|
hasClient(id) {
|
|
20
22
|
return this.clients.has(id);
|
|
21
23
|
}
|
|
22
|
-
async newClient(requestedId) {
|
|
24
|
+
async newClient(requestedId, url, domain) {
|
|
23
25
|
let id;
|
|
24
26
|
if (requestedId && !this.clients.has(requestedId)) {
|
|
25
27
|
id = requestedId;
|
|
@@ -35,16 +37,23 @@ export class ClientManager {
|
|
|
35
37
|
});
|
|
36
38
|
const client = new Client({ agent, id });
|
|
37
39
|
this.clients.set(id, client);
|
|
40
|
+
this.clientTunnelInfo.set(id, { domain, url });
|
|
38
41
|
client.once('close', () => {
|
|
39
42
|
this.removeClient(id);
|
|
40
43
|
});
|
|
41
44
|
try {
|
|
42
45
|
const info = await agent.listen();
|
|
43
46
|
++this.stats.tunnels;
|
|
47
|
+
// Call the onTunnelCreated hook
|
|
48
|
+
if (this.opt.onTunnelCreated) {
|
|
49
|
+
this.opt.onTunnelCreated({ domain, id, url });
|
|
50
|
+
}
|
|
44
51
|
return {
|
|
52
|
+
domain,
|
|
45
53
|
id: id,
|
|
46
54
|
maxConnCount: maxSockets,
|
|
47
55
|
port: info.port,
|
|
56
|
+
url,
|
|
48
57
|
};
|
|
49
58
|
}
|
|
50
59
|
catch (err) {
|
|
@@ -57,9 +66,17 @@ export class ClientManager {
|
|
|
57
66
|
const client = this.clients.get(id);
|
|
58
67
|
if (!client)
|
|
59
68
|
return;
|
|
69
|
+
const tunnelInfo = this.clientTunnelInfo.get(id);
|
|
70
|
+
const domain = tunnelInfo?.domain || '';
|
|
71
|
+
const url = tunnelInfo?.url || '';
|
|
60
72
|
--this.stats.tunnels;
|
|
61
73
|
this.clients.delete(id);
|
|
74
|
+
this.clientTunnelInfo.delete(id);
|
|
62
75
|
client.close();
|
|
76
|
+
// Call the onTunnelClosed hook
|
|
77
|
+
if (this.opt.onTunnelClosed) {
|
|
78
|
+
this.opt.onTunnelClosed({ domain, id, url });
|
|
79
|
+
}
|
|
63
80
|
}
|
|
64
81
|
}
|
|
65
82
|
//# sourceMappingURL=ClientManager.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClientManager.js","sourceRoot":"","sources":["../../src/server/ClientManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAIzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"ClientManager.js","sourceRoot":"","sources":["../../src/server/ClientManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,20 +8,20 @@ describe('ClientManager', () => {
|
|
|
8
8
|
});
|
|
9
9
|
it('should create a new client with random id', async () => {
|
|
10
10
|
const manager = new ClientManager();
|
|
11
|
-
const client = await manager.newClient();
|
|
11
|
+
const client = await manager.newClient(undefined, 'http://test.example.com', 'example.com');
|
|
12
12
|
expect(manager.hasClient(client.id)).toBe(true);
|
|
13
13
|
manager.removeClient(client.id);
|
|
14
14
|
});
|
|
15
15
|
it('should create a new client with id', async () => {
|
|
16
16
|
const manager = new ClientManager();
|
|
17
|
-
await manager.newClient('foobar');
|
|
17
|
+
await manager.newClient('foobar', 'http://foobar.example.com', 'example.com');
|
|
18
18
|
expect(manager.hasClient('foobar')).toBe(true);
|
|
19
19
|
manager.removeClient('foobar');
|
|
20
20
|
});
|
|
21
21
|
it('should create a new client with random id if previous exists', async () => {
|
|
22
22
|
const manager = new ClientManager();
|
|
23
|
-
const clientA = await manager.newClient('foobar');
|
|
24
|
-
const clientB = await manager.newClient('foobar');
|
|
23
|
+
const clientA = await manager.newClient('foobar', 'http://foobar.example.com', 'example.com');
|
|
24
|
+
const clientB = await manager.newClient('foobar', 'http://foobar.example.com', 'example.com');
|
|
25
25
|
expect(clientA.id).toBe('foobar');
|
|
26
26
|
expect(manager.hasClient(clientB.id)).toBe(true);
|
|
27
27
|
expect(clientB.id).not.toBe(clientA.id);
|
|
@@ -30,7 +30,7 @@ describe('ClientManager', () => {
|
|
|
30
30
|
});
|
|
31
31
|
it('should remove client once it goes offline', async () => {
|
|
32
32
|
const manager = new ClientManager();
|
|
33
|
-
const client = await manager.newClient('foobar');
|
|
33
|
+
const client = await manager.newClient('foobar', 'http://foobar.example.com', 'example.com');
|
|
34
34
|
const socket = await new Promise((resolve) => {
|
|
35
35
|
const netClient = net.createConnection({ port: client.port }, () => {
|
|
36
36
|
resolve(netClient);
|
|
@@ -47,8 +47,8 @@ describe('ClientManager', () => {
|
|
|
47
47
|
}, 5000);
|
|
48
48
|
it('should remove correct client once it goes offline', async () => {
|
|
49
49
|
const manager = new ClientManager();
|
|
50
|
-
const clientFoo = await manager.newClient('foo');
|
|
51
|
-
await manager.newClient('bar');
|
|
50
|
+
const clientFoo = await manager.newClient('foo', 'http://foo.example.com', 'example.com');
|
|
51
|
+
await manager.newClient('bar', 'http://bar.example.com', 'example.com');
|
|
52
52
|
const socket = await new Promise((resolve) => {
|
|
53
53
|
const netClient = net.createConnection({ port: clientFoo.port }, () => {
|
|
54
54
|
resolve(netClient);
|
|
@@ -64,7 +64,7 @@ describe('ClientManager', () => {
|
|
|
64
64
|
}, 5000);
|
|
65
65
|
it('should remove clients if they do not connect within 5 seconds', async () => {
|
|
66
66
|
const manager = new ClientManager();
|
|
67
|
-
await manager.newClient('foo');
|
|
67
|
+
await manager.newClient('foo', 'http://foo.example.com', 'example.com');
|
|
68
68
|
expect(manager.hasClient('foo')).toBe(true);
|
|
69
69
|
// wait past grace period (1s)
|
|
70
70
|
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClientManager.spec.js","sourceRoot":"","sources":["../../src/server/ClientManager.spec.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,OAAO,GAAG,IAAI,aAAa,EAAE,CAAC;QACpC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QACzD,MAAM,OAAO,GAAG,IAAI,aAAa,EAAE,CAAC;QACpC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"ClientManager.spec.js","sourceRoot":"","sources":["../../src/server/ClientManager.spec.ts"],"names":[],"mappings":"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"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { Client, type ClientOptions } from './Client.js';
|
|
2
|
-
export { ClientManager, type ClientManagerOptions, type NewClientInfo } from './ClientManager.js';
|
|
3
|
-
export { createServer, type PipenetServer, type ServerOptions } from './server.js';
|
|
2
|
+
export { ClientManager, type ClientManagerOptions, type NewClientInfo, type TunnelInfo } from './ClientManager.js';
|
|
3
|
+
export { createServer, type PipenetServer, type RequestInfo, type ServerHooks, type ServerOptions } from './server.js';
|
|
4
4
|
export { TunnelAgent, type TunnelAgentListenInfo, type TunnelAgentOptions, type TunnelAgentStats } from './TunnelAgent.js';
|
|
5
5
|
export { type SocketHandler, TunnelServer } from './TunnelServer.js';
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACnH,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACvH,OAAO,EAAE,WAAW,EAAE,KAAK,qBAAqB,EAAE,KAAK,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAC3H,OAAO,EAAE,KAAK,aAAa,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAsB,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAsB,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,aAAa,EAAkE,MAAM,oBAAoB,CAAC;AACnH,OAAO,EAAE,YAAY,EAA8E,MAAM,aAAa,CAAC;AACvH,OAAO,EAAE,WAAW,EAA8E,MAAM,kBAAkB,CAAC;AAC3H,OAAO,EAAsB,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/server/server.d.ts
CHANGED
|
@@ -3,12 +3,38 @@ import { TunnelServer } from './TunnelServer.js';
|
|
|
3
3
|
export interface PipenetServer extends http.Server {
|
|
4
4
|
tunnelServer?: TunnelServer;
|
|
5
5
|
}
|
|
6
|
-
export interface
|
|
6
|
+
export interface RequestInfo {
|
|
7
|
+
headers: Record<string, string | string[] | undefined>;
|
|
8
|
+
method: string;
|
|
9
|
+
path: string;
|
|
10
|
+
remoteAddress?: string;
|
|
11
|
+
tunnelId: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ServerHooks {
|
|
14
|
+
/**
|
|
15
|
+
* Called when a request is proxied through a tunnel
|
|
16
|
+
*/
|
|
17
|
+
onRequest?: (request: RequestInfo) => void;
|
|
18
|
+
/**
|
|
19
|
+
* Called when a tunnel is closed
|
|
20
|
+
*/
|
|
21
|
+
onTunnelClosed?: (tunnel: TunnelInfo) => void;
|
|
22
|
+
/**
|
|
23
|
+
* Called when a new tunnel is created
|
|
24
|
+
*/
|
|
25
|
+
onTunnelCreated?: (tunnel: TunnelInfo) => void;
|
|
26
|
+
}
|
|
27
|
+
export interface ServerOptions extends ServerHooks {
|
|
7
28
|
domains?: string[];
|
|
8
29
|
landing?: string;
|
|
9
30
|
maxTcpSockets?: number;
|
|
10
31
|
secure?: boolean;
|
|
11
32
|
tunnelPort?: number;
|
|
12
33
|
}
|
|
34
|
+
export interface TunnelInfo {
|
|
35
|
+
domain: string;
|
|
36
|
+
id: string;
|
|
37
|
+
url: string;
|
|
38
|
+
}
|
|
13
39
|
export declare function createServer(opt?: ServerOptions): PipenetServer;
|
|
14
40
|
//# sourceMappingURL=server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,MAAM,CAAC;AAOxB,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAIjD,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,MAAM;IAChD,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,MAAM,CAAC;AAOxB,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAIjD,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,MAAM;IAChD,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAE3C;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IAE9C;;OAEG;IACH,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;CAChD;AAED,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,YAAY,CAAC,GAAG,GAAE,aAAkB,GAAG,aAAa,CAmMnE"}
|
package/dist/server/server.js
CHANGED
|
@@ -56,10 +56,18 @@ export function createServer(opt = {}) {
|
|
|
56
56
|
});
|
|
57
57
|
app.use(router.routes());
|
|
58
58
|
app.use(router.allowedMethods());
|
|
59
|
+
// Helper to extract domain from host (strips port if present)
|
|
60
|
+
const getDomain = (host) => {
|
|
61
|
+
// Remove port if present (e.g., "localhost:3000" -> "localhost")
|
|
62
|
+
return host.split(':')[0];
|
|
63
|
+
};
|
|
64
|
+
// Helper to build the tunnel URL
|
|
65
|
+
const buildUrl = (id, host) => {
|
|
66
|
+
return schema + '://' + id + '.' + host;
|
|
67
|
+
};
|
|
59
68
|
// Helper to build response with tunnel port if configured
|
|
60
|
-
const buildResponse = (info
|
|
61
|
-
const
|
|
62
|
-
const response = { ...info, url };
|
|
69
|
+
const buildResponse = (info) => {
|
|
70
|
+
const response = { ...info };
|
|
63
71
|
// If using shared tunnel server, override port and add sharedTunnel flag
|
|
64
72
|
if (tunnelPort) {
|
|
65
73
|
response.port = tunnelPort;
|
|
@@ -76,9 +84,11 @@ export function createServer(opt = {}) {
|
|
|
76
84
|
const isNewClientRequest = ctx.query['new'] !== undefined;
|
|
77
85
|
if (isNewClientRequest) {
|
|
78
86
|
const reqId = hri.random();
|
|
87
|
+
const domain = getDomain(ctx.request.host);
|
|
88
|
+
const url = buildUrl(reqId, ctx.request.host);
|
|
79
89
|
log('making new client with id %s', reqId);
|
|
80
|
-
const info = await manager.newClient(reqId);
|
|
81
|
-
ctx.body = buildResponse(info
|
|
90
|
+
const info = await manager.newClient(reqId, url, domain);
|
|
91
|
+
ctx.body = buildResponse(info);
|
|
82
92
|
return;
|
|
83
93
|
}
|
|
84
94
|
ctx.redirect(landingPage);
|
|
@@ -96,9 +106,11 @@ export function createServer(opt = {}) {
|
|
|
96
106
|
ctx.body = { message: msg };
|
|
97
107
|
return;
|
|
98
108
|
}
|
|
109
|
+
const domain = getDomain(ctx.request.host);
|
|
110
|
+
const url = buildUrl(reqId, ctx.request.host);
|
|
99
111
|
log('making new client with id %s', reqId);
|
|
100
|
-
const info = await manager.newClient(reqId);
|
|
101
|
-
ctx.body = buildResponse(info
|
|
112
|
+
const info = await manager.newClient(reqId, url, domain);
|
|
113
|
+
ctx.body = buildResponse(info);
|
|
102
114
|
});
|
|
103
115
|
const server = http.createServer();
|
|
104
116
|
server.tunnelServer = tunnelServer;
|
|
@@ -121,6 +133,16 @@ export function createServer(opt = {}) {
|
|
|
121
133
|
res.end('404');
|
|
122
134
|
return;
|
|
123
135
|
}
|
|
136
|
+
// Call the onRequest hook
|
|
137
|
+
if (opt.onRequest) {
|
|
138
|
+
opt.onRequest({
|
|
139
|
+
headers: req.headers,
|
|
140
|
+
method: req.method || 'GET',
|
|
141
|
+
path: req.url || '/',
|
|
142
|
+
remoteAddress: req.socket.remoteAddress,
|
|
143
|
+
tunnelId: clientId,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
124
146
|
client.handleRequest(req, res);
|
|
125
147
|
});
|
|
126
148
|
server.on('upgrade', (req, socket) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACzC,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,MAAM,MAAM,YAAY,CAAC;AAChC,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,GAAG,GAAG,KAAK,CAAC,gBAAgB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import net from 'net';
|
|
2
2
|
import request from 'supertest';
|
|
3
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
4
|
import { WebSocket, WebSocketServer } from 'ws';
|
|
5
5
|
import { createServer } from './server.js';
|
|
6
6
|
describe('Server', () => {
|
|
@@ -109,5 +109,108 @@ describe('Server', () => {
|
|
|
109
109
|
expect(res.statusCode).toBe(204);
|
|
110
110
|
expect(res.headers['access-control-allow-origin']).toBe('*');
|
|
111
111
|
});
|
|
112
|
+
describe('hooks', () => {
|
|
113
|
+
it('should call onTunnelCreated when a tunnel is created', async () => {
|
|
114
|
+
const onTunnelCreated = vi.fn();
|
|
115
|
+
const server = createServer({ onTunnelCreated });
|
|
116
|
+
await new Promise(resolve => server.listen(resolve));
|
|
117
|
+
await request(server).get('/test-tunnel');
|
|
118
|
+
expect(onTunnelCreated).toHaveBeenCalledTimes(1);
|
|
119
|
+
expect(onTunnelCreated).toHaveBeenCalledWith(expect.objectContaining({
|
|
120
|
+
domain: expect.any(String),
|
|
121
|
+
id: 'test-tunnel',
|
|
122
|
+
url: expect.stringContaining('test-tunnel'),
|
|
123
|
+
}));
|
|
124
|
+
await new Promise(resolve => server.close(() => resolve()));
|
|
125
|
+
});
|
|
126
|
+
it('should include domain in tunnel hooks', async () => {
|
|
127
|
+
const onTunnelCreated = vi.fn();
|
|
128
|
+
const server = createServer({
|
|
129
|
+
domains: ['tunnel.example.com'],
|
|
130
|
+
onTunnelCreated,
|
|
131
|
+
});
|
|
132
|
+
await new Promise(resolve => server.listen(resolve));
|
|
133
|
+
await request(server)
|
|
134
|
+
.get('/domain-test')
|
|
135
|
+
.set('Host', 'tunnel.example.com');
|
|
136
|
+
expect(onTunnelCreated).toHaveBeenCalledTimes(1);
|
|
137
|
+
expect(onTunnelCreated).toHaveBeenCalledWith({
|
|
138
|
+
domain: 'tunnel.example.com',
|
|
139
|
+
id: 'domain-test',
|
|
140
|
+
url: 'http://domain-test.tunnel.example.com',
|
|
141
|
+
});
|
|
142
|
+
await new Promise(resolve => server.close(() => resolve()));
|
|
143
|
+
});
|
|
144
|
+
it('should call onTunnelClosed when a tunnel is closed', async () => {
|
|
145
|
+
const onTunnelCreated = vi.fn();
|
|
146
|
+
const onTunnelClosed = vi.fn();
|
|
147
|
+
const server = createServer({ onTunnelClosed, onTunnelCreated });
|
|
148
|
+
await new Promise(resolve => server.listen(resolve));
|
|
149
|
+
const res = await request(server).get('/close-test');
|
|
150
|
+
const localTunnelPort = res.body.port;
|
|
151
|
+
// Connect a socket to activate the tunnel
|
|
152
|
+
const socket = net.createConnection({ port: localTunnelPort });
|
|
153
|
+
await new Promise(resolve => socket.once('connect', resolve));
|
|
154
|
+
expect(onTunnelCreated).toHaveBeenCalledTimes(1);
|
|
155
|
+
expect(onTunnelClosed).not.toHaveBeenCalled();
|
|
156
|
+
// Close the socket to trigger tunnel close
|
|
157
|
+
socket.destroy();
|
|
158
|
+
// Wait for the close event to propagate (Client has a 1s grace timeout after offline)
|
|
159
|
+
await new Promise(resolve => setTimeout(resolve, 1200));
|
|
160
|
+
expect(onTunnelClosed).toHaveBeenCalledTimes(1);
|
|
161
|
+
expect(onTunnelClosed).toHaveBeenCalledWith(expect.objectContaining({
|
|
162
|
+
domain: expect.any(String),
|
|
163
|
+
id: 'close-test',
|
|
164
|
+
url: expect.stringContaining('close-test'),
|
|
165
|
+
}));
|
|
166
|
+
await new Promise(resolve => server.close(() => resolve()));
|
|
167
|
+
}, 5000);
|
|
168
|
+
it('should call onRequest when a request is proxied', async () => {
|
|
169
|
+
const onRequest = vi.fn();
|
|
170
|
+
const server = createServer({
|
|
171
|
+
domains: ['example.com'],
|
|
172
|
+
onRequest,
|
|
173
|
+
});
|
|
174
|
+
await new Promise(resolve => server.listen(resolve));
|
|
175
|
+
// Create a tunnel
|
|
176
|
+
const res = await request(server).get('/request-test');
|
|
177
|
+
const localTunnelPort = res.body.port;
|
|
178
|
+
// Create a simple echo server
|
|
179
|
+
const echoServer = net.createServer((socket) => {
|
|
180
|
+
socket.on('data', () => {
|
|
181
|
+
const response = 'HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK';
|
|
182
|
+
socket.write(response);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
await new Promise(resolve => echoServer.listen(0, resolve));
|
|
186
|
+
const echoPort = echoServer.address().port;
|
|
187
|
+
// Connect tunnel socket to echo server
|
|
188
|
+
const ltSocket = net.createConnection({ port: localTunnelPort });
|
|
189
|
+
const echoSocket = net.createConnection({ port: echoPort });
|
|
190
|
+
await Promise.all([
|
|
191
|
+
new Promise(resolve => ltSocket.once('connect', resolve)),
|
|
192
|
+
new Promise(resolve => echoSocket.once('connect', resolve)),
|
|
193
|
+
]);
|
|
194
|
+
ltSocket.pipe(echoSocket).pipe(ltSocket);
|
|
195
|
+
// Make a request through the tunnel
|
|
196
|
+
await request(server)
|
|
197
|
+
.get('/some/path')
|
|
198
|
+
.set('Host', 'request-test.example.com');
|
|
199
|
+
expect(onRequest).toHaveBeenCalledTimes(1);
|
|
200
|
+
expect(onRequest).toHaveBeenCalledWith({
|
|
201
|
+
headers: expect.objectContaining({
|
|
202
|
+
host: 'request-test.example.com',
|
|
203
|
+
}),
|
|
204
|
+
method: 'GET',
|
|
205
|
+
path: '/some/path',
|
|
206
|
+
remoteAddress: expect.any(String),
|
|
207
|
+
tunnelId: 'request-test',
|
|
208
|
+
});
|
|
209
|
+
ltSocket.destroy();
|
|
210
|
+
echoSocket.destroy();
|
|
211
|
+
echoServer.close();
|
|
212
|
+
await new Promise(resolve => server.close(() => resolve()));
|
|
213
|
+
});
|
|
214
|
+
});
|
|
112
215
|
});
|
|
113
216
|
//# sourceMappingURL=server.spec.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.spec.js","sourceRoot":"","sources":["../../src/server/server.spec.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,OAAO,MAAM,WAAW,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"server.spec.js","sourceRoot":"","sources":["../../src/server/server.spec.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -11,22 +11,22 @@ describe('ClientManager', () => {
|
|
|
11
11
|
|
|
12
12
|
it('should create a new client with random id', async () => {
|
|
13
13
|
const manager = new ClientManager();
|
|
14
|
-
const client = await manager.newClient();
|
|
14
|
+
const client = await manager.newClient(undefined, 'http://test.example.com', 'example.com');
|
|
15
15
|
expect(manager.hasClient(client.id)).toBe(true);
|
|
16
16
|
manager.removeClient(client.id);
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
it('should create a new client with id', async () => {
|
|
20
20
|
const manager = new ClientManager();
|
|
21
|
-
await manager.newClient('foobar');
|
|
21
|
+
await manager.newClient('foobar', 'http://foobar.example.com', 'example.com');
|
|
22
22
|
expect(manager.hasClient('foobar')).toBe(true);
|
|
23
23
|
manager.removeClient('foobar');
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
it('should create a new client with random id if previous exists', async () => {
|
|
27
27
|
const manager = new ClientManager();
|
|
28
|
-
const clientA = await manager.newClient('foobar');
|
|
29
|
-
const clientB = await manager.newClient('foobar');
|
|
28
|
+
const clientA = await manager.newClient('foobar', 'http://foobar.example.com', 'example.com');
|
|
29
|
+
const clientB = await manager.newClient('foobar', 'http://foobar.example.com', 'example.com');
|
|
30
30
|
expect(clientA.id).toBe('foobar');
|
|
31
31
|
expect(manager.hasClient(clientB.id)).toBe(true);
|
|
32
32
|
expect(clientB.id).not.toBe(clientA.id);
|
|
@@ -36,7 +36,7 @@ describe('ClientManager', () => {
|
|
|
36
36
|
|
|
37
37
|
it('should remove client once it goes offline', async () => {
|
|
38
38
|
const manager = new ClientManager();
|
|
39
|
-
const client = await manager.newClient('foobar');
|
|
39
|
+
const client = await manager.newClient('foobar', 'http://foobar.example.com', 'example.com');
|
|
40
40
|
|
|
41
41
|
const socket = await new Promise<net.Socket>((resolve) => {
|
|
42
42
|
const netClient = net.createConnection({ port: client.port }, () => {
|
|
@@ -57,8 +57,8 @@ describe('ClientManager', () => {
|
|
|
57
57
|
|
|
58
58
|
it('should remove correct client once it goes offline', async () => {
|
|
59
59
|
const manager = new ClientManager();
|
|
60
|
-
const clientFoo = await manager.newClient('foo');
|
|
61
|
-
await manager.newClient('bar');
|
|
60
|
+
const clientFoo = await manager.newClient('foo', 'http://foo.example.com', 'example.com');
|
|
61
|
+
await manager.newClient('bar', 'http://bar.example.com', 'example.com');
|
|
62
62
|
|
|
63
63
|
const socket = await new Promise<net.Socket>((resolve) => {
|
|
64
64
|
const netClient = net.createConnection({ port: clientFoo.port }, () => {
|
|
@@ -80,7 +80,7 @@ describe('ClientManager', () => {
|
|
|
80
80
|
|
|
81
81
|
it('should remove clients if they do not connect within 5 seconds', async () => {
|
|
82
82
|
const manager = new ClientManager();
|
|
83
|
-
await manager.newClient('foo');
|
|
83
|
+
await manager.newClient('foo', 'http://foo.example.com', 'example.com');
|
|
84
84
|
expect(manager.hasClient('foo')).toBe(true);
|
|
85
85
|
|
|
86
86
|
// wait past grace period (1s)
|
|
@@ -8,24 +8,36 @@ import { TunnelAgent } from './TunnelAgent.js';
|
|
|
8
8
|
|
|
9
9
|
export interface ClientManagerOptions {
|
|
10
10
|
maxTcpSockets?: number;
|
|
11
|
+
onTunnelClosed?: (tunnel: TunnelInfo) => void;
|
|
12
|
+
onTunnelCreated?: (tunnel: TunnelInfo) => void;
|
|
11
13
|
tunnelServer?: TunnelServer;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export interface NewClientInfo {
|
|
17
|
+
domain: string;
|
|
15
18
|
id: string;
|
|
16
19
|
maxConnCount?: number;
|
|
17
20
|
port: number;
|
|
21
|
+
url: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface TunnelInfo {
|
|
25
|
+
domain: string;
|
|
26
|
+
id: string;
|
|
27
|
+
url: string;
|
|
18
28
|
}
|
|
19
29
|
|
|
20
30
|
export class ClientManager {
|
|
21
31
|
public stats: { tunnels: number };
|
|
22
32
|
private clients: Map<string, Client>;
|
|
33
|
+
private clientTunnelInfo: Map<string, { domain: string; url: string }>;
|
|
23
34
|
private log: debug.Debugger;
|
|
24
35
|
private opt: ClientManagerOptions;
|
|
25
36
|
|
|
26
37
|
constructor(opt: ClientManagerOptions = {}) {
|
|
27
38
|
this.opt = opt;
|
|
28
39
|
this.clients = new Map();
|
|
40
|
+
this.clientTunnelInfo = new Map();
|
|
29
41
|
this.stats = { tunnels: 0 };
|
|
30
42
|
this.log = debug('lt:ClientManager');
|
|
31
43
|
}
|
|
@@ -38,7 +50,7 @@ export class ClientManager {
|
|
|
38
50
|
return this.clients.has(id);
|
|
39
51
|
}
|
|
40
52
|
|
|
41
|
-
async newClient(requestedId
|
|
53
|
+
async newClient(requestedId: string | undefined, url: string, domain: string): Promise<NewClientInfo> {
|
|
42
54
|
let id: string;
|
|
43
55
|
if (requestedId && !this.clients.has(requestedId)) {
|
|
44
56
|
id = requestedId;
|
|
@@ -56,6 +68,7 @@ export class ClientManager {
|
|
|
56
68
|
const client = new Client({ agent, id });
|
|
57
69
|
|
|
58
70
|
this.clients.set(id, client);
|
|
71
|
+
this.clientTunnelInfo.set(id, { domain, url });
|
|
59
72
|
|
|
60
73
|
client.once('close', () => {
|
|
61
74
|
this.removeClient(id);
|
|
@@ -64,10 +77,18 @@ export class ClientManager {
|
|
|
64
77
|
try {
|
|
65
78
|
const info = await agent.listen();
|
|
66
79
|
++this.stats.tunnels;
|
|
80
|
+
|
|
81
|
+
// Call the onTunnelCreated hook
|
|
82
|
+
if (this.opt.onTunnelCreated) {
|
|
83
|
+
this.opt.onTunnelCreated({ domain, id, url });
|
|
84
|
+
}
|
|
85
|
+
|
|
67
86
|
return {
|
|
87
|
+
domain,
|
|
68
88
|
id: id,
|
|
69
89
|
maxConnCount: maxSockets,
|
|
70
90
|
port: info.port,
|
|
91
|
+
url,
|
|
71
92
|
};
|
|
72
93
|
} catch (err) {
|
|
73
94
|
this.removeClient(id);
|
|
@@ -79,8 +100,19 @@ export class ClientManager {
|
|
|
79
100
|
this.log('removing client: %s', id);
|
|
80
101
|
const client = this.clients.get(id);
|
|
81
102
|
if (!client) return;
|
|
103
|
+
|
|
104
|
+
const tunnelInfo = this.clientTunnelInfo.get(id);
|
|
105
|
+
const domain = tunnelInfo?.domain || '';
|
|
106
|
+
const url = tunnelInfo?.url || '';
|
|
107
|
+
|
|
82
108
|
--this.stats.tunnels;
|
|
83
109
|
this.clients.delete(id);
|
|
110
|
+
this.clientTunnelInfo.delete(id);
|
|
84
111
|
client.close();
|
|
112
|
+
|
|
113
|
+
// Call the onTunnelClosed hook
|
|
114
|
+
if (this.opt.onTunnelClosed) {
|
|
115
|
+
this.opt.onTunnelClosed({ domain, id, url });
|
|
116
|
+
}
|
|
85
117
|
}
|
|
86
118
|
}
|
package/src/server/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { Client, type ClientOptions } from './Client.js';
|
|
2
|
-
export { ClientManager, type ClientManagerOptions, type NewClientInfo } from './ClientManager.js';
|
|
3
|
-
export { createServer, type PipenetServer, type ServerOptions } from './server.js';
|
|
2
|
+
export { ClientManager, type ClientManagerOptions, type NewClientInfo, type TunnelInfo } from './ClientManager.js';
|
|
3
|
+
export { createServer, type PipenetServer, type RequestInfo, type ServerHooks, type ServerOptions } from './server.js';
|
|
4
4
|
export { TunnelAgent, type TunnelAgentListenInfo, type TunnelAgentOptions, type TunnelAgentStats } from './TunnelAgent.js';
|
|
5
5
|
export { type SocketHandler, TunnelServer } from './TunnelServer.js';
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import net from 'net';
|
|
2
2
|
import request from 'supertest';
|
|
3
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
4
|
import { WebSocket, WebSocketServer } from 'ws';
|
|
5
5
|
|
|
6
|
+
import type { RequestInfo, TunnelInfo } from './server.js';
|
|
7
|
+
|
|
6
8
|
import { createServer } from './server.js';
|
|
7
9
|
|
|
8
10
|
describe('Server', () => {
|
|
@@ -136,4 +138,134 @@ describe('Server', () => {
|
|
|
136
138
|
expect(res.statusCode).toBe(204);
|
|
137
139
|
expect(res.headers['access-control-allow-origin']).toBe('*');
|
|
138
140
|
});
|
|
141
|
+
|
|
142
|
+
describe('hooks', () => {
|
|
143
|
+
it('should call onTunnelCreated when a tunnel is created', async () => {
|
|
144
|
+
const onTunnelCreated = vi.fn<(tunnel: TunnelInfo) => void>();
|
|
145
|
+
const server = createServer({ onTunnelCreated });
|
|
146
|
+
await new Promise<void>(resolve => server.listen(resolve));
|
|
147
|
+
|
|
148
|
+
await request(server).get('/test-tunnel');
|
|
149
|
+
|
|
150
|
+
expect(onTunnelCreated).toHaveBeenCalledTimes(1);
|
|
151
|
+
expect(onTunnelCreated).toHaveBeenCalledWith(
|
|
152
|
+
expect.objectContaining({
|
|
153
|
+
domain: expect.any(String),
|
|
154
|
+
id: 'test-tunnel',
|
|
155
|
+
url: expect.stringContaining('test-tunnel'),
|
|
156
|
+
})
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
await new Promise<void>(resolve => server.close(() => resolve()));
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should include domain in tunnel hooks', async () => {
|
|
163
|
+
const onTunnelCreated = vi.fn<(tunnel: TunnelInfo) => void>();
|
|
164
|
+
const server = createServer({
|
|
165
|
+
domains: ['tunnel.example.com'],
|
|
166
|
+
onTunnelCreated,
|
|
167
|
+
});
|
|
168
|
+
await new Promise<void>(resolve => server.listen(resolve));
|
|
169
|
+
|
|
170
|
+
await request(server)
|
|
171
|
+
.get('/domain-test')
|
|
172
|
+
.set('Host', 'tunnel.example.com');
|
|
173
|
+
|
|
174
|
+
expect(onTunnelCreated).toHaveBeenCalledTimes(1);
|
|
175
|
+
expect(onTunnelCreated).toHaveBeenCalledWith({
|
|
176
|
+
domain: 'tunnel.example.com',
|
|
177
|
+
id: 'domain-test',
|
|
178
|
+
url: 'http://domain-test.tunnel.example.com',
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
await new Promise<void>(resolve => server.close(() => resolve()));
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should call onTunnelClosed when a tunnel is closed', async () => {
|
|
185
|
+
const onTunnelCreated = vi.fn<(tunnel: TunnelInfo) => void>();
|
|
186
|
+
const onTunnelClosed = vi.fn<(tunnel: TunnelInfo) => void>();
|
|
187
|
+
const server = createServer({ onTunnelClosed, onTunnelCreated });
|
|
188
|
+
await new Promise<void>(resolve => server.listen(resolve));
|
|
189
|
+
|
|
190
|
+
const res = await request(server).get('/close-test');
|
|
191
|
+
const localTunnelPort = res.body.port;
|
|
192
|
+
|
|
193
|
+
// Connect a socket to activate the tunnel
|
|
194
|
+
const socket = net.createConnection({ port: localTunnelPort });
|
|
195
|
+
await new Promise<void>(resolve => socket.once('connect', resolve));
|
|
196
|
+
|
|
197
|
+
expect(onTunnelCreated).toHaveBeenCalledTimes(1);
|
|
198
|
+
expect(onTunnelClosed).not.toHaveBeenCalled();
|
|
199
|
+
|
|
200
|
+
// Close the socket to trigger tunnel close
|
|
201
|
+
socket.destroy();
|
|
202
|
+
|
|
203
|
+
// Wait for the close event to propagate (Client has a 1s grace timeout after offline)
|
|
204
|
+
await new Promise(resolve => setTimeout(resolve, 1200));
|
|
205
|
+
|
|
206
|
+
expect(onTunnelClosed).toHaveBeenCalledTimes(1);
|
|
207
|
+
expect(onTunnelClosed).toHaveBeenCalledWith(
|
|
208
|
+
expect.objectContaining({
|
|
209
|
+
domain: expect.any(String),
|
|
210
|
+
id: 'close-test',
|
|
211
|
+
url: expect.stringContaining('close-test'),
|
|
212
|
+
})
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
await new Promise<void>(resolve => server.close(() => resolve()));
|
|
216
|
+
}, 5000);
|
|
217
|
+
|
|
218
|
+
it('should call onRequest when a request is proxied', async () => {
|
|
219
|
+
const onRequest = vi.fn<(request: RequestInfo) => void>();
|
|
220
|
+
const server = createServer({
|
|
221
|
+
domains: ['example.com'],
|
|
222
|
+
onRequest,
|
|
223
|
+
});
|
|
224
|
+
await new Promise<void>(resolve => server.listen(resolve));
|
|
225
|
+
|
|
226
|
+
// Create a tunnel
|
|
227
|
+
const res = await request(server).get('/request-test');
|
|
228
|
+
const localTunnelPort = res.body.port;
|
|
229
|
+
|
|
230
|
+
// Create a simple echo server
|
|
231
|
+
const echoServer = net.createServer((socket) => {
|
|
232
|
+
socket.on('data', () => {
|
|
233
|
+
const response = 'HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK';
|
|
234
|
+
socket.write(response);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
await new Promise<void>(resolve => echoServer.listen(0, resolve));
|
|
238
|
+
const echoPort = (echoServer.address() as net.AddressInfo).port;
|
|
239
|
+
|
|
240
|
+
// Connect tunnel socket to echo server
|
|
241
|
+
const ltSocket = net.createConnection({ port: localTunnelPort });
|
|
242
|
+
const echoSocket = net.createConnection({ port: echoPort });
|
|
243
|
+
await Promise.all([
|
|
244
|
+
new Promise<void>(resolve => ltSocket.once('connect', resolve)),
|
|
245
|
+
new Promise<void>(resolve => echoSocket.once('connect', resolve)),
|
|
246
|
+
]);
|
|
247
|
+
ltSocket.pipe(echoSocket).pipe(ltSocket);
|
|
248
|
+
|
|
249
|
+
// Make a request through the tunnel
|
|
250
|
+
await request(server)
|
|
251
|
+
.get('/some/path')
|
|
252
|
+
.set('Host', 'request-test.example.com');
|
|
253
|
+
|
|
254
|
+
expect(onRequest).toHaveBeenCalledTimes(1);
|
|
255
|
+
expect(onRequest).toHaveBeenCalledWith({
|
|
256
|
+
headers: expect.objectContaining({
|
|
257
|
+
host: 'request-test.example.com',
|
|
258
|
+
}),
|
|
259
|
+
method: 'GET',
|
|
260
|
+
path: '/some/path',
|
|
261
|
+
remoteAddress: expect.any(String),
|
|
262
|
+
tunnelId: 'request-test',
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
ltSocket.destroy();
|
|
266
|
+
echoSocket.destroy();
|
|
267
|
+
echoServer.close();
|
|
268
|
+
await new Promise<void>(resolve => server.close(() => resolve()));
|
|
269
|
+
});
|
|
270
|
+
});
|
|
139
271
|
});
|
package/src/server/server.ts
CHANGED
|
@@ -14,7 +14,32 @@ export interface PipenetServer extends http.Server {
|
|
|
14
14
|
tunnelServer?: TunnelServer;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export interface
|
|
17
|
+
export interface RequestInfo {
|
|
18
|
+
headers: Record<string, string | string[] | undefined>;
|
|
19
|
+
method: string;
|
|
20
|
+
path: string;
|
|
21
|
+
remoteAddress?: string;
|
|
22
|
+
tunnelId: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ServerHooks {
|
|
26
|
+
/**
|
|
27
|
+
* Called when a request is proxied through a tunnel
|
|
28
|
+
*/
|
|
29
|
+
onRequest?: (request: RequestInfo) => void;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Called when a tunnel is closed
|
|
33
|
+
*/
|
|
34
|
+
onTunnelClosed?: (tunnel: TunnelInfo) => void;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Called when a new tunnel is created
|
|
38
|
+
*/
|
|
39
|
+
onTunnelCreated?: (tunnel: TunnelInfo) => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ServerOptions extends ServerHooks {
|
|
18
43
|
domains?: string[];
|
|
19
44
|
landing?: string;
|
|
20
45
|
maxTcpSockets?: number;
|
|
@@ -22,6 +47,12 @@ export interface ServerOptions {
|
|
|
22
47
|
tunnelPort?: number;
|
|
23
48
|
}
|
|
24
49
|
|
|
50
|
+
export interface TunnelInfo {
|
|
51
|
+
domain: string;
|
|
52
|
+
id: string;
|
|
53
|
+
url: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
25
56
|
export function createServer(opt: ServerOptions = {}): PipenetServer {
|
|
26
57
|
const validHosts = opt.domains && opt.domains.length > 0 ? opt.domains : undefined;
|
|
27
58
|
const myTldjs = tldjs.fromUserSettings({ validHosts });
|
|
@@ -84,10 +115,20 @@ export function createServer(opt: ServerOptions = {}): PipenetServer {
|
|
|
84
115
|
app.use(router.routes());
|
|
85
116
|
app.use(router.allowedMethods());
|
|
86
117
|
|
|
118
|
+
// Helper to extract domain from host (strips port if present)
|
|
119
|
+
const getDomain = (host: string) => {
|
|
120
|
+
// Remove port if present (e.g., "localhost:3000" -> "localhost")
|
|
121
|
+
return host.split(':')[0];
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Helper to build the tunnel URL
|
|
125
|
+
const buildUrl = (id: string, host: string) => {
|
|
126
|
+
return schema + '://' + id + '.' + host;
|
|
127
|
+
};
|
|
128
|
+
|
|
87
129
|
// Helper to build response with tunnel port if configured
|
|
88
|
-
const buildResponse = (info: { id: string; maxConnCount?: number; port: number;
|
|
89
|
-
const
|
|
90
|
-
const response: Record<string, unknown> = { ...info, url };
|
|
130
|
+
const buildResponse = (info: { domain: string; id: string; maxConnCount?: number; port: number; url: string }) => {
|
|
131
|
+
const response: Record<string, unknown> = { ...info };
|
|
91
132
|
// If using shared tunnel server, override port and add sharedTunnel flag
|
|
92
133
|
if (tunnelPort) {
|
|
93
134
|
response.port = tunnelPort;
|
|
@@ -107,10 +148,12 @@ export function createServer(opt: ServerOptions = {}): PipenetServer {
|
|
|
107
148
|
const isNewClientRequest = ctx.query['new'] !== undefined;
|
|
108
149
|
if (isNewClientRequest) {
|
|
109
150
|
const reqId = hri.random();
|
|
151
|
+
const domain = getDomain(ctx.request.host);
|
|
152
|
+
const url = buildUrl(reqId, ctx.request.host);
|
|
110
153
|
log('making new client with id %s', reqId);
|
|
111
|
-
const info = await manager.newClient(reqId);
|
|
154
|
+
const info = await manager.newClient(reqId, url, domain);
|
|
112
155
|
|
|
113
|
-
ctx.body = buildResponse(info
|
|
156
|
+
ctx.body = buildResponse(info);
|
|
114
157
|
return;
|
|
115
158
|
}
|
|
116
159
|
|
|
@@ -134,10 +177,12 @@ export function createServer(opt: ServerOptions = {}): PipenetServer {
|
|
|
134
177
|
return;
|
|
135
178
|
}
|
|
136
179
|
|
|
180
|
+
const domain = getDomain(ctx.request.host);
|
|
181
|
+
const url = buildUrl(reqId, ctx.request.host);
|
|
137
182
|
log('making new client with id %s', reqId);
|
|
138
|
-
const info = await manager.newClient(reqId);
|
|
183
|
+
const info = await manager.newClient(reqId, url, domain);
|
|
139
184
|
|
|
140
|
-
ctx.body = buildResponse(info
|
|
185
|
+
ctx.body = buildResponse(info);
|
|
141
186
|
});
|
|
142
187
|
|
|
143
188
|
const server: PipenetServer = http.createServer();
|
|
@@ -166,6 +211,17 @@ export function createServer(opt: ServerOptions = {}): PipenetServer {
|
|
|
166
211
|
return;
|
|
167
212
|
}
|
|
168
213
|
|
|
214
|
+
// Call the onRequest hook
|
|
215
|
+
if (opt.onRequest) {
|
|
216
|
+
opt.onRequest({
|
|
217
|
+
headers: req.headers,
|
|
218
|
+
method: req.method || 'GET',
|
|
219
|
+
path: req.url || '/',
|
|
220
|
+
remoteAddress: req.socket.remoteAddress,
|
|
221
|
+
tunnelId: clientId,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
169
225
|
client.handleRequest(req, res);
|
|
170
226
|
});
|
|
171
227
|
|