@bruch/max-client 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 +22 -0
- package/README.md +168 -0
- package/UPSTREAM.md +10 -0
- package/dist/api/account.d.ts +24 -0
- package/dist/api/account.d.ts.map +1 -0
- package/dist/api/auth.d.ts +57 -0
- package/dist/api/auth.d.ts.map +1 -0
- package/dist/api/bots.d.ts +8 -0
- package/dist/api/bots.d.ts.map +1 -0
- package/dist/api/chats.d.ts +41 -0
- package/dist/api/chats.d.ts.map +1 -0
- package/dist/api/facade.d.ts +19 -0
- package/dist/api/facade.d.ts.map +1 -0
- package/dist/api/index.d.ts +10 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/messages.d.ts +35 -0
- package/dist/api/messages.d.ts.map +1 -0
- package/dist/api/response.d.ts +7 -0
- package/dist/api/response.d.ts.map +1 -0
- package/dist/api/runtime.d.ts +19 -0
- package/dist/api/runtime.d.ts.map +1 -0
- package/dist/api/uploads.d.ts +23 -0
- package/dist/api/uploads.d.ts.map +1 -0
- package/dist/api/users.d.ts +25 -0
- package/dist/api/users.d.ts.map +1 -0
- package/dist/app.d.ts +55 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/auth/flows.d.ts +27 -0
- package/dist/auth/flows.d.ts.map +1 -0
- package/dist/auth/index.d.ts +3 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +109 -0
- package/dist/auth/index.js.map +11 -0
- package/dist/auth/providers.d.ts +11 -0
- package/dist/auth/providers.d.ts.map +1 -0
- package/dist/client.d.ts +139 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/config.d.ts +80 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/connection/connection.d.ts +25 -0
- package/dist/connection/connection.d.ts.map +1 -0
- package/dist/connection/index.d.ts +4 -0
- package/dist/connection/index.d.ts.map +1 -0
- package/dist/connection/pending.d.ts +9 -0
- package/dist/connection/pending.d.ts.map +1 -0
- package/dist/connection/readers.d.ts +17 -0
- package/dist/connection/readers.d.ts.map +1 -0
- package/dist/dispatch/dispatcher.d.ts +16 -0
- package/dist/dispatch/dispatcher.d.ts.map +1 -0
- package/dist/dispatch/index.d.ts +3 -0
- package/dist/dispatch/index.d.ts.map +1 -0
- package/dist/dispatch/router.d.ts +64 -0
- package/dist/dispatch/router.d.ts.map +1 -0
- package/dist/errors.d.ts +24 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/files/file.d.ts +29 -0
- package/dist/files/file.d.ts.map +1 -0
- package/dist/files/index.d.ts +2 -0
- package/dist/files/index.d.ts.map +1 -0
- package/dist/files/index.js +95 -0
- package/dist/files/index.js.map +10 -0
- package/dist/formatting/index.d.ts +2 -0
- package/dist/formatting/index.d.ts.map +1 -0
- package/dist/formatting/markdown.d.ts +13 -0
- package/dist/formatting/markdown.d.ts.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3967 -0
- package/dist/index.js.map +48 -0
- package/dist/logging.d.ts +10 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/protocol/compression.d.ts +8 -0
- package/dist/protocol/compression.d.ts.map +1 -0
- package/dist/protocol/enums.d.ts +175 -0
- package/dist/protocol/enums.d.ts.map +1 -0
- package/dist/protocol/index.d.ts +6 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +438 -0
- package/dist/protocol/index.js.map +13 -0
- package/dist/protocol/tcp.d.ts +35 -0
- package/dist/protocol/tcp.d.ts.map +1 -0
- package/dist/protocol/types.d.ts +38 -0
- package/dist/protocol/types.d.ts.map +1 -0
- package/dist/protocol/websocket.d.ts +7 -0
- package/dist/protocol/websocket.d.ts.map +1 -0
- package/dist/session/index.d.ts +3 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +179 -0
- package/dist/session/index.js.map +12 -0
- package/dist/session/store.d.ts +18 -0
- package/dist/session/store.d.ts.map +1 -0
- package/dist/session/types.d.ts +29 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/telemetry/index.d.ts +4 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +831 -0
- package/dist/telemetry/index.js.map +17 -0
- package/dist/telemetry/navigation.d.ts +41 -0
- package/dist/telemetry/navigation.d.ts.map +1 -0
- package/dist/telemetry/payloads.d.ts +30 -0
- package/dist/telemetry/payloads.d.ts.map +1 -0
- package/dist/telemetry/service.d.ts +53 -0
- package/dist/telemetry/service.d.ts.map +1 -0
- package/dist/transport/base.d.ts +18 -0
- package/dist/transport/base.d.ts.map +1 -0
- package/dist/transport/index.d.ts +4 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/tcp.d.ts +28 -0
- package/dist/transport/tcp.d.ts.map +1 -0
- package/dist/transport/websocket.d.ts +14 -0
- package/dist/transport/websocket.d.ts.map +1 -0
- package/dist/types/attachments.d.ts +18 -0
- package/dist/types/attachments.d.ts.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +376 -0
- package/dist/types/index.js.map +12 -0
- package/dist/types/models.d.ts +210 -0
- package/dist/types/models.d.ts.map +1 -0
- package/dist/types/utils.d.ts +10 -0
- package/dist/types/utils.d.ts.map +1 -0
- package/package.json +95 -0
- package/src/api/account.ts +93 -0
- package/src/api/auth.ts +312 -0
- package/src/api/bots.ts +21 -0
- package/src/api/chats.ts +234 -0
- package/src/api/facade.ts +28 -0
- package/src/api/index.ts +9 -0
- package/src/api/messages.ts +230 -0
- package/src/api/response.ts +24 -0
- package/src/api/runtime.ts +27 -0
- package/src/api/uploads.ts +91 -0
- package/src/api/users.ts +68 -0
- package/src/app.ts +271 -0
- package/src/auth/flows.ts +103 -0
- package/src/auth/index.ts +2 -0
- package/src/auth/providers.ts +30 -0
- package/src/client.ts +506 -0
- package/src/config.ts +161 -0
- package/src/connection/connection.ts +93 -0
- package/src/connection/index.ts +3 -0
- package/src/connection/pending.ts +49 -0
- package/src/connection/readers.ts +33 -0
- package/src/dispatch/dispatcher.ts +194 -0
- package/src/dispatch/index.ts +2 -0
- package/src/dispatch/router.ts +149 -0
- package/src/errors.ts +41 -0
- package/src/files/file.ts +92 -0
- package/src/files/index.ts +1 -0
- package/src/formatting/index.ts +1 -0
- package/src/formatting/markdown.ts +123 -0
- package/src/index.ts +15 -0
- package/src/logging.ts +67 -0
- package/src/protocol/compression.ts +69 -0
- package/src/protocol/enums.ts +175 -0
- package/src/protocol/index.ts +5 -0
- package/src/protocol/tcp.ts +139 -0
- package/src/protocol/types.ts +45 -0
- package/src/protocol/websocket.ts +47 -0
- package/src/session/index.ts +2 -0
- package/src/session/store.ts +154 -0
- package/src/session/types.ts +45 -0
- package/src/telemetry/index.ts +3 -0
- package/src/telemetry/navigation.ts +160 -0
- package/src/telemetry/payloads.ts +84 -0
- package/src/telemetry/service.ts +194 -0
- package/src/transport/base.ts +43 -0
- package/src/transport/index.ts +3 -0
- package/src/transport/tcp.ts +296 -0
- package/src/transport/websocket.ts +63 -0
- package/src/types/attachments.ts +24 -0
- package/src/types/index.ts +3 -0
- package/src/types/models.ts +461 -0
- package/src/types/utils.ts +45 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import type { Socket } from "bun";
|
|
2
|
+
|
|
3
|
+
import { ConnectionError } from "../errors.ts";
|
|
4
|
+
import { AsyncQueue, type Transport } from "./base.ts";
|
|
5
|
+
|
|
6
|
+
export type TcpConnector = (
|
|
7
|
+
options: Bun.TCPSocketConnectOptions<undefined>,
|
|
8
|
+
) => Promise<Socket<undefined>>;
|
|
9
|
+
|
|
10
|
+
const defaultConnector: TcpConnector = async (options) => await Bun.connect(options);
|
|
11
|
+
|
|
12
|
+
export class TcpTransport implements Transport<Uint8Array> {
|
|
13
|
+
private socket: Socket<undefined> | null = null;
|
|
14
|
+
private readonly chunks = new AsyncQueue<Uint8Array>();
|
|
15
|
+
private buffered: Uint8Array<ArrayBufferLike> = new Uint8Array();
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
readonly host: string,
|
|
19
|
+
readonly port: number,
|
|
20
|
+
readonly useTls = true,
|
|
21
|
+
readonly proxy: string | null = null,
|
|
22
|
+
private readonly connector: TcpConnector = defaultConnector,
|
|
23
|
+
) {}
|
|
24
|
+
|
|
25
|
+
get connected(): boolean {
|
|
26
|
+
return this.socket !== null && this.socket.readyState === 1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async connect(): Promise<void> {
|
|
30
|
+
if (this.socket) return;
|
|
31
|
+
this.chunks.reset();
|
|
32
|
+
const endpoint = this.proxy ? new URL(this.proxy) : null;
|
|
33
|
+
let resolveOpen: () => void = () => undefined;
|
|
34
|
+
let rejectOpen: (error: Error) => void = () => undefined;
|
|
35
|
+
let opened = false;
|
|
36
|
+
const open = new Promise<void>((resolve, reject) => {
|
|
37
|
+
resolveOpen = resolve;
|
|
38
|
+
rejectOpen = reject;
|
|
39
|
+
});
|
|
40
|
+
const handlers = this.handlers();
|
|
41
|
+
try {
|
|
42
|
+
this.socket = await this.connector({
|
|
43
|
+
hostname: endpoint?.hostname ?? this.host,
|
|
44
|
+
port: endpoint ? Number(endpoint.port || defaultProxyPort(endpoint.protocol)) : this.port,
|
|
45
|
+
tls:
|
|
46
|
+
endpoint?.protocol === "https:"
|
|
47
|
+
? { serverName: endpoint.hostname }
|
|
48
|
+
: !endpoint && this.useTls
|
|
49
|
+
? { serverName: this.host }
|
|
50
|
+
: false,
|
|
51
|
+
socket: {
|
|
52
|
+
...handlers,
|
|
53
|
+
open: () => {
|
|
54
|
+
opened = true;
|
|
55
|
+
resolveOpen();
|
|
56
|
+
},
|
|
57
|
+
close: (socket, error) => {
|
|
58
|
+
void handlers.close?.(socket, error);
|
|
59
|
+
if (!opened)
|
|
60
|
+
rejectOpen(error ?? new ConnectionError("Connection closed while opening"));
|
|
61
|
+
},
|
|
62
|
+
error: (socket, error) => {
|
|
63
|
+
void handlers.error?.(socket, error);
|
|
64
|
+
if (!opened) rejectOpen(error);
|
|
65
|
+
},
|
|
66
|
+
connectError: (socket, error) => {
|
|
67
|
+
void handlers.connectError?.(socket, error);
|
|
68
|
+
if (!opened) rejectOpen(error);
|
|
69
|
+
},
|
|
70
|
+
timeout: (socket) => {
|
|
71
|
+
void handlers.timeout?.(socket);
|
|
72
|
+
if (!opened) rejectOpen(new ConnectionError("Connection timed out while opening"));
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
await open;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
this.socket = null;
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
if (endpoint) {
|
|
82
|
+
await this.openTunnel(endpoint);
|
|
83
|
+
if (this.useTls) await this.upgradeTls();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async close(): Promise<void> {
|
|
88
|
+
this.socket?.end();
|
|
89
|
+
this.socket = null;
|
|
90
|
+
this.buffered = new Uint8Array();
|
|
91
|
+
this.chunks.reset();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async send(data: Uint8Array): Promise<void> {
|
|
95
|
+
if (!this.socket || !this.connected) throw new ConnectionError("Transport is not connected");
|
|
96
|
+
await this.sendRaw(data);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async recv(length?: number): Promise<Uint8Array> {
|
|
100
|
+
if (!this.connected) throw new ConnectionError("Transport is not connected");
|
|
101
|
+
if (length === undefined) return await this.chunks.shift();
|
|
102
|
+
while (this.buffered.byteLength < length) {
|
|
103
|
+
this.buffered = concatBytes(this.buffered, await this.chunks.shift());
|
|
104
|
+
}
|
|
105
|
+
const result = this.buffered.slice(0, length);
|
|
106
|
+
this.buffered = this.buffered.slice(length);
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private handlers(): Bun.SocketHandler<undefined> {
|
|
111
|
+
return {
|
|
112
|
+
data: (_socket, data) => this.chunks.push(Uint8Array.from(data)),
|
|
113
|
+
close: (_socket, error) => {
|
|
114
|
+
this.socket = null;
|
|
115
|
+
this.chunks.fail(error ?? new ConnectionError("Connection closed"));
|
|
116
|
+
},
|
|
117
|
+
end: () => this.chunks.fail(new ConnectionError("Connection closed by server")),
|
|
118
|
+
error: (_socket, error) => this.chunks.fail(error),
|
|
119
|
+
connectError: (_socket, error) => this.chunks.fail(error),
|
|
120
|
+
timeout: () => this.chunks.fail(new ConnectionError("Connection timed out")),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private async openTunnel(proxy: URL): Promise<void> {
|
|
125
|
+
if (proxy.protocol === "http:" || proxy.protocol === "https:") {
|
|
126
|
+
await this.httpConnect(proxy);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (proxy.protocol === "socks5:" || proxy.protocol === "socks5h:") {
|
|
130
|
+
await this.socks5Connect(proxy);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (proxy.protocol === "socks4:" || proxy.protocol === "socks4a:") {
|
|
134
|
+
await this.socks4Connect(proxy);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
throw new TypeError(`Unsupported proxy protocol: ${proxy.protocol}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private async httpConnect(proxy: URL): Promise<void> {
|
|
141
|
+
const headers = [
|
|
142
|
+
`CONNECT ${this.host}:${this.port} HTTP/1.1`,
|
|
143
|
+
`Host: ${this.host}:${this.port}`,
|
|
144
|
+
];
|
|
145
|
+
if (proxy.username || proxy.password) {
|
|
146
|
+
headers.push(
|
|
147
|
+
`Proxy-Authorization: Basic ${Buffer.from(`${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`).toString("base64")}`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
await this.sendRaw(new TextEncoder().encode(`${headers.join("\r\n")}\r\n\r\n`));
|
|
151
|
+
const response = new TextDecoder().decode(
|
|
152
|
+
await this.readUntil(Uint8Array.from([13, 10, 13, 10])),
|
|
153
|
+
);
|
|
154
|
+
const status = Number(/^HTTP\/\d(?:\.\d)?\s+(\d{3})/.exec(response)?.[1]);
|
|
155
|
+
if (status !== 200)
|
|
156
|
+
throw new ConnectionError(`HTTP proxy CONNECT failed with status ${status || "unknown"}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private async socks5Connect(proxy: URL): Promise<void> {
|
|
160
|
+
const authenticated = Boolean(proxy.username || proxy.password);
|
|
161
|
+
await this.sendRaw(Uint8Array.from(authenticated ? [5, 2, 0, 2] : [5, 1, 0]));
|
|
162
|
+
const greeting = await this.recv(2);
|
|
163
|
+
if (greeting[0] !== 5 || greeting[1] === 0xff)
|
|
164
|
+
throw new ConnectionError("SOCKS5 negotiation failed");
|
|
165
|
+
if (greeting[1] === 2) {
|
|
166
|
+
const username = new TextEncoder().encode(decodeURIComponent(proxy.username));
|
|
167
|
+
const password = new TextEncoder().encode(decodeURIComponent(proxy.password));
|
|
168
|
+
if (username.length > 255 || password.length > 255)
|
|
169
|
+
throw new TypeError("SOCKS5 credentials are too long");
|
|
170
|
+
await this.sendRaw(
|
|
171
|
+
Uint8Array.from([1, username.length, ...username, password.length, ...password]),
|
|
172
|
+
);
|
|
173
|
+
const auth = await this.recv(2);
|
|
174
|
+
if (auth[1] !== 0) throw new ConnectionError("SOCKS5 authentication failed");
|
|
175
|
+
} else if (greeting[1] !== 0) {
|
|
176
|
+
throw new ConnectionError(`Unsupported SOCKS5 auth method: ${greeting[1]}`);
|
|
177
|
+
}
|
|
178
|
+
const hostname = new TextEncoder().encode(this.host);
|
|
179
|
+
if (hostname.length > 255) throw new TypeError("SOCKS5 hostname is too long");
|
|
180
|
+
await this.sendRaw(
|
|
181
|
+
Uint8Array.from([
|
|
182
|
+
5,
|
|
183
|
+
1,
|
|
184
|
+
0,
|
|
185
|
+
3,
|
|
186
|
+
hostname.length,
|
|
187
|
+
...hostname,
|
|
188
|
+
(this.port >>> 8) & 0xff,
|
|
189
|
+
this.port & 0xff,
|
|
190
|
+
]),
|
|
191
|
+
);
|
|
192
|
+
const head = await this.recv(4);
|
|
193
|
+
if (head[0] !== 5 || head[1] !== 0)
|
|
194
|
+
throw new ConnectionError(`SOCKS5 CONNECT failed: ${head[1]}`);
|
|
195
|
+
const addressLength = head[3] === 1 ? 4 : head[3] === 4 ? 16 : (await this.recv(1))[0];
|
|
196
|
+
if (addressLength === undefined) throw new ConnectionError("Invalid SOCKS5 response");
|
|
197
|
+
await this.recv(addressLength + 2);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private async socks4Connect(proxy: URL): Promise<void> {
|
|
201
|
+
const user = new TextEncoder().encode(decodeURIComponent(proxy.username));
|
|
202
|
+
const host = new TextEncoder().encode(this.host);
|
|
203
|
+
const request = Uint8Array.from([
|
|
204
|
+
4,
|
|
205
|
+
1,
|
|
206
|
+
(this.port >>> 8) & 0xff,
|
|
207
|
+
this.port & 0xff,
|
|
208
|
+
0,
|
|
209
|
+
0,
|
|
210
|
+
0,
|
|
211
|
+
1,
|
|
212
|
+
...user,
|
|
213
|
+
0,
|
|
214
|
+
...host,
|
|
215
|
+
0,
|
|
216
|
+
]);
|
|
217
|
+
await this.sendRaw(request);
|
|
218
|
+
const response = await this.recv(8);
|
|
219
|
+
if (response[1] !== 90) throw new ConnectionError(`SOCKS4 CONNECT failed: ${response[1]}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private async upgradeTls(): Promise<void> {
|
|
223
|
+
const socket = this.socket;
|
|
224
|
+
if (!socket) throw new ConnectionError("Proxy socket closed before TLS upgrade");
|
|
225
|
+
await new Promise<void>((resolve, reject) => {
|
|
226
|
+
const handlers = this.handlers();
|
|
227
|
+
const [, tls] = socket.upgradeTLS({
|
|
228
|
+
tls: { serverName: this.host },
|
|
229
|
+
socket: {
|
|
230
|
+
...handlers,
|
|
231
|
+
open: () => resolve(),
|
|
232
|
+
error: (_socket, error) => {
|
|
233
|
+
void handlers.error?.(_socket, error);
|
|
234
|
+
reject(error);
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
this.socket = tls;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private async sendRaw(data: Uint8Array): Promise<void> {
|
|
243
|
+
const socket = this.socket;
|
|
244
|
+
if (!socket || socket.readyState <= 0) throw new ConnectionError("Socket is not connected");
|
|
245
|
+
let offset = 0;
|
|
246
|
+
while (offset < data.byteLength) {
|
|
247
|
+
const written = socket.write(data, offset, data.byteLength - offset);
|
|
248
|
+
if (written < 0) throw new ConnectionError("Socket closed while writing");
|
|
249
|
+
if (written === 0) {
|
|
250
|
+
await Bun.sleep(0);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
offset += written;
|
|
254
|
+
}
|
|
255
|
+
socket.flush();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private async readUntil(delimiter: Uint8Array): Promise<Uint8Array> {
|
|
259
|
+
let data = this.buffered;
|
|
260
|
+
for (;;) {
|
|
261
|
+
const index = findBytes(data, delimiter);
|
|
262
|
+
if (index >= 0) {
|
|
263
|
+
const end = index + delimiter.length;
|
|
264
|
+
const result = data.slice(0, end);
|
|
265
|
+
this.buffered = data.slice(end);
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
if (data.byteLength > 64 * 1024)
|
|
269
|
+
throw new ConnectionError("Proxy response header is too large");
|
|
270
|
+
data = concatBytes(data, await this.chunks.shift());
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function concatBytes(left: Uint8Array, right: Uint8Array): Uint8Array {
|
|
276
|
+
const result = new Uint8Array(left.byteLength + right.byteLength);
|
|
277
|
+
result.set(left);
|
|
278
|
+
result.set(right, left.byteLength);
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function defaultProxyPort(protocol: string): number {
|
|
283
|
+
if (protocol === "https:") return 443;
|
|
284
|
+
if (protocol === "http:") return 8080;
|
|
285
|
+
return 1080;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function findBytes(source: Uint8Array, target: Uint8Array): number {
|
|
289
|
+
outer: for (let index = 0; index <= source.length - target.length; index += 1) {
|
|
290
|
+
for (let offset = 0; offset < target.length; offset += 1) {
|
|
291
|
+
if (source[index + offset] !== target[offset]) continue outer;
|
|
292
|
+
}
|
|
293
|
+
return index;
|
|
294
|
+
}
|
|
295
|
+
return -1;
|
|
296
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { AsyncQueue, type Transport } from "./base.ts";
|
|
2
|
+
import { ConnectionError } from "../errors.ts";
|
|
3
|
+
|
|
4
|
+
export class WebSocketTransport implements Transport<string> {
|
|
5
|
+
private socket: WebSocket | null = null;
|
|
6
|
+
private readonly messages = new AsyncQueue<string>();
|
|
7
|
+
|
|
8
|
+
constructor(
|
|
9
|
+
readonly url: string,
|
|
10
|
+
readonly proxy: string | null = null,
|
|
11
|
+
) {}
|
|
12
|
+
|
|
13
|
+
get connected(): boolean {
|
|
14
|
+
return this.socket?.readyState === WebSocket.OPEN;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async connect(): Promise<void> {
|
|
18
|
+
if (this.socket) return;
|
|
19
|
+
const socket = new WebSocket(this.url, {
|
|
20
|
+
headers: { Origin: "https://web.max.ru" },
|
|
21
|
+
...(this.proxy ? { proxy: this.proxy } : {}),
|
|
22
|
+
});
|
|
23
|
+
this.socket = socket;
|
|
24
|
+
await new Promise<void>((resolve, reject) => {
|
|
25
|
+
socket.addEventListener("open", () => resolve(), { once: true });
|
|
26
|
+
socket.addEventListener(
|
|
27
|
+
"error",
|
|
28
|
+
() => reject(new ConnectionError("WebSocket connection failed")),
|
|
29
|
+
{
|
|
30
|
+
once: true,
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
socket.addEventListener("message", (event) => {
|
|
35
|
+
if (typeof event.data === "string") this.messages.push(event.data);
|
|
36
|
+
else this.messages.fail(new TypeError("Expected a text WebSocket frame"));
|
|
37
|
+
});
|
|
38
|
+
socket.addEventListener("close", () => {
|
|
39
|
+
this.socket = null;
|
|
40
|
+
this.messages.fail(new ConnectionError("WebSocket closed"));
|
|
41
|
+
});
|
|
42
|
+
socket.addEventListener("error", () =>
|
|
43
|
+
this.messages.fail(new ConnectionError("WebSocket failed")),
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async close(): Promise<void> {
|
|
48
|
+
const socket = this.socket;
|
|
49
|
+
this.socket = null;
|
|
50
|
+
if (socket && socket.readyState < WebSocket.CLOSING) socket.close();
|
|
51
|
+
this.messages.clear();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async send(data: string): Promise<void> {
|
|
55
|
+
if (!this.socket || !this.connected) throw new ConnectionError("Transport is not connected");
|
|
56
|
+
this.socket.send(data);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async recv(): Promise<string> {
|
|
60
|
+
if (!this.connected) throw new ConnectionError("Transport is not connected");
|
|
61
|
+
return await this.messages.shift();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { asRecord, asString } from "./utils.ts";
|
|
2
|
+
|
|
3
|
+
export const AttachmentType = {
|
|
4
|
+
PHOTO: "PHOTO",
|
|
5
|
+
VIDEO: "VIDEO",
|
|
6
|
+
FILE: "FILE",
|
|
7
|
+
CONTACT: "CONTACT",
|
|
8
|
+
STICKER: "STICKER",
|
|
9
|
+
AUDIO: "AUDIO",
|
|
10
|
+
CONTROL: "CONTROL",
|
|
11
|
+
INLINE_KEYBOARD: "INLINE_KEYBOARD",
|
|
12
|
+
SHARE: "SHARE",
|
|
13
|
+
CALL: "CALL",
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
export interface Attachment {
|
|
17
|
+
readonly type: string;
|
|
18
|
+
readonly raw: Readonly<Record<string, unknown>>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function parseAttachment(value: unknown): Attachment {
|
|
22
|
+
const raw = asRecord(value, "attachment");
|
|
23
|
+
return { type: asString(raw.type ?? raw._type, "UNKNOWN"), raw };
|
|
24
|
+
}
|