@leemour/max-cli 0.7.0 → 0.8.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 +198 -79
- package/dist/cache/schema.d.ts +13 -8
- package/dist/cache/schema.d.ts.map +1 -1
- package/dist/cache/schema.js +84 -23
- package/dist/cache/schema.js.map +1 -1
- package/dist/cache/store.d.ts +15 -0
- package/dist/cache/store.d.ts.map +1 -1
- package/dist/cache/store.js +32 -3
- package/dist/cache/store.js.map +1 -1
- package/dist/client.d.ts +28 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +110 -5
- package/dist/client.js.map +1 -1
- package/dist/commands/chats.d.ts.map +1 -1
- package/dist/commands/chats.js +8 -0
- package/dist/commands/chats.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +6 -2
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/context.d.ts +14 -2
- package/dist/commands/context.d.ts.map +1 -1
- package/dist/commands/context.js +18 -19
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/export.d.ts +3 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +83 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +9 -2
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/messages.d.ts.map +1 -1
- package/dist/commands/messages.js +126 -3
- package/dist/commands/messages.js.map +1 -1
- package/dist/commands/models.d.ts +7 -0
- package/dist/commands/models.d.ts.map +1 -0
- package/dist/commands/models.js +47 -0
- package/dist/commands/models.js.map +1 -0
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +50 -2
- package/dist/commands/serve.js.map +1 -1
- package/dist/commands/session.d.ts.map +1 -1
- package/dist/commands/session.js +13 -3
- package/dist/commands/session.js.map +1 -1
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/commands/watch.js +12 -16
- package/dist/commands/watch.js.map +1 -1
- package/dist/config.d.ts +18 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +58 -4
- package/dist/config.js.map +1 -1
- package/dist/domain/map.d.ts.map +1 -1
- package/dist/domain/map.js +5 -0
- package/dist/domain/map.js.map +1 -1
- package/dist/domain/models.d.ts +14 -0
- package/dist/domain/models.d.ts.map +1 -1
- package/dist/domain/models.js.map +1 -1
- package/dist/download.d.ts +2 -0
- package/dist/download.d.ts.map +1 -1
- package/dist/download.js +11 -0
- package/dist/download.js.map +1 -1
- package/dist/export.d.ts +26 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +75 -0
- package/dist/export.js.map +1 -0
- package/dist/generated/client.generated.d.ts +2 -0
- package/dist/generated/client.generated.d.ts.map +1 -1
- package/dist/generated/client.generated.js +2 -0
- package/dist/generated/client.generated.js.map +1 -1
- package/dist/generated/opcodes.generated.d.ts +0 -9
- package/dist/generated/opcodes.generated.d.ts.map +1 -1
- package/dist/generated/opcodes.generated.js +0 -9
- package/dist/generated/opcodes.generated.js.map +1 -1
- package/dist/generated/operations.generated.d.ts +18 -1
- package/dist/generated/operations.generated.d.ts.map +1 -1
- package/dist/generated/operations.generated.js +4 -2
- package/dist/generated/operations.generated.js.map +1 -1
- package/dist/mcp/confirm.d.ts +7 -0
- package/dist/mcp/confirm.d.ts.map +1 -1
- package/dist/mcp/confirm.js +16 -7
- package/dist/mcp/confirm.js.map +1 -1
- package/dist/mcp/instructions.d.ts +5 -1
- package/dist/mcp/instructions.d.ts.map +1 -1
- package/dist/mcp/instructions.js +16 -2
- package/dist/mcp/instructions.js.map +1 -1
- package/dist/mcp/server.d.ts +3 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +22 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session.d.ts +1 -1
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +1 -1
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/tools.d.ts +8 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +87 -10
- package/dist/mcp/tools.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +6 -2
- package/dist/program.js.map +1 -1
- package/dist/sends/guard.d.ts +7 -3
- package/dist/sends/guard.d.ts.map +1 -1
- package/dist/sends/guard.js +32 -11
- package/dist/sends/guard.js.map +1 -1
- package/dist/sends/journal.d.ts +6 -1
- package/dist/sends/journal.d.ts.map +1 -1
- package/dist/sends/journal.js.map +1 -1
- package/dist/sends/permissions.d.ts +11 -0
- package/dist/sends/permissions.d.ts.map +1 -0
- package/dist/sends/permissions.js +47 -0
- package/dist/sends/permissions.js.map +1 -0
- package/dist/server/server-connection.d.ts +25 -15
- package/dist/server/server-connection.d.ts.map +1 -1
- package/dist/server/server-connection.js +69 -63
- package/dist/server/server-connection.js.map +1 -1
- package/dist/server/server.d.ts +7 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +82 -19
- package/dist/server/server.js.map +1 -1
- package/dist/server/start.d.ts +17 -1
- package/dist/server/start.d.ts.map +1 -1
- package/dist/server/start.js +46 -7
- package/dist/server/start.js.map +1 -1
- package/dist/session/adopt.d.ts.map +1 -1
- package/dist/session/adopt.js +4 -0
- package/dist/session/adopt.js.map +1 -1
- package/dist/session/handshake.d.ts +1 -1
- package/dist/spec/index.js +4 -4
- package/dist/spec/index.js.map +1 -1
- package/dist/spec/operations/chats.d.ts +12 -2
- package/dist/spec/operations/chats.d.ts.map +1 -1
- package/dist/spec/operations/chats.js +32 -5
- package/dist/spec/operations/chats.js.map +1 -1
- package/dist/spec/operations/messages.d.ts +13 -1
- package/dist/spec/operations/messages.d.ts.map +1 -1
- package/dist/spec/operations/messages.js +18 -4
- package/dist/spec/operations/messages.js.map +1 -1
- package/dist/transcribe/index.d.ts +34 -0
- package/dist/transcribe/index.d.ts.map +1 -0
- package/dist/transcribe/index.js +43 -0
- package/dist/transcribe/index.js.map +1 -0
- package/dist/transcribe/install.d.ts +18 -0
- package/dist/transcribe/install.d.ts.map +1 -0
- package/dist/transcribe/install.js +67 -0
- package/dist/transcribe/install.js.map +1 -0
- package/dist/transcribe/models.d.ts +32 -0
- package/dist/transcribe/models.d.ts.map +1 -0
- package/dist/transcribe/models.js +124 -0
- package/dist/transcribe/models.js.map +1 -0
- package/dist/transcribe/speech.d.ts +25 -0
- package/dist/transcribe/speech.d.ts.map +1 -0
- package/dist/transcribe/speech.js +105 -0
- package/dist/transcribe/speech.js.map +1 -0
- package/dist/update.d.ts +1 -11
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +14 -42
- package/dist/update.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +8 -3
- package/skills/max-cli/SKILL.md +19 -3
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a profile may be allowed to do (`CLI-37`, `NEED-251`). One name per thing the owner would
|
|
3
|
+
* recognise, so `sessions` — which logs the owner out of their phone — never rides along with
|
|
4
|
+
* adding a contact. There is no `*`: "everything" is not setting `allow` at all, and a wildcard
|
|
5
|
+
* would switch on `delete` without anyone naming it.
|
|
6
|
+
*/
|
|
7
|
+
export const PERMISSIONS = [
|
|
8
|
+
"send",
|
|
9
|
+
"forward",
|
|
10
|
+
"reaction",
|
|
11
|
+
"edit",
|
|
12
|
+
"pin",
|
|
13
|
+
"read",
|
|
14
|
+
"delete",
|
|
15
|
+
"groups",
|
|
16
|
+
"contacts",
|
|
17
|
+
"profile",
|
|
18
|
+
"folders",
|
|
19
|
+
"sessions",
|
|
20
|
+
];
|
|
21
|
+
const ACCOUNT = {
|
|
22
|
+
"contact-add": "contacts",
|
|
23
|
+
"contact-remove": "contacts",
|
|
24
|
+
"contact-import": "contacts",
|
|
25
|
+
profile: "profile",
|
|
26
|
+
"folder-create": "folders",
|
|
27
|
+
"folder-update": "folders",
|
|
28
|
+
"folder-delete": "folders",
|
|
29
|
+
"sessions-end": "sessions",
|
|
30
|
+
};
|
|
31
|
+
export const permissionFor = (kind, action) => {
|
|
32
|
+
switch (kind) {
|
|
33
|
+
case "message":
|
|
34
|
+
return "send";
|
|
35
|
+
case "chat":
|
|
36
|
+
return "groups";
|
|
37
|
+
case "account": {
|
|
38
|
+
const permission = ACCOUNT[action];
|
|
39
|
+
if (!permission)
|
|
40
|
+
throw new Error(`an account change without a known action: ${action}`);
|
|
41
|
+
return permission;
|
|
42
|
+
}
|
|
43
|
+
default:
|
|
44
|
+
return kind;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=permissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/sends/permissions.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,KAAK;IACL,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,SAAS;IACT,SAAS;IACT,UAAU;CACF,CAAA;AAIV,MAAM,OAAO,GAAsC;IACjD,aAAa,EAAE,UAAU;IACzB,gBAAgB,EAAE,UAAU;IAC5B,gBAAgB,EAAE,UAAU;IAC5B,OAAO,EAAE,SAAS;IAClB,eAAe,EAAE,SAAS;IAC1B,eAAe,EAAE,SAAS;IAC1B,eAAe,EAAE,SAAS;IAC1B,cAAc,EAAE,UAAU;CAC3B,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAc,EAAE,MAAe,EAAc,EAAE;IAC3E,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,SAAS;YACZ,OAAO,MAAM,CAAA;QACf,KAAK,MAAM;YACT,OAAO,QAAQ,CAAA;QACjB,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,MAAM,UAAU,GAAG,OAAO,CAAC,MAAuB,CAAC,CAAA;YACnD,IAAI,CAAC,UAAU;gBAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,MAAM,EAAE,CAAC,CAAA;YACvF,OAAO,UAAU,CAAA;QACnB,CAAC;QACD;YACE,OAAO,IAAI,CAAA;IACf,CAAC;AACH,CAAC,CAAA"}
|
|
@@ -2,34 +2,44 @@ import { Connection, type Wire, type WireEvent } from "../protocol/connection.js
|
|
|
2
2
|
import { type Payload } from "../protocol/frame.js";
|
|
3
3
|
import type { SessionStore } from "../session/store.js";
|
|
4
4
|
/**
|
|
5
|
-
* **A command's connection
|
|
5
|
+
* **A command's connection: `max serve`'s, shared** (`MAX-35`, `NEED-229`).
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* One connection to MAX per profile, whoever asks — CLI commands, `max mcp`, `max watch`. INIT
|
|
8
|
+
* and LOGIN are answered by the server's own login; **every other request goes over its
|
|
9
|
+
* connection, sends included**. The send guards still apply: they run in the command's client,
|
|
10
|
+
* before the request reaches this wire.
|
|
11
|
+
*
|
|
12
|
+
* No server answering: `ensure` starts one and waits for it. A connection of the command's own
|
|
13
|
+
* is opened only when there is no `ensure` (the owner said `serve: false`) and no server, or for
|
|
14
|
+
* a token being tried out (`max session start`), which is by definition not the server's.
|
|
12
15
|
*/
|
|
13
16
|
export declare class ServerConnection implements Wire {
|
|
14
17
|
#private;
|
|
15
|
-
constructor({ path, store, timeoutMs, direct,
|
|
18
|
+
constructor({ path, store, timeoutMs, direct, ensure, }: {
|
|
16
19
|
path: string;
|
|
17
20
|
store: SessionStore;
|
|
18
21
|
timeoutMs?: number;
|
|
19
22
|
direct?: () => Connection;
|
|
20
|
-
/**
|
|
21
|
-
|
|
23
|
+
/** Starts a server when none answers and waits for it; `false` if it did not come up. */
|
|
24
|
+
ensure?: () => Promise<boolean>;
|
|
22
25
|
});
|
|
23
26
|
open(): Promise<void>;
|
|
24
27
|
invoke(opcode: number, payload?: Payload, watch?: (event: WireEvent) => void): Promise<Payload>;
|
|
25
28
|
close(): Promise<void>;
|
|
26
29
|
}
|
|
27
30
|
/**
|
|
28
|
-
* What
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
+
* What the server does **not** pass on: logging in is the server's own business, and a login by
|
|
32
|
+
* QR or SMS is `max session start` on a connection of its own. Everything else the specification
|
|
33
|
+
* lets a client send goes through the one connection.
|
|
34
|
+
*/
|
|
35
|
+
export declare const forwarded: (opcode: number) => boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Asks a running server to stop. `"refused"` is a server started by hand, which only Ctrl-C
|
|
38
|
+
* stops; `"none"` is nobody listening.
|
|
31
39
|
*/
|
|
32
|
-
export declare const
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
export declare const stopServer: (path: string, { force }?: {
|
|
41
|
+
force?: boolean | undefined;
|
|
42
|
+
}) => Promise<"stopped" | "refused" | "none">;
|
|
43
|
+
/** What a running server says about itself, or `undefined` when nobody answers. */
|
|
44
|
+
export declare const serverStatus: (path: string) => Promise<Record<string, unknown> | undefined>;
|
|
35
45
|
//# sourceMappingURL=server-connection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-connection.d.ts","sourceRoot":"","sources":["../../src/server/server-connection.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server-connection.d.ts","sourceRoot":"","sources":["../../src/server/server-connection.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAiB,KAAK,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAChG,OAAO,EAAQ,KAAK,OAAO,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AASvD;;;;;;;;;;;GAWG;AACH,qBAAa,gBAAiB,YAAW,IAAI;;gBAc/B,EACV,IAAI,EACJ,KAAK,EACL,SAAkB,EAClB,MAAM,EACN,MAAM,GACP,EAAE;QACD,IAAI,EAAE,MAAM,CAAA;QACZ,KAAK,EAAE,YAAY,CAAA;QACnB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,MAAM,CAAC,EAAE,MAAM,UAAU,CAAA;QACzB,yFAAyF;QACzF,MAAM,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;KAChC;IAQK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAErB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,OAAY,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAkDnG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAwE7B;AAED;;;;GAIG;AACH,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,KAAG,OAIxC,CAAA;AAEH;;;GAGG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,EAAE;;CAAsB,KAAG,OAAO,CAAC,SAAS,GAAG,SAAS,GAAG,MAAM,CAyBpG,CAAA;AAEJ,mFAAmF;AACnF,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAenF,CAAA"}
|
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
import { connect } from "node:net";
|
|
2
|
+
import { CliError } from "@leemour/cli-core";
|
|
2
3
|
import { Opcode } from "../generated/opcodes.generated.js";
|
|
4
|
+
import { OPERATIONS } from "../generated/operations.generated.js";
|
|
3
5
|
import { Connection, ProtocolError } from "../protocol/connection.js";
|
|
4
6
|
import { asId } from "../protocol/frame.js";
|
|
5
|
-
import { startSession } from "../session/handshake.js";
|
|
6
|
-
import { buildRequest } from "../spec/index.js";
|
|
7
7
|
import { fromLine, lineReader, toLine } from "./lines.js";
|
|
8
8
|
/**
|
|
9
|
-
* **A command's connection
|
|
9
|
+
* **A command's connection: `max serve`'s, shared** (`MAX-35`, `NEED-229`).
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* One connection to MAX per profile, whoever asks — CLI commands, `max mcp`, `max watch`. INIT
|
|
12
|
+
* and LOGIN are answered by the server's own login; **every other request goes over its
|
|
13
|
+
* connection, sends included**. The send guards still apply: they run in the command's client,
|
|
14
|
+
* before the request reaches this wire.
|
|
15
|
+
*
|
|
16
|
+
* No server answering: `ensure` starts one and waits for it. A connection of the command's own
|
|
17
|
+
* is opened only when there is no `ensure` (the owner said `serve: false`) and no server, or for
|
|
18
|
+
* a token being tried out (`max session start`), which is by definition not the server's.
|
|
16
19
|
*/
|
|
17
20
|
export class ServerConnection {
|
|
18
21
|
#path;
|
|
19
22
|
#store;
|
|
20
23
|
#timeoutMs;
|
|
21
24
|
#direct;
|
|
22
|
-
#
|
|
25
|
+
#ensure;
|
|
23
26
|
#pending = new Map();
|
|
24
27
|
#socket;
|
|
25
28
|
#id = 0;
|
|
@@ -27,9 +30,8 @@ export class ServerConnection {
|
|
|
27
30
|
/** What the client sent as INIT; a fallback at LOGIN has to send it first, or MAX refuses. */
|
|
28
31
|
#init;
|
|
29
32
|
#own;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
this.#onUnreachable = onUnreachable;
|
|
33
|
+
constructor({ path, store, timeoutMs = 30_000, direct, ensure, }) {
|
|
34
|
+
this.#ensure = ensure;
|
|
33
35
|
this.#path = path;
|
|
34
36
|
this.#store = store;
|
|
35
37
|
this.#timeoutMs = timeoutMs;
|
|
@@ -38,17 +40,11 @@ export class ServerConnection {
|
|
|
38
40
|
async open() { }
|
|
39
41
|
async invoke(opcode, payload = {}, watch) {
|
|
40
42
|
if (this.#own)
|
|
41
|
-
return this.#
|
|
43
|
+
return this.#own.invoke(opcode, payload, watch);
|
|
42
44
|
if (opcode === Opcode.SESSION_INIT) {
|
|
43
45
|
this.#init = payload;
|
|
44
|
-
this.#login = await this.#
|
|
45
|
-
|
|
46
|
-
this.#onUnreachable?.();
|
|
47
|
-
return undefined;
|
|
48
|
-
});
|
|
49
|
-
if (!this.#login)
|
|
50
|
-
return this.#fallBack(opcode, payload, watch);
|
|
51
|
-
return {};
|
|
46
|
+
this.#login = await this.#askServer({ login: true });
|
|
47
|
+
return this.#login ? {} : this.#fallBack(opcode, payload, watch);
|
|
52
48
|
}
|
|
53
49
|
if (opcode === Opcode.LOGIN) {
|
|
54
50
|
// A token being tried out (`max session start`) is not the one the server logged in with, and
|
|
@@ -58,13 +54,30 @@ export class ServerConnection {
|
|
|
58
54
|
}
|
|
59
55
|
return this.#login;
|
|
60
56
|
}
|
|
61
|
-
if (!READS.has(opcode))
|
|
62
|
-
return this.#ownRequest(opcode, payload, watch);
|
|
63
57
|
watch?.({ phase: "sent", seq: this.#id + 1, opcode, bytes: 0 });
|
|
64
58
|
const answer = await this.#ask({ opcode, payload });
|
|
65
59
|
watch?.({ phase: "received", seq: this.#id, opcode, bytes: 0 });
|
|
66
60
|
return answer;
|
|
67
61
|
}
|
|
62
|
+
/** The server's answer, starting one if `ensure` may; `undefined` when there is none to be had. */
|
|
63
|
+
async #askServer(request) {
|
|
64
|
+
try {
|
|
65
|
+
return await this.#ask(request);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
const code = error.code;
|
|
69
|
+
if (code !== "ECONNREFUSED" && code !== "ENOENT")
|
|
70
|
+
throw error;
|
|
71
|
+
if (!this.#ensure || !(await this.#ensure())) {
|
|
72
|
+
if (this.#ensure) {
|
|
73
|
+
throw new CliError("network_error", "`max serve` did not start, and this profile shares one connection (`serve: true`) — " +
|
|
74
|
+
"its log is beside the profile's state; `--no-serve` runs this command on its own");
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
return this.#ask(request);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
68
81
|
#sameAccount(login) {
|
|
69
82
|
const profile = login.profile;
|
|
70
83
|
const viewerId = this.#store.readState().viewerId;
|
|
@@ -80,7 +93,7 @@ export class ServerConnection {
|
|
|
80
93
|
this.#socket = undefined;
|
|
81
94
|
await this.#own?.close();
|
|
82
95
|
}
|
|
83
|
-
/** Before anything was sent: the command
|
|
96
|
+
/** Before anything was sent: the command runs on its own connection, as with no server at all. */
|
|
84
97
|
async #fallBack(opcode, payload, watch) {
|
|
85
98
|
this.#socket?.destroy();
|
|
86
99
|
this.#socket = undefined;
|
|
@@ -88,28 +101,8 @@ export class ServerConnection {
|
|
|
88
101
|
await this.#own.open();
|
|
89
102
|
if (opcode === Opcode.LOGIN && this.#init)
|
|
90
103
|
await this.#own.invoke(Opcode.SESSION_INIT, this.#init);
|
|
91
|
-
this.#ownLoggedIn = opcode === Opcode.LOGIN;
|
|
92
104
|
return this.#own.invoke(opcode, payload, watch);
|
|
93
105
|
}
|
|
94
|
-
/** A write mid-command: a connection of its own, logged in, then the request. */
|
|
95
|
-
async #ownRequest(opcode, payload, watch) {
|
|
96
|
-
if (!this.#own) {
|
|
97
|
-
this.#own = this.#direct();
|
|
98
|
-
await this.#own.open();
|
|
99
|
-
}
|
|
100
|
-
const own = this.#own;
|
|
101
|
-
if (!this.#ownLoggedIn && opcode !== Opcode.SESSION_INIT && opcode !== Opcode.LOGIN) {
|
|
102
|
-
const token = this.#store.readToken() ?? "";
|
|
103
|
-
await startSession((operation, request) => own.invoke(operation.opcode, buildRequest(operation, request)), {
|
|
104
|
-
token,
|
|
105
|
-
deviceId: this.#store.readState().deviceId,
|
|
106
|
-
});
|
|
107
|
-
this.#ownLoggedIn = true;
|
|
108
|
-
}
|
|
109
|
-
if (opcode === Opcode.LOGIN)
|
|
110
|
-
this.#ownLoggedIn = true;
|
|
111
|
-
return own.invoke(opcode, payload, watch);
|
|
112
|
-
}
|
|
113
106
|
async #ask(request) {
|
|
114
107
|
const socket = await this.#connected();
|
|
115
108
|
this.#id += 1;
|
|
@@ -161,35 +154,48 @@ export class ServerConnection {
|
|
|
161
154
|
}
|
|
162
155
|
}
|
|
163
156
|
/**
|
|
164
|
-
* What
|
|
165
|
-
*
|
|
166
|
-
*
|
|
157
|
+
* What the server does **not** pass on: logging in is the server's own business, and a login by
|
|
158
|
+
* QR or SMS is `max session start` on a connection of its own. Everything else the specification
|
|
159
|
+
* lets a client send goes through the one connection.
|
|
160
|
+
*/
|
|
161
|
+
export const forwarded = (opcode) => Object.values(OPERATIONS).some((operation) => operation.opcode === opcode && !operation.name.startsWith("session.") && !operation.name.startsWith("login."));
|
|
162
|
+
/**
|
|
163
|
+
* Asks a running server to stop. `"refused"` is a server started by hand, which only Ctrl-C
|
|
164
|
+
* stops; `"none"` is nobody listening.
|
|
167
165
|
*/
|
|
168
|
-
export const
|
|
169
|
-
Opcode.CHAT_HISTORY,
|
|
170
|
-
Opcode.CHATS_LIST,
|
|
171
|
-
Opcode.CONTACT_INFO,
|
|
172
|
-
Opcode.FILE_DOWNLOAD,
|
|
173
|
-
Opcode.VIDEO_PLAY,
|
|
174
|
-
Opcode.MSG_GET_REACTIONS,
|
|
175
|
-
]);
|
|
176
|
-
/** Asks a running server to stop. Resolves whether or not one was there. */
|
|
177
|
-
export const stopServer = (path) => new Promise((resolve) => {
|
|
166
|
+
export const stopServer = (path, { force = false } = {}) => new Promise((resolve) => {
|
|
178
167
|
const socket = connect(path);
|
|
168
|
+
let answer = "none";
|
|
179
169
|
const timer = setTimeout(() => {
|
|
180
170
|
socket.destroy();
|
|
181
|
-
resolve(
|
|
171
|
+
resolve(answer);
|
|
182
172
|
}, 2000);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
socket.
|
|
173
|
+
// `force` is a person's own `max serve --stop`, which a server started by hand obeys too.
|
|
174
|
+
socket.once("connect", () => socket.write(toLine({ id: 1, stop: true, ...(force ? { force } : {}) })));
|
|
175
|
+
socket.on("data", lineReader((line) => {
|
|
176
|
+
answer = fromLine(line).stopped === true ? "stopped" : "refused";
|
|
177
|
+
}));
|
|
186
178
|
socket.once("error", () => {
|
|
187
179
|
clearTimeout(timer);
|
|
188
|
-
resolve(
|
|
180
|
+
resolve("none");
|
|
189
181
|
});
|
|
182
|
+
// After the answer, the server closes its end: stopped, it is gone once this fires.
|
|
190
183
|
socket.once("close", () => {
|
|
191
184
|
clearTimeout(timer);
|
|
192
|
-
resolve(
|
|
185
|
+
resolve(answer);
|
|
193
186
|
});
|
|
194
187
|
});
|
|
188
|
+
/** What a running server says about itself, or `undefined` when nobody answers. */
|
|
189
|
+
export const serverStatus = (path) => new Promise((resolve) => {
|
|
190
|
+
const socket = connect(path);
|
|
191
|
+
const done = (status) => {
|
|
192
|
+
clearTimeout(timer);
|
|
193
|
+
socket.destroy();
|
|
194
|
+
resolve(status);
|
|
195
|
+
};
|
|
196
|
+
const timer = setTimeout(() => done(undefined), 2000);
|
|
197
|
+
socket.once("connect", () => socket.write(toLine({ status: true })));
|
|
198
|
+
socket.on("data", lineReader((line) => done(fromLine(line))));
|
|
199
|
+
socket.once("error", () => done(undefined));
|
|
200
|
+
});
|
|
195
201
|
//# sourceMappingURL=server-connection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-connection.js","sourceRoot":"","sources":["../../src/server/server-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAe,MAAM,UAAU,CAAA;AAC/C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"server-connection.js","sourceRoot":"","sources":["../../src/server/server-connection.ts"],"names":[],"mappings":"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"}
|
package/dist/server/server.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export type ServerEvent = {
|
|
|
10
10
|
event: "status";
|
|
11
11
|
connected: boolean;
|
|
12
12
|
at: string;
|
|
13
|
+
byHand?: boolean;
|
|
14
|
+
pid?: number;
|
|
13
15
|
};
|
|
14
16
|
export interface MaxServerOptions {
|
|
15
17
|
store: SessionStore;
|
|
@@ -23,6 +25,11 @@ export interface MaxServerOptions {
|
|
|
23
25
|
/** The least time between two background logins after the snapshot went stale. */
|
|
24
26
|
refreshEveryMs?: number;
|
|
25
27
|
retryAfterMs?: (attempt: number) => number;
|
|
28
|
+
/**
|
|
29
|
+
* **A command started it**, not a person. Only such a server stops when asked over its socket;
|
|
30
|
+
* one started by hand runs until Ctrl-C, whoever asks (the owner's rule, 2026-09-24).
|
|
31
|
+
*/
|
|
32
|
+
startedByCommand?: boolean;
|
|
26
33
|
/**
|
|
27
34
|
* Stop after this long with nobody using it — no request, no `max watch`. Absent, it runs until
|
|
28
35
|
* stopped; a server started by a command gets one, or it would outlive every reason it had.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAa,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAa,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAErD,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAiB,MAAM,2BAA2B,CAAA;AAC7F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAIvD,MAAM,MAAM,WAAW,GACnB;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,UAAU,CAAA;CAAE,GACzC;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvF,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,YAAY,CAAA;IACnB,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wCAAwC;IACxC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,6DAA6D;IAC7D,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,GAAG,SAAS,CAAC,KAAK,UAAU,CAAA;IAClF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kFAAkF;IAClF,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;IAC1C;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,6EAA6E;IAC7E,MAAM,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAA;CACpC;AAWD;;;;;;;;;;;;;GAaG;AACH,qBAAa,SAAS;;IAqBpB,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;gBAEhB,OAAO,EAAE,gBAAgB;IAUrC,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,2FAA2F;IACrF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BtB,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;CA6PzC;AAKD,mGAAmG;AACnG,eAAO,MAAM,YAAY,GAAI,OAAO,YAAY,KAAG,MAA0C,CAAA;AAE7F,yFAAyF;AACzF,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,KAAG,OAAO,CAAC,OAAO,CAQlD,CAAA"}
|
package/dist/server/server.js
CHANGED
|
@@ -3,11 +3,14 @@ import { connect, createServer } from "node:net";
|
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
import { CliError } from "@leemour/cli-core";
|
|
5
5
|
import { MaxClient } from "../client.js";
|
|
6
|
+
import { Opcode } from "../generated/opcodes.generated.js";
|
|
6
7
|
import { Connection, ProtocolError } from "../protocol/connection.js";
|
|
7
8
|
import { fromLine, lineReader, toLine } from "./lines.js";
|
|
8
|
-
import {
|
|
9
|
+
import { forwarded, stopServer } from "./server-connection.js";
|
|
9
10
|
/** The web client's keep-alive interval (web.max.ru bundle, 2026-09-24). A different one is a fingerprint. */
|
|
10
11
|
const PING_EVERY_MS = 30_000;
|
|
12
|
+
/** A message arrived — what MAX pushes, and what a send through this server is handed on as. */
|
|
13
|
+
const NEW_MESSAGE = 128;
|
|
11
14
|
/** A login MAX counts; after one, the next background login waits at least this long. */
|
|
12
15
|
const REFRESH_EVERY_MS = 60_000;
|
|
13
16
|
/**
|
|
@@ -33,8 +36,13 @@ export class MaxServer {
|
|
|
33
36
|
#retry;
|
|
34
37
|
#attempt = 0;
|
|
35
38
|
#stopped = false;
|
|
36
|
-
/**
|
|
37
|
-
#
|
|
39
|
+
/** Every client on the socket — a command, `max mcp`, `max watch`. Idle means none at all. */
|
|
40
|
+
#open = new Set();
|
|
41
|
+
/** Message ids already handed to watchers, newest last. */
|
|
42
|
+
#seen = new Set();
|
|
43
|
+
/** Settles when a connection is up; replaced while the server reconnects. */
|
|
44
|
+
#up;
|
|
45
|
+
#markUp = () => { };
|
|
38
46
|
#refresh;
|
|
39
47
|
#lastRefresh = 0;
|
|
40
48
|
#lastUse = Date.now();
|
|
@@ -44,6 +52,9 @@ export class MaxServer {
|
|
|
44
52
|
done;
|
|
45
53
|
constructor(options) {
|
|
46
54
|
this.#options = options;
|
|
55
|
+
this.#up = new Promise((resolve) => {
|
|
56
|
+
this.#markUp = resolve;
|
|
57
|
+
});
|
|
47
58
|
this.done = new Promise((resolve, reject) => {
|
|
48
59
|
this.#finish = (error) => (error ? reject(error) : resolve());
|
|
49
60
|
});
|
|
@@ -53,9 +64,15 @@ export class MaxServer {
|
|
|
53
64
|
}
|
|
54
65
|
/** Logs in first: a token that does not work should fail here, before anything listens. */
|
|
55
66
|
async start() {
|
|
67
|
+
// The socket first: whoever binds it is the profile's one server, and a second one learns it
|
|
68
|
+
// before it has logged in — not after, with a login MAX has already counted.
|
|
56
69
|
try {
|
|
57
|
-
await this.#connect();
|
|
58
70
|
await this.#listen();
|
|
71
|
+
await this.#connect();
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
await this.stop();
|
|
75
|
+
throw error;
|
|
59
76
|
}
|
|
60
77
|
finally {
|
|
61
78
|
rmSync(startingPath(this.#options.store), { force: true });
|
|
@@ -63,7 +80,7 @@ export class MaxServer {
|
|
|
63
80
|
const { idleMs } = this.#options;
|
|
64
81
|
if (idleMs !== undefined) {
|
|
65
82
|
this.#idle = setInterval(() => {
|
|
66
|
-
if (this.#
|
|
83
|
+
if (this.#open.size === 0 && Date.now() - this.#lastUse >= idleMs) {
|
|
67
84
|
this.#options.note(`nobody has used it for ${Math.round(idleMs / 60_000)} min — stopping`);
|
|
68
85
|
void this.stop();
|
|
69
86
|
}
|
|
@@ -74,15 +91,21 @@ export class MaxServer {
|
|
|
74
91
|
if (this.#stopped)
|
|
75
92
|
return;
|
|
76
93
|
this.#stopped = true;
|
|
94
|
+
// Anyone still waiting for a connection gets "not connected" rather than waiting forever.
|
|
95
|
+
this.#markUp();
|
|
77
96
|
clearInterval(this.#ping);
|
|
78
97
|
clearTimeout(this.#retry);
|
|
79
98
|
clearTimeout(this.#refresh);
|
|
80
99
|
clearInterval(this.#idle);
|
|
81
|
-
for (const socket of this.#
|
|
100
|
+
for (const socket of this.#open)
|
|
82
101
|
socket.destroy();
|
|
83
102
|
this.#subscribers.clear();
|
|
84
|
-
|
|
85
|
-
|
|
103
|
+
// Only the server that bound the socket removes it: one refused as "already running" would
|
|
104
|
+
// otherwise delete the running server's socket and let a third start beside it.
|
|
105
|
+
if (this.#listener) {
|
|
106
|
+
await new Promise((resolve) => this.#listener?.close(() => resolve()));
|
|
107
|
+
rmSync(this.#options.store.socketPath(), { force: true });
|
|
108
|
+
}
|
|
86
109
|
await this.#client?.close();
|
|
87
110
|
this.#client = undefined;
|
|
88
111
|
this.#finish?.(error);
|
|
@@ -128,8 +151,8 @@ export class MaxServer {
|
|
|
128
151
|
const replaced = this.#client;
|
|
129
152
|
mine = client;
|
|
130
153
|
this.#client = client;
|
|
154
|
+
this.#markUp();
|
|
131
155
|
this.#attempt = 0;
|
|
132
|
-
this.#stale = false;
|
|
133
156
|
this.#lastRefresh = Date.now();
|
|
134
157
|
clearInterval(this.#ping);
|
|
135
158
|
this.#ping = setInterval(() => {
|
|
@@ -155,7 +178,6 @@ export class MaxServer {
|
|
|
155
178
|
* in again in the background — at most once a minute, since each is a login MAX counts.
|
|
156
179
|
*/
|
|
157
180
|
#goneStale() {
|
|
158
|
-
this.#stale = true;
|
|
159
181
|
if (this.#refresh || this.#stopped)
|
|
160
182
|
return;
|
|
161
183
|
const every = this.#options.refreshEveryMs ?? REFRESH_EVERY_MS;
|
|
@@ -172,6 +194,9 @@ export class MaxServer {
|
|
|
172
194
|
clearInterval(this.#ping);
|
|
173
195
|
const client = this.#client;
|
|
174
196
|
this.#client = undefined;
|
|
197
|
+
this.#up = new Promise((resolve) => {
|
|
198
|
+
this.#markUp = resolve;
|
|
199
|
+
});
|
|
175
200
|
client?.close().catch(() => { });
|
|
176
201
|
if (this.#stopped)
|
|
177
202
|
return;
|
|
@@ -193,13 +218,22 @@ export class MaxServer {
|
|
|
193
218
|
}, wait);
|
|
194
219
|
}
|
|
195
220
|
#broadcast(event) {
|
|
221
|
+
if (event.event === "message") {
|
|
222
|
+
// A retried send answers with the same message, and MAX repeats pushes after a hiccup.
|
|
223
|
+
if (this.#seen.has(event.message.id))
|
|
224
|
+
return;
|
|
225
|
+
this.#seen.add(event.message.id);
|
|
226
|
+
if (this.#seen.size > 500)
|
|
227
|
+
this.#seen.delete(this.#seen.values().next().value);
|
|
228
|
+
}
|
|
196
229
|
const line = toLine(event);
|
|
197
230
|
for (const socket of this.#subscribers)
|
|
198
231
|
socket.write(line);
|
|
199
232
|
}
|
|
200
233
|
async #listen() {
|
|
201
234
|
const path = this.#options.store.socketPath();
|
|
202
|
-
|
|
235
|
+
// A server a command started gives way to one started by hand; one started by hand does not.
|
|
236
|
+
if ((await answers(path)) && !(!this.#options.startedByCommand && (await stopServer(path)) === "stopped")) {
|
|
203
237
|
throw new CliError("validation_error", `a server is already running for profile "${this.#options.store.profile}"`);
|
|
204
238
|
}
|
|
205
239
|
// Nobody answers on it, so it is what a crashed server left behind.
|
|
@@ -214,9 +248,11 @@ export class MaxServer {
|
|
|
214
248
|
this.#listener = listener;
|
|
215
249
|
}
|
|
216
250
|
#serve(socket) {
|
|
251
|
+
this.#open.add(socket);
|
|
217
252
|
socket.on("error", () => this.#subscribers.delete(socket));
|
|
218
253
|
socket.on("close", () => {
|
|
219
254
|
this.#subscribers.delete(socket);
|
|
255
|
+
this.#open.delete(socket);
|
|
220
256
|
this.#lastUse = Date.now();
|
|
221
257
|
});
|
|
222
258
|
socket.on("data", lineReader((line) => {
|
|
@@ -232,7 +268,13 @@ export class MaxServer {
|
|
|
232
268
|
socket.write(toLine({ error: { code: "bad_request", message: "one JSON object per line" } }));
|
|
233
269
|
return;
|
|
234
270
|
}
|
|
235
|
-
const status = {
|
|
271
|
+
const status = {
|
|
272
|
+
event: "status",
|
|
273
|
+
connected: this.connected,
|
|
274
|
+
at: new Date().toISOString(),
|
|
275
|
+
byHand: !this.#options.startedByCommand,
|
|
276
|
+
pid: process.pid,
|
|
277
|
+
};
|
|
236
278
|
const { id } = request;
|
|
237
279
|
this.#lastUse = Date.now();
|
|
238
280
|
if (request.subscribe === true) {
|
|
@@ -243,16 +285,24 @@ export class MaxServer {
|
|
|
243
285
|
socket.write(toLine(status));
|
|
244
286
|
}
|
|
245
287
|
else if (request.stop === true) {
|
|
246
|
-
// Only the owner can reach this socket (mode 600)
|
|
247
|
-
// session is not kept alive by a server
|
|
288
|
+
// Only the owner can reach this socket (mode 600). `max session end` asks, so a forgotten
|
|
289
|
+
// session is not kept alive by a server a command started — one started by hand stays.
|
|
290
|
+
if (!this.#options.startedByCommand && request.force !== true) {
|
|
291
|
+
socket.end(toLine({ id, stopped: false, reason: "started by hand; Ctrl-C, or `max serve --stop`" }));
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
248
294
|
socket.end(toLine({ id, stopped: true }));
|
|
249
295
|
await this.stop();
|
|
250
296
|
}
|
|
251
297
|
else if (request.login === true) {
|
|
298
|
+
// A request that arrives while the server logs in, or logs in again, waits for it: the
|
|
299
|
+
// command's own timeout is what gives up, not this. A login a deletion made stale is still
|
|
300
|
+
// handed out — only a deleted last message can be wrong in it — while a fresh one is fetched.
|
|
301
|
+
await this.#up;
|
|
252
302
|
const client = this.#client;
|
|
253
|
-
socket.write(toLine(client
|
|
303
|
+
socket.write(toLine(client
|
|
254
304
|
? { id, payload: client.live.snapshot() }
|
|
255
|
-
: { id, error: { code: "unavailable", message:
|
|
305
|
+
: { id, error: { code: "unavailable", message: "not connected" } }));
|
|
256
306
|
}
|
|
257
307
|
else if (typeof request.opcode === "number") {
|
|
258
308
|
socket.write(toLine({ id, ...(await this.#forward(request.opcode, request.payload)) }));
|
|
@@ -262,16 +312,29 @@ export class MaxServer {
|
|
|
262
312
|
}
|
|
263
313
|
}
|
|
264
314
|
async #forward(opcode, payload) {
|
|
315
|
+
await this.#up;
|
|
265
316
|
const client = this.#client;
|
|
266
|
-
if (!
|
|
317
|
+
if (!forwarded(opcode)) {
|
|
267
318
|
return {
|
|
268
|
-
error: { code: "not_allowed", message: `opcode ${opcode} is
|
|
319
|
+
error: { code: "not_allowed", message: `opcode ${opcode} is a login; the server does its own` },
|
|
269
320
|
};
|
|
270
321
|
}
|
|
271
322
|
if (!client)
|
|
272
323
|
return { error: { code: "unavailable", message: "not connected" } };
|
|
273
324
|
try {
|
|
274
|
-
|
|
325
|
+
const request = (payload ?? {});
|
|
326
|
+
const answer = await client.live.forward(opcode, request);
|
|
327
|
+
// MAX does not push a message back to the connection that sent it, and every send now comes
|
|
328
|
+
// through this one — so `max watch` would never see what `max` itself sent. The answer
|
|
329
|
+
// carries the message; hand it on as if it had been pushed. A scheduled one is not sent yet.
|
|
330
|
+
const scheduled = request.message?.delayedAttributes;
|
|
331
|
+
if (opcode === Opcode.MSG_SEND && answer.message && !scheduled) {
|
|
332
|
+
this.#pushed(client, NEW_MESSAGE, { chatId: request.chatId, message: answer.message });
|
|
333
|
+
}
|
|
334
|
+
// Nor does it push our own deletion back; the chat's last message may be the one deleted.
|
|
335
|
+
if (opcode === Opcode.MSG_DELETE)
|
|
336
|
+
this.#goneStale();
|
|
337
|
+
return { payload: answer };
|
|
275
338
|
}
|
|
276
339
|
catch (error) {
|
|
277
340
|
if (error instanceof ProtocolError) {
|