@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,123 @@
|
|
|
1
|
+
export interface TextElement {
|
|
2
|
+
type: string;
|
|
3
|
+
from: number;
|
|
4
|
+
length: number;
|
|
5
|
+
attributes?: { url?: string };
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const markers = new Map([
|
|
9
|
+
["```", "CODE"],
|
|
10
|
+
["**", "STRONG"],
|
|
11
|
+
["__", "UNDERLINE"],
|
|
12
|
+
["~~", "STRIKETHROUGH"],
|
|
13
|
+
["`", "MONOSPACED"],
|
|
14
|
+
["_", "EMPHASIZED"],
|
|
15
|
+
["*", "EMPHASIZED"],
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export function formatMarkdown(text: string): { text: string; elements: TextElement[] } {
|
|
19
|
+
let clean = "";
|
|
20
|
+
let index = 0;
|
|
21
|
+
let cleanPosition = 0;
|
|
22
|
+
let lineStart = true;
|
|
23
|
+
const active = new Map<string, number>();
|
|
24
|
+
const elements: TextElement[] = [];
|
|
25
|
+
|
|
26
|
+
while (index < text.length) {
|
|
27
|
+
const link = parseLink(text, index);
|
|
28
|
+
if (link) {
|
|
29
|
+
const length = utf16Length(link.label);
|
|
30
|
+
elements.push({ type: "LINK", from: cleanPosition, length, attributes: { url: link.url } });
|
|
31
|
+
clean += link.label;
|
|
32
|
+
cleanPosition += length;
|
|
33
|
+
index = link.next;
|
|
34
|
+
lineStart = false;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const current = text[index] as string;
|
|
39
|
+
if (lineStart && (current === "#" || current === ">")) {
|
|
40
|
+
const result = parseLineEntity(text, index, current === "#" ? "HEADING" : "QUOTE");
|
|
41
|
+
if (result) {
|
|
42
|
+
clean += result.value;
|
|
43
|
+
const length = utf16Length(result.value);
|
|
44
|
+
elements.push({ type: result.type, from: cleanPosition, length });
|
|
45
|
+
cleanPosition += length;
|
|
46
|
+
index = result.next;
|
|
47
|
+
lineStart = false;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let handled = false;
|
|
53
|
+
for (const [marker, type] of markers) {
|
|
54
|
+
if (!text.startsWith(marker, index)) continue;
|
|
55
|
+
const open = active.get(marker);
|
|
56
|
+
if (open === undefined) {
|
|
57
|
+
const closing = text.indexOf(marker, index + marker.length);
|
|
58
|
+
const newline = text.indexOf("\n", index + marker.length);
|
|
59
|
+
if (
|
|
60
|
+
closing <= index + marker.length ||
|
|
61
|
+
(marker !== "```" && newline !== -1 && closing > newline)
|
|
62
|
+
) {
|
|
63
|
+
clean += marker;
|
|
64
|
+
cleanPosition += utf16Length(marker);
|
|
65
|
+
} else {
|
|
66
|
+
active.set(marker, cleanPosition);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
const length = cleanPosition - open;
|
|
70
|
+
if (length > 0) elements.push({ type, from: open, length });
|
|
71
|
+
active.delete(marker);
|
|
72
|
+
}
|
|
73
|
+
index += marker.length;
|
|
74
|
+
handled = true;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
if (handled) {
|
|
78
|
+
lineStart = false;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
clean += current;
|
|
83
|
+
cleanPosition += utf16Length(current);
|
|
84
|
+
index += 1;
|
|
85
|
+
lineStart = current === "\n";
|
|
86
|
+
}
|
|
87
|
+
return { text: clean, elements };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function parseLink(
|
|
91
|
+
text: string,
|
|
92
|
+
index: number,
|
|
93
|
+
): { label: string; url: string; next: number } | null {
|
|
94
|
+
if (text[index] !== "[") return null;
|
|
95
|
+
const labelEnd = text.indexOf("]", index + 1);
|
|
96
|
+
if (labelEnd < 0 || text[labelEnd + 1] !== "(") return null;
|
|
97
|
+
const urlEnd = text.indexOf(")", labelEnd + 2);
|
|
98
|
+
if (urlEnd < 0) return null;
|
|
99
|
+
const label = text.slice(index + 1, labelEnd);
|
|
100
|
+
const url = text.slice(labelEnd + 2, urlEnd);
|
|
101
|
+
return label && url ? { label, url, next: urlEnd + 1 } : null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function parseLineEntity(
|
|
105
|
+
text: string,
|
|
106
|
+
index: number,
|
|
107
|
+
type: "HEADING" | "QUOTE",
|
|
108
|
+
): { type: string; value: string; next: number } | null {
|
|
109
|
+
let contentStart = index + 1;
|
|
110
|
+
if (type === "HEADING") {
|
|
111
|
+
while (text[contentStart] === "#") contentStart += 1;
|
|
112
|
+
if (text[contentStart] !== " ") return null;
|
|
113
|
+
}
|
|
114
|
+
if (text[contentStart] === " ") contentStart += 1;
|
|
115
|
+
const newline = text.indexOf("\n", contentStart);
|
|
116
|
+
const next = newline < 0 ? text.length : newline;
|
|
117
|
+
const value = text.slice(contentStart, next);
|
|
118
|
+
return value ? { type, value, next } : null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function utf16Length(value: string): number {
|
|
122
|
+
return value.length;
|
|
123
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const VERSION = "0.1.0";
|
|
2
|
+
|
|
3
|
+
export * from "./api/index.ts";
|
|
4
|
+
export * from "./auth/index.ts";
|
|
5
|
+
export * from "./client.ts";
|
|
6
|
+
export * from "./config.ts";
|
|
7
|
+
export * from "./dispatch/index.ts";
|
|
8
|
+
export * from "./errors.ts";
|
|
9
|
+
export * from "./files/index.ts";
|
|
10
|
+
export * from "./formatting/index.ts";
|
|
11
|
+
export * from "./logging.ts";
|
|
12
|
+
export * from "./protocol/index.ts";
|
|
13
|
+
export * from "./session/index.ts";
|
|
14
|
+
export * from "./telemetry/index.ts";
|
|
15
|
+
export * from "./types/index.ts";
|
package/src/logging.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type LogLevel = "DEBUG" | "INFO" | "WARN" | "ERROR" | "SILENT";
|
|
2
|
+
|
|
3
|
+
export interface Logger {
|
|
4
|
+
debug(message: string, context?: Record<string, unknown>): void;
|
|
5
|
+
info(message: string, context?: Record<string, unknown>): void;
|
|
6
|
+
warn(message: string, context?: Record<string, unknown>): void;
|
|
7
|
+
error(message: string, context?: Record<string, unknown>): void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const priorities: Record<LogLevel, number> = {
|
|
11
|
+
DEBUG: 10,
|
|
12
|
+
INFO: 20,
|
|
13
|
+
WARN: 30,
|
|
14
|
+
ERROR: 40,
|
|
15
|
+
SILENT: 100,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
class ConsoleLogger implements Logger {
|
|
19
|
+
constructor(private readonly level: LogLevel) {}
|
|
20
|
+
|
|
21
|
+
debug(message: string, context?: Record<string, unknown>): void {
|
|
22
|
+
this.write("DEBUG", message, context);
|
|
23
|
+
}
|
|
24
|
+
info(message: string, context?: Record<string, unknown>): void {
|
|
25
|
+
this.write("INFO", message, context);
|
|
26
|
+
}
|
|
27
|
+
warn(message: string, context?: Record<string, unknown>): void {
|
|
28
|
+
this.write("WARN", message, context);
|
|
29
|
+
}
|
|
30
|
+
error(message: string, context?: Record<string, unknown>): void {
|
|
31
|
+
this.write("ERROR", message, context);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private write(
|
|
35
|
+
level: Exclude<LogLevel, "SILENT">,
|
|
36
|
+
message: string,
|
|
37
|
+
context?: Record<string, unknown>,
|
|
38
|
+
): void {
|
|
39
|
+
if (priorities[level] < priorities[this.level]) return;
|
|
40
|
+
const suffix = context ? ` ${JSON.stringify(redact(context))}` : "";
|
|
41
|
+
const line = `[tsmax] ${level.toLowerCase()} ${message}${suffix}`;
|
|
42
|
+
if (level === "ERROR") console.error(line);
|
|
43
|
+
else if (level === "WARN") console.warn(line);
|
|
44
|
+
else console.log(line);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let activeLogger: Logger = new ConsoleLogger("INFO");
|
|
49
|
+
|
|
50
|
+
export function configureLogging(level: LogLevel = "INFO", logger?: Logger): Logger {
|
|
51
|
+
activeLogger = logger ?? new ConsoleLogger(level);
|
|
52
|
+
return activeLogger;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getLogger(): Logger {
|
|
56
|
+
return activeLogger;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function redact(value: unknown): unknown {
|
|
60
|
+
if (Array.isArray(value)) return value.map(redact);
|
|
61
|
+
if (value === null || typeof value !== "object") return value;
|
|
62
|
+
const result: Record<string, unknown> = {};
|
|
63
|
+
for (const [key, item] of Object.entries(value)) {
|
|
64
|
+
result[key] = /token|password|phone|code/i.test(key) ? "[redacted]" : redact(item);
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const DEFAULT_MAX_OUTPUT = 5 * 1024 * 1024;
|
|
2
|
+
|
|
3
|
+
export class Lz4BlockCompression {
|
|
4
|
+
decompress(source: Uint8Array, maxOutput = DEFAULT_MAX_OUTPUT): Uint8Array {
|
|
5
|
+
const output: number[] = [];
|
|
6
|
+
let position = 0;
|
|
7
|
+
|
|
8
|
+
while (position < source.length) {
|
|
9
|
+
const token = source[position++];
|
|
10
|
+
if (token === undefined) break;
|
|
11
|
+
|
|
12
|
+
let literalLength = token >>> 4;
|
|
13
|
+
if (literalLength === 15) {
|
|
14
|
+
while (position < source.length) {
|
|
15
|
+
const byte = source[position++];
|
|
16
|
+
if (byte === undefined) break;
|
|
17
|
+
literalLength += byte;
|
|
18
|
+
if (byte !== 255) break;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (position + literalLength > source.length) {
|
|
23
|
+
throw new RangeError("LZ4: literal length out of bounds");
|
|
24
|
+
}
|
|
25
|
+
for (let index = 0; index < literalLength; index += 1) {
|
|
26
|
+
output.push(source[position + index] as number);
|
|
27
|
+
}
|
|
28
|
+
position += literalLength;
|
|
29
|
+
this.ensureSize(output.length, maxOutput);
|
|
30
|
+
if (position >= source.length) break;
|
|
31
|
+
|
|
32
|
+
if (position + 1 >= source.length) throw new RangeError("LZ4: incomplete offset");
|
|
33
|
+
const offset = (source[position] as number) | ((source[position + 1] as number) << 8);
|
|
34
|
+
position += 2;
|
|
35
|
+
if (offset === 0) throw new RangeError("LZ4: zero offset");
|
|
36
|
+
|
|
37
|
+
let matchLength = (token & 0x0f) + 4;
|
|
38
|
+
if ((token & 0x0f) === 0x0f) {
|
|
39
|
+
while (position < source.length) {
|
|
40
|
+
const byte = source[position++];
|
|
41
|
+
if (byte === undefined) break;
|
|
42
|
+
matchLength += byte;
|
|
43
|
+
if (byte !== 255) break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const matchPosition = output.length - offset;
|
|
48
|
+
if (matchPosition < 0) throw new RangeError("LZ4: match out of bounds");
|
|
49
|
+
for (let index = 0; index < matchLength; index += 1) {
|
|
50
|
+
output.push(output[matchPosition + (index % offset)] as number);
|
|
51
|
+
}
|
|
52
|
+
this.ensureSize(output.length, maxOutput);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return Uint8Array.from(output);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private ensureSize(size: number, maximum: number): void {
|
|
59
|
+
if (size > maximum) throw new RangeError("LZ4: output too large");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class ZstdCompression {
|
|
64
|
+
decompress(source: Uint8Array, maxOutput = DEFAULT_MAX_OUTPUT): Uint8Array {
|
|
65
|
+
const output = Bun.zstdDecompressSync(source);
|
|
66
|
+
if (output.byteLength > maxOutput) throw new RangeError("Zstd: output too large");
|
|
67
|
+
return output;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
export const Command = {
|
|
2
|
+
REQUEST: 0,
|
|
3
|
+
RESPONSE: 1,
|
|
4
|
+
EVENT: 2,
|
|
5
|
+
ERROR: 3,
|
|
6
|
+
} as const;
|
|
7
|
+
export type Command = (typeof Command)[keyof typeof Command];
|
|
8
|
+
|
|
9
|
+
export const Opcode = {
|
|
10
|
+
PING: 1,
|
|
11
|
+
DEBUG: 2,
|
|
12
|
+
RECONNECT: 3,
|
|
13
|
+
LOG: 5,
|
|
14
|
+
SESSION_INIT: 6,
|
|
15
|
+
PROFILE: 16,
|
|
16
|
+
AUTH_REQUEST: 17,
|
|
17
|
+
AUTH: 18,
|
|
18
|
+
LOGIN: 19,
|
|
19
|
+
LOGOUT: 20,
|
|
20
|
+
SYNC: 21,
|
|
21
|
+
CONFIG: 22,
|
|
22
|
+
AUTH_CONFIRM: 23,
|
|
23
|
+
PRESET_AVATARS: 25,
|
|
24
|
+
ASSETS_GET: 26,
|
|
25
|
+
ASSETS_UPDATE: 27,
|
|
26
|
+
ASSETS_GET_BY_IDS: 28,
|
|
27
|
+
ASSETS_ADD: 29,
|
|
28
|
+
SEARCH_FEEDBACK: 31,
|
|
29
|
+
CONTACT_INFO: 32,
|
|
30
|
+
CONTACT_ADD: 33,
|
|
31
|
+
CONTACT_UPDATE: 34,
|
|
32
|
+
CONTACT_PRESENCE: 35,
|
|
33
|
+
CONTACT_LIST: 36,
|
|
34
|
+
CONTACT_SEARCH: 37,
|
|
35
|
+
CONTACT_MUTUAL: 38,
|
|
36
|
+
CONTACT_PHOTOS: 39,
|
|
37
|
+
CONTACT_SORT: 40,
|
|
38
|
+
CONTACT_VERIFY: 42,
|
|
39
|
+
REMOVE_CONTACT_PHOTO: 43,
|
|
40
|
+
CONTACT_INFO_BY_PHONE: 46,
|
|
41
|
+
CHAT_INFO: 48,
|
|
42
|
+
CHAT_HISTORY: 49,
|
|
43
|
+
CHAT_MARK: 50,
|
|
44
|
+
CHAT_MEDIA: 51,
|
|
45
|
+
CHAT_DELETE: 52,
|
|
46
|
+
CHATS_LIST: 53,
|
|
47
|
+
CHAT_CLEAR: 54,
|
|
48
|
+
CHAT_UPDATE: 55,
|
|
49
|
+
CHAT_CHECK_LINK: 56,
|
|
50
|
+
CHAT_JOIN: 57,
|
|
51
|
+
CHAT_LEAVE: 58,
|
|
52
|
+
CHAT_MEMBERS: 59,
|
|
53
|
+
PUBLIC_SEARCH: 60,
|
|
54
|
+
CHAT_PERSONAL_CONFIG: 61,
|
|
55
|
+
CHAT_LIVESTREAM_INFO: 62,
|
|
56
|
+
CHAT_CREATE: 63,
|
|
57
|
+
MSG_SEND: 64,
|
|
58
|
+
MSG_TYPING: 65,
|
|
59
|
+
MSG_DELETE: 66,
|
|
60
|
+
MSG_EDIT: 67,
|
|
61
|
+
CHAT_SEARCH: 68,
|
|
62
|
+
MSG_SHARE_PREVIEW: 70,
|
|
63
|
+
MSG_GET: 71,
|
|
64
|
+
MSG_SEARCH_TOUCH: 72,
|
|
65
|
+
MSG_SEARCH: 73,
|
|
66
|
+
MSG_GET_STAT: 74,
|
|
67
|
+
CHAT_SUBSCRIBE: 75,
|
|
68
|
+
VIDEO_CHAT_START: 76,
|
|
69
|
+
CHAT_MEMBERS_UPDATE: 77,
|
|
70
|
+
VIDEO_CHAT_START_ACTIVE: 78,
|
|
71
|
+
VIDEO_CHAT_HISTORY: 79,
|
|
72
|
+
PHOTO_UPLOAD: 80,
|
|
73
|
+
STICKER_UPLOAD: 81,
|
|
74
|
+
VIDEO_UPLOAD: 82,
|
|
75
|
+
VIDEO_PLAY: 83,
|
|
76
|
+
VIDEO_CHAT_CREATE_JOIN_LINK: 84,
|
|
77
|
+
CHAT_PIN_SET_VISIBILITY: 86,
|
|
78
|
+
FILE_UPLOAD: 87,
|
|
79
|
+
FILE_DOWNLOAD: 88,
|
|
80
|
+
LINK_INFO: 89,
|
|
81
|
+
MSG_DELETE_RANGE: 92,
|
|
82
|
+
SESSIONS_INFO: 96,
|
|
83
|
+
SESSIONS_CLOSE: 97,
|
|
84
|
+
PHONE_BIND_REQUEST: 98,
|
|
85
|
+
PHONE_BIND_CONFIRM: 99,
|
|
86
|
+
AUTH_LOGIN_RESTORE_PASSWORD: 101,
|
|
87
|
+
GET_INBOUND_CALLS: 103,
|
|
88
|
+
AUTH_2FA_DETAILS: 104,
|
|
89
|
+
EXTERNAL_CALLBACK: 105,
|
|
90
|
+
AUTH_VALIDATE_PASSWORD: 107,
|
|
91
|
+
AUTH_VALIDATE_HINT: 108,
|
|
92
|
+
AUTH_VERIFY_EMAIL: 109,
|
|
93
|
+
AUTH_CHECK_EMAIL: 110,
|
|
94
|
+
AUTH_SET_2FA: 111,
|
|
95
|
+
AUTH_CREATE_TRACK: 112,
|
|
96
|
+
AUTH_CHECK_PASSWORD: 113,
|
|
97
|
+
AUTH_LOGIN_CHECK_PASSWORD: 115,
|
|
98
|
+
AUTH_LOGIN_PROFILE_DELETE: 116,
|
|
99
|
+
CHAT_COMPLAIN: 117,
|
|
100
|
+
MSG_SEND_CALLBACK: 118,
|
|
101
|
+
SUSPEND_BOT: 119,
|
|
102
|
+
LOCATION_STOP: 124,
|
|
103
|
+
LOCATION_SEND: 125,
|
|
104
|
+
LOCATION_REQUEST: 126,
|
|
105
|
+
GET_LAST_MENTIONS: 127,
|
|
106
|
+
NOTIF_MESSAGE: 128,
|
|
107
|
+
NOTIF_TYPING: 129,
|
|
108
|
+
NOTIF_MARK: 130,
|
|
109
|
+
NOTIF_CONTACT: 131,
|
|
110
|
+
NOTIF_PRESENCE: 132,
|
|
111
|
+
NOTIF_CONFIG: 134,
|
|
112
|
+
NOTIF_CHAT: 135,
|
|
113
|
+
NOTIF_ATTACH: 136,
|
|
114
|
+
NOTIF_CALL_START: 137,
|
|
115
|
+
NOTIF_CONTACT_SORT: 139,
|
|
116
|
+
NOTIF_MSG_DELETE_RANGE: 140,
|
|
117
|
+
NOTIF_MSG_DELETE: 142,
|
|
118
|
+
NOTIF_CALLBACK_ANSWER: 143,
|
|
119
|
+
CHAT_BOT_COMMANDS: 144,
|
|
120
|
+
BOT_INFO: 145,
|
|
121
|
+
NOTIF_LOCATION: 147,
|
|
122
|
+
NOTIF_LOCATION_REQUEST: 148,
|
|
123
|
+
NOTIF_ASSETS_UPDATE: 150,
|
|
124
|
+
NOTIF_DRAFT: 152,
|
|
125
|
+
NOTIF_DRAFT_DISCARD: 153,
|
|
126
|
+
NOTIF_MSG_DELAYED: 154,
|
|
127
|
+
NOTIF_MSG_REACTIONS_CHANGED: 155,
|
|
128
|
+
NOTIF_MSG_YOU_REACTED: 156,
|
|
129
|
+
CALLS_TOKEN: 158,
|
|
130
|
+
NOTIF_PROFILE: 159,
|
|
131
|
+
WEB_APP_INIT_DATA: 160,
|
|
132
|
+
COMPLAIN: 161,
|
|
133
|
+
COMPLAIN_REASONS_GET: 162,
|
|
134
|
+
VIDEO_CHAT_JOIN: 166,
|
|
135
|
+
DRAFT_SAVE: 176,
|
|
136
|
+
DRAFT_DISCARD: 177,
|
|
137
|
+
MSG_REACTION: 178,
|
|
138
|
+
MSG_CANCEL_REACTION: 179,
|
|
139
|
+
MSG_GET_REACTIONS: 180,
|
|
140
|
+
MSG_GET_DETAILED_REACTIONS: 181,
|
|
141
|
+
STICKER_CREATE: 193,
|
|
142
|
+
STICKER_SUGGEST: 194,
|
|
143
|
+
VIDEO_CHAT_MEMBERS: 195,
|
|
144
|
+
CHAT_HIDE: 196,
|
|
145
|
+
CHAT_SEARCH_COMMON_PARTICIPANTS: 198,
|
|
146
|
+
PROFILE_DELETE: 199,
|
|
147
|
+
PROFILE_DELETE_TIME: 200,
|
|
148
|
+
TRANSCRIBE_MEDIA: 202,
|
|
149
|
+
ORG_INFO: 256,
|
|
150
|
+
CHAT_REACTIONS_SETTINGS_SET: 257,
|
|
151
|
+
REACTIONS_SETTINGS_GET_BY_CHAT_ID: 258,
|
|
152
|
+
ASSETS_REMOVE: 259,
|
|
153
|
+
ASSETS_MOVE: 260,
|
|
154
|
+
ASSETS_LIST_MODIFY: 261,
|
|
155
|
+
FOLDERS_GET: 272,
|
|
156
|
+
FOLDERS_GET_BY_ID: 273,
|
|
157
|
+
FOLDERS_UPDATE: 274,
|
|
158
|
+
FOLDERS_REORDER: 275,
|
|
159
|
+
FOLDERS_DELETE: 276,
|
|
160
|
+
NOTIF_FOLDERS: 277,
|
|
161
|
+
GET_QR: 288,
|
|
162
|
+
GET_QR_STATUS: 289,
|
|
163
|
+
AUTH_QR_APPROVE: 290,
|
|
164
|
+
LOGIN_BY_QR: 291,
|
|
165
|
+
NOTIF_BANNERS: 292,
|
|
166
|
+
NOTIF_TRANSCRIPTION: 293,
|
|
167
|
+
CHAT_SUGGEST: 300,
|
|
168
|
+
AUDIO_PLAY: 301,
|
|
169
|
+
BANNERS_GET: 302,
|
|
170
|
+
MSG_DELIVERY: 303,
|
|
171
|
+
SEND_VOTE: 304,
|
|
172
|
+
VOTERS_LIST_BY_ANSWER: 305,
|
|
173
|
+
GET_POLL_UPDATES: 306,
|
|
174
|
+
} as const;
|
|
175
|
+
export type Opcode = (typeof Opcode)[keyof typeof Opcode];
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { decode, encode } from "@msgpack/msgpack";
|
|
2
|
+
|
|
3
|
+
import { Lz4BlockCompression, ZstdCompression } from "./compression.ts";
|
|
4
|
+
import type { InboundFrame, OutboundFrame, PackedPacket, Payload, Protocol } from "./types.ts";
|
|
5
|
+
|
|
6
|
+
export const TCP_HEADER_SIZE = 10;
|
|
7
|
+
|
|
8
|
+
export class TcpPacketFramer {
|
|
9
|
+
pack(options: {
|
|
10
|
+
ver: number;
|
|
11
|
+
cmd: number;
|
|
12
|
+
seq: number;
|
|
13
|
+
opcode: number;
|
|
14
|
+
flags: number;
|
|
15
|
+
payloadBytes: Uint8Array;
|
|
16
|
+
}): Uint8Array {
|
|
17
|
+
if (options.payloadBytes.byteLength > 0x00ff_ffff) {
|
|
18
|
+
throw new RangeError("TCP payload exceeds the 24-bit length field");
|
|
19
|
+
}
|
|
20
|
+
const result = new Uint8Array(TCP_HEADER_SIZE + options.payloadBytes.byteLength);
|
|
21
|
+
const view = new DataView(result.buffer);
|
|
22
|
+
view.setUint8(0, options.ver);
|
|
23
|
+
view.setUint8(1, options.cmd);
|
|
24
|
+
view.setUint16(2, options.seq, false);
|
|
25
|
+
view.setUint16(4, options.opcode, false);
|
|
26
|
+
const packedLength = ((options.flags & 0xff) << 24) | options.payloadBytes.byteLength;
|
|
27
|
+
view.setUint32(6, packedLength >>> 0, false);
|
|
28
|
+
result.set(options.payloadBytes, TCP_HEADER_SIZE);
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
unpack(data: Uint8Array): PackedPacket | null {
|
|
33
|
+
if (data.byteLength < TCP_HEADER_SIZE) return null;
|
|
34
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
35
|
+
const packedLength = view.getUint32(6, false);
|
|
36
|
+
const payloadLength = packedLength & 0x00ff_ffff;
|
|
37
|
+
if (data.byteLength < TCP_HEADER_SIZE + payloadLength) return null;
|
|
38
|
+
return {
|
|
39
|
+
header: {
|
|
40
|
+
ver: view.getUint8(0),
|
|
41
|
+
cmd: view.getUint8(1),
|
|
42
|
+
seq: view.getUint16(2, false),
|
|
43
|
+
opcode: view.getUint16(4, false),
|
|
44
|
+
flags: packedLength >>> 24,
|
|
45
|
+
payloadLength,
|
|
46
|
+
},
|
|
47
|
+
payloadBytes: data.slice(TCP_HEADER_SIZE, TCP_HEADER_SIZE + payloadLength),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
unpackPayloadLength(data: Uint8Array): number | null {
|
|
52
|
+
if (data.byteLength < TCP_HEADER_SIZE) return null;
|
|
53
|
+
return (
|
|
54
|
+
new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(6, false) & 0x00ff_ffff
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class MsgpackPayloadCodec {
|
|
60
|
+
encode(payload: unknown): Uint8Array {
|
|
61
|
+
if (payload === null || payload === undefined) return new Uint8Array();
|
|
62
|
+
return encode(payload, { useBigInt64: true, ignoreUndefined: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
decode(payload: Uint8Array): Payload {
|
|
66
|
+
if (payload.byteLength === 0) return {};
|
|
67
|
+
return normalizeKeys(decode(payload, { useBigInt64: true })) as Payload;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export class TcpPayloadDecoder {
|
|
72
|
+
constructor(
|
|
73
|
+
private readonly serializer = new MsgpackPayloadCodec(),
|
|
74
|
+
private readonly lz4 = new Lz4BlockCompression(),
|
|
75
|
+
private readonly zstd = new ZstdCompression(),
|
|
76
|
+
) {}
|
|
77
|
+
|
|
78
|
+
decode(payload: Uint8Array, flags = 0): Payload {
|
|
79
|
+
if (payload.byteLength === 0) return {};
|
|
80
|
+
let decoded = payload;
|
|
81
|
+
if (flags === 0xff) decoded = this.zstd.decompress(payload);
|
|
82
|
+
else if (flags > 0x7f) throw new RangeError(`Invalid TCP compression factor: ${flags}`);
|
|
83
|
+
else if (flags > 0) decoded = this.lz4.decompress(payload);
|
|
84
|
+
return this.serializer.decode(decoded);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export class TcpProtocol implements Protocol<Uint8Array> {
|
|
89
|
+
readonly version = 10;
|
|
90
|
+
readonly framer = new TcpPacketFramer();
|
|
91
|
+
readonly serializer = new MsgpackPayloadCodec();
|
|
92
|
+
readonly payloadDecoder = new TcpPayloadDecoder(this.serializer);
|
|
93
|
+
|
|
94
|
+
encode(frame: OutboundFrame): Uint8Array {
|
|
95
|
+
const payloadBytes = this.serializer.encode(frame.payload);
|
|
96
|
+
return this.framer.pack({
|
|
97
|
+
ver: this.version,
|
|
98
|
+
cmd: frame.cmd ?? 0,
|
|
99
|
+
seq: frame.seq,
|
|
100
|
+
opcode: frame.opcode,
|
|
101
|
+
flags: 0,
|
|
102
|
+
payloadBytes,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
decode(raw: Uint8Array): InboundFrame {
|
|
107
|
+
const packet = this.framer.unpack(raw);
|
|
108
|
+
if (!packet) return { opcode: 0, cmd: 0, seq: null, payload: null, raw: null };
|
|
109
|
+
const payload = this.payloadDecoder.decode(packet.payloadBytes, packet.header.flags);
|
|
110
|
+
return {
|
|
111
|
+
opcode: packet.header.opcode,
|
|
112
|
+
cmd: packet.header.cmd,
|
|
113
|
+
seq: packet.header.seq,
|
|
114
|
+
payload,
|
|
115
|
+
raw: payload,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function normalizeKeys(value: unknown): unknown {
|
|
121
|
+
if (Array.isArray(value)) return value.map(normalizeKeys);
|
|
122
|
+
if (value instanceof Map) {
|
|
123
|
+
const result: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
|
|
124
|
+
for (const [key, item] of value) result[normalizeKey(key)] = normalizeKeys(item);
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
if (value !== null && typeof value === "object" && !(value instanceof Uint8Array)) {
|
|
128
|
+
const result: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
|
|
129
|
+
for (const [key, item] of Object.entries(value))
|
|
130
|
+
result[normalizeKey(key)] = normalizeKeys(item);
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function normalizeKey(value: unknown): string {
|
|
137
|
+
if (value instanceof Uint8Array) return new TextDecoder().decode(value);
|
|
138
|
+
return String(value);
|
|
139
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type WirePrimitive = null | boolean | number | bigint | string | Uint8Array;
|
|
2
|
+
export type WireValue =
|
|
3
|
+
| WirePrimitive
|
|
4
|
+
| WireValue[]
|
|
5
|
+
| { readonly [key: string]: WireValue }
|
|
6
|
+
| ReadonlyMap<WirePrimitive, WireValue>;
|
|
7
|
+
|
|
8
|
+
export type Payload = Record<string, unknown>;
|
|
9
|
+
|
|
10
|
+
export interface OutboundFrame {
|
|
11
|
+
readonly ver: number;
|
|
12
|
+
readonly opcode: number;
|
|
13
|
+
readonly cmd?: number;
|
|
14
|
+
readonly seq: number;
|
|
15
|
+
readonly payload?: Payload | null;
|
|
16
|
+
readonly compress?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface InboundFrame {
|
|
20
|
+
readonly opcode: number;
|
|
21
|
+
readonly cmd: number;
|
|
22
|
+
readonly seq: number | null;
|
|
23
|
+
readonly payload: Payload | null;
|
|
24
|
+
readonly raw: Payload | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface TcpPacketHeader {
|
|
28
|
+
readonly ver: number;
|
|
29
|
+
readonly cmd: number;
|
|
30
|
+
readonly seq: number;
|
|
31
|
+
readonly opcode: number;
|
|
32
|
+
readonly flags: number;
|
|
33
|
+
readonly payloadLength: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface PackedPacket {
|
|
37
|
+
readonly header: TcpPacketHeader;
|
|
38
|
+
readonly payloadBytes: Uint8Array;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface Protocol<Encoded extends string | Uint8Array = string | Uint8Array> {
|
|
42
|
+
readonly version: number;
|
|
43
|
+
encode(frame: OutboundFrame): Encoded;
|
|
44
|
+
decode(raw: Encoded): InboundFrame;
|
|
45
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { isInteger, isSafeNumber, parse, stringify } from "lossless-json";
|
|
2
|
+
|
|
3
|
+
import type { InboundFrame, OutboundFrame, Payload, Protocol } from "./types.ts";
|
|
4
|
+
|
|
5
|
+
export class WsProtocol implements Protocol<string> {
|
|
6
|
+
readonly version = 11;
|
|
7
|
+
|
|
8
|
+
encode(frame: OutboundFrame): string {
|
|
9
|
+
return stringify({ ...frame, cmd: frame.cmd ?? 0, ver: this.version }) as string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
decode(raw: string): InboundFrame {
|
|
13
|
+
try {
|
|
14
|
+
const value = parse(raw, undefined, {
|
|
15
|
+
parseNumber: (text) => {
|
|
16
|
+
if (isInteger(text) && !isSafeNumber(text)) return BigInt(text);
|
|
17
|
+
return Number(text);
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
if (!isRecord(value)) return emptyFrame();
|
|
21
|
+
const opcode = safeHeaderNumber(value.opcode);
|
|
22
|
+
const cmd = safeHeaderNumber(value.cmd ?? 0);
|
|
23
|
+
const seq =
|
|
24
|
+
value.seq === null || value.seq === undefined ? null : safeHeaderNumber(value.seq);
|
|
25
|
+
const payload = isRecord(value.payload) ? value.payload : null;
|
|
26
|
+
return { opcode, cmd, seq, payload, raw: payload };
|
|
27
|
+
} catch {
|
|
28
|
+
return emptyFrame();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function safeHeaderNumber(value: unknown): number {
|
|
34
|
+
const result = typeof value === "bigint" ? Number(value) : value;
|
|
35
|
+
if (typeof result !== "number" || !Number.isSafeInteger(result)) {
|
|
36
|
+
throw new TypeError("Invalid WebSocket frame header");
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isRecord(value: unknown): value is Payload {
|
|
42
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function emptyFrame(): InboundFrame {
|
|
46
|
+
return { opcode: 0, cmd: 0, seq: null, payload: null, raw: null };
|
|
47
|
+
}
|