@linkshell/gateway 0.3.9 → 0.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/Dockerfile +1 -3
- package/README.md +13 -14
- package/dist/gateway/src/agent-permission-http.d.ts +74 -19
- package/dist/gateway/src/agent-permission-http.js +56 -16
- package/dist/gateway/src/agent-permission-http.js.map +1 -1
- package/dist/gateway/src/embedded.js +61 -153
- package/dist/gateway/src/embedded.js.map +1 -1
- package/dist/gateway/src/index.js +98 -193
- package/dist/gateway/src/index.js.map +1 -1
- package/dist/gateway/src/pairings.d.ts +3 -3
- package/dist/gateway/src/pairings.js +5 -4
- package/dist/gateway/src/pairings.js.map +1 -1
- package/dist/gateway/src/relay.d.ts +2 -2
- package/dist/gateway/src/relay.js +85 -161
- package/dist/gateway/src/relay.js.map +1 -1
- package/dist/gateway/src/sessions.d.ts +28 -42
- package/dist/gateway/src/sessions.js +145 -200
- package/dist/gateway/src/sessions.js.map +1 -1
- package/dist/gateway/src/state-store.d.ts +6 -9
- package/dist/gateway/src/state-store.js +19 -26
- package/dist/gateway/src/state-store.js.map +1 -1
- package/dist/gateway/src/tokens.d.ts +7 -27
- package/dist/gateway/src/tokens.js +60 -86
- package/dist/gateway/src/tokens.js.map +1 -1
- package/dist/gateway/src/tunnel.d.ts +13 -11
- package/dist/gateway/src/tunnel.js +36 -36
- package/dist/gateway/src/tunnel.js.map +1 -1
- package/dist/gateway/tsconfig.tsbuildinfo +1 -1
- package/dist/shared-protocol/src/index.d.ts +11978 -3423
- package/dist/shared-protocol/src/index.js +114 -163
- package/dist/shared-protocol/src/index.js.map +1 -1
- package/package.json +11 -11
- package/src/agent-permission-http.ts +63 -20
- package/src/embedded.ts +60 -158
- package/src/index.ts +98 -199
- package/src/pairings.ts +7 -6
- package/src/relay.ts +97 -193
- package/src/sessions.ts +150 -213
- package/src/state-store.ts +25 -41
- package/src/tokens.ts +63 -109
- package/src/tunnel.ts +43 -49
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
export interface
|
|
2
|
-
authorizationId: string;
|
|
1
|
+
export interface StoredTokenRecord {
|
|
3
2
|
token: string;
|
|
4
|
-
|
|
5
|
-
clientDeviceId?: string;
|
|
6
|
-
clientName?: string;
|
|
3
|
+
sessionIds: string[];
|
|
7
4
|
createdAt: number;
|
|
8
5
|
lastUsedAt: number;
|
|
9
6
|
}
|
|
10
7
|
export interface StoredPairingRecord {
|
|
11
|
-
|
|
8
|
+
sessionId: string;
|
|
12
9
|
pairingCode: string;
|
|
13
10
|
expiresAt: number;
|
|
14
11
|
claimed: boolean;
|
|
15
12
|
}
|
|
16
13
|
export interface GatewayStateStore {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
loadTokens(): Promise<StoredTokenRecord[]>;
|
|
15
|
+
saveToken(record: StoredTokenRecord): Promise<void>;
|
|
16
|
+
deleteToken(token: string): Promise<void>;
|
|
20
17
|
loadPairings(): Promise<StoredPairingRecord[]>;
|
|
21
18
|
savePairing(record: StoredPairingRecord): Promise<void>;
|
|
22
19
|
deletePairing(pairingCode: string): Promise<void>;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const PAIRING_TABLE = process.env.SUPABASE_GATEWAY_PAIRING_TABLE ??
|
|
4
|
-
"linkshell_gateway_pairing_challenges";
|
|
1
|
+
const TOKEN_TABLE = process.env.SUPABASE_GATEWAY_TOKEN_TABLE ?? "linkshell_gateway_tokens";
|
|
2
|
+
const PAIRING_TABLE = process.env.SUPABASE_GATEWAY_PAIRING_TABLE ?? "linkshell_gateway_pairings";
|
|
5
3
|
const STORE_TIMEOUT_MS = Number(process.env.SUPABASE_STATE_TIMEOUT_MS ?? 3_000);
|
|
6
4
|
function msToIso(ms) {
|
|
7
5
|
return new Date(ms).toISOString();
|
|
@@ -12,9 +10,6 @@ function isoToMs(value) {
|
|
|
12
10
|
const parsed = Date.parse(value);
|
|
13
11
|
return Number.isNaN(parsed) ? Date.now() : parsed;
|
|
14
12
|
}
|
|
15
|
-
function maybeString(value) {
|
|
16
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
17
|
-
}
|
|
18
13
|
export function createSupabaseStateStore() {
|
|
19
14
|
const url = process.env.SUPABASE_URL?.replace(/\/+$/, "");
|
|
20
15
|
const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY;
|
|
@@ -43,44 +38,42 @@ export function createSupabaseStateStore() {
|
|
|
43
38
|
return (await res.json());
|
|
44
39
|
}
|
|
45
40
|
return {
|
|
46
|
-
async
|
|
47
|
-
const rows = await request(`${
|
|
41
|
+
async loadTokens() {
|
|
42
|
+
const rows = await request(`${TOKEN_TABLE}?select=token,session_ids,created_at,last_used_at`);
|
|
48
43
|
return rows.map((row) => ({
|
|
49
|
-
authorizationId: String(row.authorization_id ?? ""),
|
|
50
44
|
token: String(row.token ?? ""),
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
sessionIds: Array.isArray(row.session_ids)
|
|
46
|
+
? row.session_ids.map(String)
|
|
47
|
+
: [],
|
|
54
48
|
createdAt: isoToMs(row.created_at),
|
|
55
49
|
lastUsedAt: isoToMs(row.last_used_at),
|
|
56
|
-
})).filter((record) => record.
|
|
50
|
+
})).filter((record) => record.token);
|
|
57
51
|
},
|
|
58
|
-
async
|
|
59
|
-
await request(`${
|
|
52
|
+
async saveToken(record) {
|
|
53
|
+
await request(`${TOKEN_TABLE}?on_conflict=token`, {
|
|
60
54
|
method: "POST",
|
|
61
55
|
headers: { Prefer: "resolution=merge-duplicates" },
|
|
62
56
|
body: JSON.stringify({
|
|
63
|
-
authorization_id: record.authorizationId,
|
|
64
57
|
token: record.token,
|
|
65
|
-
|
|
66
|
-
client_device_id: record.clientDeviceId ?? null,
|
|
67
|
-
client_name: record.clientName ?? null,
|
|
58
|
+
session_ids: record.sessionIds,
|
|
68
59
|
created_at: msToIso(record.createdAt),
|
|
69
60
|
last_used_at: msToIso(record.lastUsedAt),
|
|
70
61
|
}),
|
|
71
62
|
});
|
|
72
63
|
},
|
|
73
|
-
async
|
|
74
|
-
await request(`${
|
|
64
|
+
async deleteToken(token) {
|
|
65
|
+
await request(`${TOKEN_TABLE}?token=eq.${encodeURIComponent(token)}`, {
|
|
66
|
+
method: "DELETE",
|
|
67
|
+
});
|
|
75
68
|
},
|
|
76
69
|
async loadPairings() {
|
|
77
|
-
const rows = await request(`${PAIRING_TABLE}?select=pairing_code,
|
|
70
|
+
const rows = await request(`${PAIRING_TABLE}?select=pairing_code,session_id,expires_at,claimed`);
|
|
78
71
|
return rows.map((row) => ({
|
|
79
72
|
pairingCode: String(row.pairing_code ?? ""),
|
|
80
|
-
|
|
73
|
+
sessionId: String(row.session_id ?? ""),
|
|
81
74
|
expiresAt: isoToMs(row.expires_at),
|
|
82
75
|
claimed: row.claimed === true,
|
|
83
|
-
})).filter((record) => record.pairingCode && record.
|
|
76
|
+
})).filter((record) => record.pairingCode && record.sessionId);
|
|
84
77
|
},
|
|
85
78
|
async savePairing(record) {
|
|
86
79
|
await request(`${PAIRING_TABLE}?on_conflict=pairing_code`, {
|
|
@@ -88,7 +81,7 @@ export function createSupabaseStateStore() {
|
|
|
88
81
|
headers: { Prefer: "resolution=merge-duplicates" },
|
|
89
82
|
body: JSON.stringify({
|
|
90
83
|
pairing_code: record.pairingCode,
|
|
91
|
-
|
|
84
|
+
session_id: record.sessionId,
|
|
92
85
|
expires_at: msToIso(record.expiresAt),
|
|
93
86
|
claimed: record.claimed,
|
|
94
87
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-store.js","sourceRoot":"","sources":["../../../src/state-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"state-store.js","sourceRoot":"","sources":["../../../src/state-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,38 +1,18 @@
|
|
|
1
1
|
import type { GatewayStateStore } from "./state-store.js";
|
|
2
|
-
|
|
3
|
-
authorizationId: string;
|
|
4
|
-
hostDeviceId: string;
|
|
5
|
-
clientDeviceId: string | undefined;
|
|
6
|
-
clientName: string | undefined;
|
|
7
|
-
createdAt: number;
|
|
8
|
-
lastUsedAt: number;
|
|
9
|
-
}
|
|
10
|
-
export declare class AuthorizationManager {
|
|
2
|
+
export declare class TokenManager {
|
|
11
3
|
private readonly store?;
|
|
12
4
|
private tokens;
|
|
13
|
-
private
|
|
5
|
+
private sessionToToken;
|
|
6
|
+
private cleanupTimer;
|
|
14
7
|
constructor(store?: GatewayStateStore | undefined);
|
|
15
8
|
hydrate(): Promise<void>;
|
|
16
9
|
register(deviceToken?: string): string;
|
|
17
|
-
|
|
18
|
-
authorizationId?: string;
|
|
19
|
-
clientDeviceId?: string;
|
|
20
|
-
clientName?: string;
|
|
21
|
-
createdAt?: number;
|
|
22
|
-
lastUsedAt?: number;
|
|
23
|
-
persist?: boolean;
|
|
24
|
-
}): DeviceAuthorization | undefined;
|
|
10
|
+
bind(token: string, sessionId: string): boolean;
|
|
25
11
|
validate(token: string): boolean;
|
|
26
|
-
owns(token: string,
|
|
27
|
-
revoke(token: string, hostDeviceId: string, authorizationId: string): boolean;
|
|
28
|
-
getHostDeviceIds(token: string): Set<string>;
|
|
12
|
+
owns(token: string, sessionId: string): boolean;
|
|
29
13
|
getSessionIds(token: string): Set<string>;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
bind(token: string, hostDeviceId: string): boolean;
|
|
14
|
+
getTokenForSession(sessionId: string): string | undefined;
|
|
15
|
+
private cleanup;
|
|
33
16
|
private persist;
|
|
34
17
|
destroy(): void;
|
|
35
18
|
}
|
|
36
|
-
export declare class TokenManager extends AuthorizationManager {
|
|
37
|
-
}
|
|
38
|
-
export {};
|
|
@@ -1,145 +1,119 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
|
|
2
|
+
const CLEANUP_INTERVAL = 5 * 60_000;
|
|
3
|
+
const SESSION_TTL = 7 * 24 * 60 * 60_000; // 7 days — prune stale bindings
|
|
4
|
+
export class TokenManager {
|
|
3
5
|
store;
|
|
4
6
|
tokens = new Map();
|
|
5
|
-
|
|
7
|
+
sessionToToken = new Map();
|
|
8
|
+
cleanupTimer;
|
|
6
9
|
constructor(store) {
|
|
7
10
|
this.store = store;
|
|
11
|
+
this.cleanupTimer = setInterval(() => this.cleanup(), CLEANUP_INTERVAL);
|
|
8
12
|
}
|
|
9
13
|
async hydrate() {
|
|
10
14
|
if (!this.store)
|
|
11
15
|
return;
|
|
12
16
|
try {
|
|
13
|
-
const records = await this.store.
|
|
17
|
+
const records = await this.store.loadTokens();
|
|
18
|
+
const now = Date.now();
|
|
14
19
|
for (const record of records) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
if (now - record.lastUsedAt > SESSION_TTL) {
|
|
21
|
+
void this.store.deleteToken(record.token).catch(() => { });
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
this.tokens.set(record.token, {
|
|
25
|
+
token: record.token,
|
|
26
|
+
sessionIds: new Set(record.sessionIds),
|
|
20
27
|
createdAt: record.createdAt,
|
|
21
28
|
lastUsedAt: record.lastUsedAt,
|
|
22
|
-
persist: false,
|
|
23
29
|
});
|
|
30
|
+
for (const sessionId of record.sessionIds) {
|
|
31
|
+
this.sessionToToken.set(sessionId, record.token);
|
|
32
|
+
}
|
|
24
33
|
}
|
|
25
34
|
}
|
|
26
35
|
catch (err) {
|
|
27
|
-
process.stderr.write(`[gateway]
|
|
36
|
+
process.stderr.write(`[gateway] token store hydrate failed, using memory only: ${err}\n`);
|
|
28
37
|
}
|
|
29
38
|
}
|
|
30
39
|
register(deviceToken) {
|
|
31
40
|
if (deviceToken && this.tokens.has(deviceToken)) {
|
|
32
41
|
const record = this.tokens.get(deviceToken);
|
|
33
42
|
record.lastUsedAt = Date.now();
|
|
43
|
+
this.persist(record);
|
|
34
44
|
return deviceToken;
|
|
35
45
|
}
|
|
36
46
|
const token = deviceToken || randomUUID();
|
|
37
47
|
this.tokens.set(token, {
|
|
38
48
|
token,
|
|
39
|
-
|
|
49
|
+
sessionIds: new Set(),
|
|
40
50
|
createdAt: Date.now(),
|
|
41
51
|
lastUsedAt: Date.now(),
|
|
42
52
|
});
|
|
53
|
+
this.persist(this.tokens.get(token));
|
|
43
54
|
return token;
|
|
44
55
|
}
|
|
45
|
-
|
|
46
|
-
const record = this.tokens.get(token);
|
|
47
|
-
if (!record)
|
|
48
|
-
return undefined;
|
|
49
|
-
const existing = record.authorizations.get(hostDeviceId);
|
|
50
|
-
const now = Date.now();
|
|
51
|
-
const authorization = {
|
|
52
|
-
authorizationId: input.authorizationId ?? existing?.authorizationId ?? randomUUID(),
|
|
53
|
-
hostDeviceId,
|
|
54
|
-
clientDeviceId: input.clientDeviceId ?? existing?.clientDeviceId,
|
|
55
|
-
clientName: input.clientName ?? existing?.clientName,
|
|
56
|
-
createdAt: input.createdAt ?? existing?.createdAt ?? now,
|
|
57
|
-
lastUsedAt: input.lastUsedAt ?? now,
|
|
58
|
-
};
|
|
59
|
-
record.authorizations.set(hostDeviceId, authorization);
|
|
60
|
-
record.lastUsedAt = now;
|
|
61
|
-
let tokens = this.hostDeviceToTokens.get(hostDeviceId);
|
|
62
|
-
if (!tokens) {
|
|
63
|
-
tokens = new Set();
|
|
64
|
-
this.hostDeviceToTokens.set(hostDeviceId, tokens);
|
|
65
|
-
}
|
|
66
|
-
tokens.add(token);
|
|
67
|
-
if (input.persist !== false) {
|
|
68
|
-
this.persist(token, authorization);
|
|
69
|
-
}
|
|
70
|
-
return authorization;
|
|
71
|
-
}
|
|
72
|
-
validate(token) {
|
|
56
|
+
bind(token, sessionId) {
|
|
73
57
|
const record = this.tokens.get(token);
|
|
74
58
|
if (!record)
|
|
75
59
|
return false;
|
|
60
|
+
record.sessionIds.add(sessionId);
|
|
76
61
|
record.lastUsedAt = Date.now();
|
|
62
|
+
this.sessionToToken.set(sessionId, token);
|
|
63
|
+
this.persist(record);
|
|
77
64
|
return true;
|
|
78
65
|
}
|
|
79
|
-
|
|
66
|
+
validate(token) {
|
|
80
67
|
const record = this.tokens.get(token);
|
|
81
68
|
if (!record)
|
|
82
69
|
return false;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return false;
|
|
86
|
-
const now = Date.now();
|
|
87
|
-
record.lastUsedAt = now;
|
|
88
|
-
authorization.lastUsedAt = now;
|
|
89
|
-
this.persist(token, authorization);
|
|
70
|
+
record.lastUsedAt = Date.now();
|
|
71
|
+
this.persist(record);
|
|
90
72
|
return true;
|
|
91
73
|
}
|
|
92
|
-
|
|
74
|
+
owns(token, sessionId) {
|
|
93
75
|
const record = this.tokens.get(token);
|
|
94
|
-
|
|
95
|
-
if (!record || !authorization || authorization.authorizationId !== authorizationId) {
|
|
76
|
+
if (!record)
|
|
96
77
|
return false;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
tokens?.delete(token);
|
|
101
|
-
if (tokens && tokens.size === 0) {
|
|
102
|
-
this.hostDeviceToTokens.delete(hostDeviceId);
|
|
103
|
-
}
|
|
104
|
-
void this.store?.deleteAuthorization(authorizationId).catch((err) => {
|
|
105
|
-
process.stderr.write(`[gateway] authorization store delete failed: ${err}\n`);
|
|
106
|
-
});
|
|
107
|
-
return true;
|
|
78
|
+
record.lastUsedAt = Date.now();
|
|
79
|
+
this.persist(record);
|
|
80
|
+
return record.sessionIds.has(sessionId);
|
|
108
81
|
}
|
|
109
|
-
|
|
82
|
+
getSessionIds(token) {
|
|
110
83
|
const record = this.tokens.get(token);
|
|
111
84
|
if (!record)
|
|
112
85
|
return new Set();
|
|
113
86
|
record.lastUsedAt = Date.now();
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
getSessionIds(token) {
|
|
117
|
-
return this.getHostDeviceIds(token);
|
|
118
|
-
}
|
|
119
|
-
getAuthorizationId(token, hostDeviceId) {
|
|
120
|
-
return this.tokens.get(token)?.authorizations.get(hostDeviceId)?.authorizationId;
|
|
87
|
+
this.persist(record);
|
|
88
|
+
return record.sessionIds;
|
|
121
89
|
}
|
|
122
|
-
|
|
123
|
-
return this.
|
|
90
|
+
getTokenForSession(sessionId) {
|
|
91
|
+
return this.sessionToToken.get(sessionId);
|
|
124
92
|
}
|
|
125
|
-
|
|
126
|
-
|
|
93
|
+
cleanup() {
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
for (const [token, record] of this.tokens) {
|
|
96
|
+
if (now - record.lastUsedAt > SESSION_TTL) {
|
|
97
|
+
for (const sid of record.sessionIds) {
|
|
98
|
+
this.sessionToToken.delete(sid);
|
|
99
|
+
}
|
|
100
|
+
this.tokens.delete(token);
|
|
101
|
+
void this.store?.deleteToken(token).catch(() => { });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
127
104
|
}
|
|
128
|
-
persist(
|
|
129
|
-
void this.store?.
|
|
130
|
-
token,
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
clientName: authorization.clientName,
|
|
135
|
-
createdAt: authorization.createdAt,
|
|
136
|
-
lastUsedAt: authorization.lastUsedAt,
|
|
105
|
+
persist(record) {
|
|
106
|
+
void this.store?.saveToken({
|
|
107
|
+
token: record.token,
|
|
108
|
+
sessionIds: [...record.sessionIds],
|
|
109
|
+
createdAt: record.createdAt,
|
|
110
|
+
lastUsedAt: record.lastUsedAt,
|
|
137
111
|
}).catch((err) => {
|
|
138
|
-
process.stderr.write(`[gateway]
|
|
112
|
+
process.stderr.write(`[gateway] token store save failed: ${err}\n`);
|
|
139
113
|
});
|
|
140
114
|
}
|
|
141
|
-
destroy() {
|
|
142
|
-
|
|
143
|
-
|
|
115
|
+
destroy() {
|
|
116
|
+
clearInterval(this.cleanupTimer);
|
|
117
|
+
}
|
|
144
118
|
}
|
|
145
119
|
//# sourceMappingURL=tokens.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../../src/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../../src/tokens.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import type WebSocket from "ws";
|
|
3
|
-
import type {
|
|
3
|
+
import type { SessionManager } from "./sessions.js";
|
|
4
4
|
import type { TokenManager } from "./tokens.js";
|
|
5
5
|
export interface PendingTunnelRequest {
|
|
6
6
|
res: ServerResponse;
|
|
@@ -10,23 +10,22 @@ export interface PendingTunnelRequest {
|
|
|
10
10
|
export interface PendingTunnelWs {
|
|
11
11
|
ws: WebSocket;
|
|
12
12
|
}
|
|
13
|
-
/** Parse lsh_tunnel cookie: "
|
|
13
|
+
/** Parse lsh_tunnel cookie: "sessionId:port:token" */
|
|
14
14
|
export declare function parseTunnelCookie(req: IncomingMessage): {
|
|
15
|
-
|
|
15
|
+
sessionId: string;
|
|
16
16
|
port: number;
|
|
17
17
|
token: string;
|
|
18
18
|
} | null;
|
|
19
19
|
export declare function parseTunnelPath(pathname: string): {
|
|
20
|
-
|
|
20
|
+
sessionId: string;
|
|
21
21
|
port: number;
|
|
22
22
|
path: string;
|
|
23
23
|
} | null;
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
export declare function handleTunnelRequest(req: IncomingMessage, res: ServerResponse, sessions: SessionManager, tokens: TokenManager, parsed: {
|
|
25
|
+
sessionId: string;
|
|
26
26
|
port: number;
|
|
27
27
|
path: string;
|
|
28
|
-
}
|
|
29
|
-
export declare function handleTunnelRequest(req: IncomingMessage, res: ServerResponse, sessions: DeviceManager, tokens: TokenManager, parsed: ParsedTunnelTarget, url: URL, preAuthToken?: string): Promise<void>;
|
|
28
|
+
}, url: URL, preAuthToken?: string): Promise<void>;
|
|
30
29
|
export declare function handleTunnelResponse(payload: {
|
|
31
30
|
requestId: string;
|
|
32
31
|
statusCode: number;
|
|
@@ -46,6 +45,9 @@ export declare function handleTunnelWsClose(payload: {
|
|
|
46
45
|
}): void;
|
|
47
46
|
export declare function registerTunnelWs(requestId: string, ws: WebSocket): void;
|
|
48
47
|
export declare function removeTunnelWs(requestId: string): void;
|
|
49
|
-
export declare function cleanupSessionTunnels(
|
|
50
|
-
export declare function handleTunnelWsUpgrade(ws: WebSocket, parsed:
|
|
51
|
-
|
|
48
|
+
export declare function cleanupSessionTunnels(sessionId: string): void;
|
|
49
|
+
export declare function handleTunnelWsUpgrade(ws: WebSocket, parsed: {
|
|
50
|
+
sessionId: string;
|
|
51
|
+
port: number;
|
|
52
|
+
path: string;
|
|
53
|
+
}, url: URL, sessions: SessionManager, tokens: TokenManager): Promise<void>;
|