@mtcute/core 0.25.0 → 0.25.2

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.
@@ -7,6 +7,19 @@ if (typeof globalThis !== "undefined" && !globalThis._MTCUTE_CJS_DEPRECATION_WAR
7
7
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
8
8
  const resolvePeer = require("../users/resolve-peer.cjs");
9
9
  const empty = ["", void 0];
10
+ function adjustOffsets(entities, from, by) {
11
+ for (const ent of entities) {
12
+ if (ent.offset < from) continue;
13
+ if (by >= 0) {
14
+ ent.offset += by;
15
+ continue;
16
+ }
17
+ const adjustTotal = Math.min(ent.offset, Math.abs(by));
18
+ const adjustInternal = Math.max(Math.abs(by) - ent.offset, 0);
19
+ ent.offset -= adjustTotal;
20
+ ent.length -= adjustInternal;
21
+ }
22
+ }
10
23
  async function _normalizeInputText(client, input) {
11
24
  if (!input) {
12
25
  return empty;
@@ -14,8 +27,10 @@ async function _normalizeInputText(client, input) {
14
27
  if (typeof input === "string") {
15
28
  return [input, void 0];
16
29
  }
17
- const { text, entities } = input;
30
+ let text = input.text;
31
+ const entities = input.entities;
18
32
  if (!entities) return [text, void 0];
33
+ let canTrimStart = true;
19
34
  for (const ent of entities) {
20
35
  if (ent._ === "messageEntityMentionName") {
21
36
  try {
@@ -27,6 +42,20 @@ async function _normalizeInputText(client, input) {
27
42
  client.log.warn("Failed to resolve mention entity for %s: %s", ent.userId, e);
28
43
  }
29
44
  }
45
+ if (ent._ === "messageEntityPre" && ent.offset === 0) {
46
+ canTrimStart = false;
47
+ }
48
+ }
49
+ if (canTrimStart) {
50
+ const resultTrimmed = text.trimStart();
51
+ const numCharsTrimmed = text.length - resultTrimmed.length;
52
+ text = resultTrimmed;
53
+ adjustOffsets(entities, 0, -numCharsTrimmed);
54
+ }
55
+ text = text.trimEnd();
56
+ for (const ent of entities) {
57
+ const end = ent.offset + ent.length;
58
+ if (end > text.length) ent.length = text.length - ent.offset;
30
59
  }
31
60
  return [text, entities];
32
61
  }
@@ -1,5 +1,18 @@
1
1
  import { resolveUser } from "../users/resolve-peer.js";
2
2
  const empty = ["", void 0];
3
+ function adjustOffsets(entities, from, by) {
4
+ for (const ent of entities) {
5
+ if (ent.offset < from) continue;
6
+ if (by >= 0) {
7
+ ent.offset += by;
8
+ continue;
9
+ }
10
+ const adjustTotal = Math.min(ent.offset, Math.abs(by));
11
+ const adjustInternal = Math.max(Math.abs(by) - ent.offset, 0);
12
+ ent.offset -= adjustTotal;
13
+ ent.length -= adjustInternal;
14
+ }
15
+ }
3
16
  async function _normalizeInputText(client, input) {
4
17
  if (!input) {
5
18
  return empty;
@@ -7,8 +20,10 @@ async function _normalizeInputText(client, input) {
7
20
  if (typeof input === "string") {
8
21
  return [input, void 0];
9
22
  }
10
- const { text, entities } = input;
23
+ let text = input.text;
24
+ const entities = input.entities;
11
25
  if (!entities) return [text, void 0];
26
+ let canTrimStart = true;
12
27
  for (const ent of entities) {
13
28
  if (ent._ === "messageEntityMentionName") {
14
29
  try {
@@ -20,6 +35,20 @@ async function _normalizeInputText(client, input) {
20
35
  client.log.warn("Failed to resolve mention entity for %s: %s", ent.userId, e);
21
36
  }
22
37
  }
38
+ if (ent._ === "messageEntityPre" && ent.offset === 0) {
39
+ canTrimStart = false;
40
+ }
41
+ }
42
+ if (canTrimStart) {
43
+ const resultTrimmed = text.trimStart();
44
+ const numCharsTrimmed = text.length - resultTrimmed.length;
45
+ text = resultTrimmed;
46
+ adjustOffsets(entities, 0, -numCharsTrimmed);
47
+ }
48
+ text = text.trimEnd();
49
+ for (const ent of entities) {
50
+ const end = ent.offset + ent.length;
51
+ if (end > text.length) ent.length = text.length - ent.offset;
23
52
  }
24
53
  return [text, entities];
25
54
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -97,12 +97,14 @@ class UpdatesManager {
97
97
  this._onCatchingUp = handler;
98
98
  }
99
99
  destroy() {
100
+ this.log.debug("destroyed, stopping updates loop");
100
101
  this.stopLoop();
101
102
  }
102
103
  notifyLoggedIn() {
103
104
  this.startLoop().catch((err) => this.client.onError.emit(utils.unknownToError(err)));
104
105
  }
105
106
  notifyLoggedOut() {
107
+ this.log.debug("notifyLoggedOut, stopping updates loop");
106
108
  this.stopLoop();
107
109
  this.cpts.clear();
108
110
  this.cptsMod.clear();
@@ -316,6 +318,7 @@ class UpdatesManager {
316
318
  } catch (e) {
317
319
  if (tl.tl.RpcError.is(e, "AUTH_KEY_UNREGISTERED")) {
318
320
  lock.release();
321
+ this.log.debug("received AUTH_KEY_UNREGISTERED, stopping updates loop");
319
322
  this.stopLoop();
320
323
  return;
321
324
  }
@@ -812,6 +815,7 @@ class UpdatesManager {
812
815
  0,
813
816
  this._fetchDifference(requestedDiff).catch((err) => {
814
817
  if (tl.tl.RpcError.is(err, "AUTH_KEY_UNREGISTERED")) {
818
+ this.log.debug("received AUTH_KEY_UNREGISTERED, stopping updates loop");
815
819
  this.stopLoop();
816
820
  return;
817
821
  }
@@ -90,12 +90,14 @@ class UpdatesManager {
90
90
  this._onCatchingUp = handler;
91
91
  }
92
92
  destroy() {
93
+ this.log.debug("destroyed, stopping updates loop");
93
94
  this.stopLoop();
94
95
  }
95
96
  notifyLoggedIn() {
96
97
  this.startLoop().catch((err) => this.client.onError.emit(unknownToError(err)));
97
98
  }
98
99
  notifyLoggedOut() {
100
+ this.log.debug("notifyLoggedOut, stopping updates loop");
99
101
  this.stopLoop();
100
102
  this.cpts.clear();
101
103
  this.cptsMod.clear();
@@ -309,6 +311,7 @@ class UpdatesManager {
309
311
  } catch (e) {
310
312
  if (tl.RpcError.is(e, "AUTH_KEY_UNREGISTERED")) {
311
313
  lock.release();
314
+ this.log.debug("received AUTH_KEY_UNREGISTERED, stopping updates loop");
312
315
  this.stopLoop();
313
316
  return;
314
317
  }
@@ -805,6 +808,7 @@ class UpdatesManager {
805
808
  0,
806
809
  this._fetchDifference(requestedDiff).catch((err) => {
807
810
  if (tl.RpcError.is(err, "AUTH_KEY_UNREGISTERED")) {
811
+ this.log.debug("received AUTH_KEY_UNREGISTERED, stopping updates loop");
808
812
  this.stopLoop();
809
813
  return;
810
814
  }
@@ -9,14 +9,16 @@ const utils = require("@fuman/utils");
9
9
  const errors = require("./errors.cjs");
10
10
  const protocol = require("./protocol.cjs");
11
11
  class WorkerInvoker {
12
- constructor(send) {
12
+ constructor(send, workerId) {
13
13
  this.send = send;
14
+ this.workerId = workerId;
14
15
  }
15
16
  _nextId = 0;
16
17
  _pending = /* @__PURE__ */ new Map();
17
18
  _invoke(target, method, args, isVoid, abortSignal) {
18
19
  const id = this._nextId++;
19
20
  this.send({
21
+ _mtcuteWorkerId: this.workerId,
20
22
  type: "invoke",
21
23
  id,
22
24
  target,
@@ -27,6 +29,7 @@ class WorkerInvoker {
27
29
  });
28
30
  abortSignal?.addEventListener("abort", () => {
29
31
  this.send({
32
+ _mtcuteWorkerId: this.workerId,
30
33
  type: "abort",
31
34
  id
32
35
  });
@@ -4,7 +4,8 @@ export type InvokeTarget = Extract<WorkerInboundMessage, {
4
4
  }>['target'];
5
5
  export declare class WorkerInvoker {
6
6
  private send;
7
- constructor(send: SendFn);
7
+ private readonly workerId;
8
+ constructor(send: SendFn, workerId: string);
8
9
  private _nextId;
9
10
  private _pending;
10
11
  private _invoke;
@@ -4,7 +4,8 @@ export type InvokeTarget = Extract<WorkerInboundMessage, {
4
4
  }>['target'];
5
5
  export declare class WorkerInvoker {
6
6
  private send;
7
- constructor(send: SendFn);
7
+ private readonly workerId;
8
+ constructor(send: SendFn, workerId: string);
8
9
  private _nextId;
9
10
  private _pending;
10
11
  private _invoke;
@@ -2,14 +2,16 @@ import { Deferred } from "@fuman/utils";
2
2
  import { deserializeError } from "./errors.js";
3
3
  import { serializeResult, deserializeResult } from "./protocol.js";
4
4
  class WorkerInvoker {
5
- constructor(send) {
5
+ constructor(send, workerId) {
6
6
  this.send = send;
7
+ this.workerId = workerId;
7
8
  }
8
9
  _nextId = 0;
9
10
  _pending = /* @__PURE__ */ new Map();
10
11
  _invoke(target, method, args, isVoid, abortSignal) {
11
12
  const id = this._nextId++;
12
13
  this.send({
14
+ _mtcuteWorkerId: this.workerId,
13
15
  type: "invoke",
14
16
  id,
15
17
  target,
@@ -20,6 +22,7 @@ class WorkerInvoker {
20
22
  });
21
23
  abortSignal?.addEventListener("abort", () => {
22
24
  this.send({
25
+ _mtcuteWorkerId: this.workerId,
23
26
  type: "abort",
24
27
  id
25
28
  });
@@ -19,8 +19,9 @@ class TelegramWorkerPort {
19
19
  this.options = options;
20
20
  this.log = new logger.LogManager("worker", options.platform);
21
21
  this.platform = options.platform;
22
+ this.workerId = options.workerId ?? protocol.DEFAULT_WORKER_ID;
22
23
  this._connection = this.connectToWorker(this.options.worker, this._onMessage);
23
- this._invoker = new invoker.WorkerInvoker(this._connection[0]);
24
+ this._invoker = new invoker.WorkerInvoker(this._connection[0], this.workerId);
24
25
  this.storage = new storage.TelegramStorageProxy(this._invoker);
25
26
  this.appConfig = new appConfig.AppConfigManagerProxy(this._invoker);
26
27
  const bind = this._invoker.makeBinder("client");
@@ -82,6 +83,7 @@ class TelegramWorkerPort {
82
83
  recreateDc;
83
84
  _abortController = new AbortController();
84
85
  stopSignal = this._abortController.signal;
86
+ workerId;
85
87
  call(message, params) {
86
88
  if (params?.abortSignal) {
87
89
  const { abortSignal, ...rest } = params;
@@ -94,6 +96,7 @@ class TelegramWorkerPort {
94
96
  onConnectionState = new utils.Emitter();
95
97
  onError = new utils.Emitter();
96
98
  _onMessage = (message) => {
99
+ if (message._mtcuteWorkerId !== this.workerId) return;
97
100
  switch (message.type) {
98
101
  case "log":
99
102
  this.log.handler(message.color, message.level, message.tag, message.fmt, message.args);
@@ -12,6 +12,7 @@ import { AppConfigManagerProxy } from './app-config.js';
12
12
  import { TelegramStorageProxy } from './storage.js';
13
13
  export interface TelegramWorkerPortOptions {
14
14
  worker: SomeWorker;
15
+ workerId?: string;
15
16
  platform: ICorePlatform;
16
17
  }
17
18
  export declare abstract class TelegramWorkerPort<Custom extends WorkerCustomMethods> implements ITelegramClient {
@@ -46,6 +47,7 @@ export declare abstract class TelegramWorkerPort<Custom extends WorkerCustomMeth
46
47
  readonly recreateDc: ITelegramClient['recreateDc'];
47
48
  private _abortController;
48
49
  readonly stopSignal: AbortSignal;
50
+ readonly workerId: string;
49
51
  constructor(options: TelegramWorkerPortOptions);
50
52
  call<T extends tl.RpcMethod>(message: MustEqual<T, tl.RpcMethod>, params?: RpcCallOptions): Promise<tl.RpcCallReturn[T['_']]>;
51
53
  abstract connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void];
@@ -12,6 +12,7 @@ import { AppConfigManagerProxy } from './app-config.js';
12
12
  import { TelegramStorageProxy } from './storage.js';
13
13
  export interface TelegramWorkerPortOptions {
14
14
  worker: SomeWorker;
15
+ workerId?: string;
15
16
  platform: ICorePlatform;
16
17
  }
17
18
  export declare abstract class TelegramWorkerPort<Custom extends WorkerCustomMethods> implements ITelegramClient {
@@ -46,6 +47,7 @@ export declare abstract class TelegramWorkerPort<Custom extends WorkerCustomMeth
46
47
  readonly recreateDc: ITelegramClient['recreateDc'];
47
48
  private _abortController;
48
49
  readonly stopSignal: AbortSignal;
50
+ readonly workerId: string;
49
51
  constructor(options: TelegramWorkerPortOptions);
50
52
  call<T extends tl.RpcMethod>(message: MustEqual<T, tl.RpcMethod>, params?: RpcCallOptions): Promise<tl.RpcCallReturn[T['_']]>;
51
53
  abstract connectToWorker(worker: SomeWorker, handler: ClientMessageHandler): [SendFn, () => void];
@@ -5,15 +5,16 @@ import { PeersIndex } from "../types/peers/peers-index.js";
5
5
  import { AppConfigManagerProxy } from "./app-config.js";
6
6
  import { deserializeError } from "./errors.js";
7
7
  import { WorkerInvoker } from "./invoker.js";
8
- import { deserializeResult } from "./protocol.js";
8
+ import { DEFAULT_WORKER_ID, deserializeResult } from "./protocol.js";
9
9
  import { TelegramStorageProxy } from "./storage.js";
10
10
  class TelegramWorkerPort {
11
11
  constructor(options) {
12
12
  this.options = options;
13
13
  this.log = new LogManager("worker", options.platform);
14
14
  this.platform = options.platform;
15
+ this.workerId = options.workerId ?? DEFAULT_WORKER_ID;
15
16
  this._connection = this.connectToWorker(this.options.worker, this._onMessage);
16
- this._invoker = new WorkerInvoker(this._connection[0]);
17
+ this._invoker = new WorkerInvoker(this._connection[0], this.workerId);
17
18
  this.storage = new TelegramStorageProxy(this._invoker);
18
19
  this.appConfig = new AppConfigManagerProxy(this._invoker);
19
20
  const bind = this._invoker.makeBinder("client");
@@ -75,6 +76,7 @@ class TelegramWorkerPort {
75
76
  recreateDc;
76
77
  _abortController = new AbortController();
77
78
  stopSignal = this._abortController.signal;
79
+ workerId;
78
80
  call(message, params) {
79
81
  if (params?.abortSignal) {
80
82
  const { abortSignal, ...rest } = params;
@@ -87,6 +89,7 @@ class TelegramWorkerPort {
87
89
  onConnectionState = new Emitter();
88
90
  onError = new Emitter();
89
91
  _onMessage = (message) => {
92
+ if (message._mtcuteWorkerId !== this.workerId) return;
90
93
  switch (message.type) {
91
94
  case "log":
92
95
  this.log.handler(message.color, message.level, message.tag, message.fmt, message.args);
@@ -6,6 +6,7 @@ if (typeof globalThis !== "undefined" && !globalThis._MTCUTE_CJS_DEPRECATION_WAR
6
6
  "use strict";
7
7
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
8
8
  const Long = require("long");
9
+ const DEFAULT_WORKER_ID = "default";
9
10
  function serializeResult(result) {
10
11
  if (ArrayBuffer.isView(result)) return result;
11
12
  if (Array.isArray(result)) {
@@ -59,5 +60,6 @@ function deserializeResult(result) {
59
60
  }
60
61
  return result;
61
62
  }
63
+ exports.DEFAULT_WORKER_ID = DEFAULT_WORKER_ID;
62
64
  exports.deserializeResult = deserializeResult;
63
65
  exports.serializeResult = serializeResult;
@@ -3,6 +3,7 @@ import { Worker as NodeWorker } from 'node:worker_threads';
3
3
  import { ConnectionState } from '../client.types.js';
4
4
  import { SerializedError } from './errors.js';
5
5
  export type WorkerInboundMessage = {
6
+ _mtcuteWorkerId: string;
6
7
  type: 'invoke';
7
8
  id: number;
8
9
  target: 'custom' | 'client' | 'storage' | 'storage-self' | 'storage-peers' | 'app-config';
@@ -11,27 +12,34 @@ export type WorkerInboundMessage = {
11
12
  void: boolean;
12
13
  withAbort: boolean;
13
14
  } | {
15
+ _mtcuteWorkerId: string;
14
16
  type: 'abort';
15
17
  id: number;
16
18
  };
17
19
  export type WorkerOutboundMessage = {
20
+ _mtcuteWorkerId: string;
18
21
  type: 'server_update';
19
22
  update: SerializedResult<tl.TypeUpdates>;
20
23
  } | {
24
+ _mtcuteWorkerId: string;
21
25
  type: 'update';
22
26
  update: SerializedResult<tl.TypeUpdate>;
23
27
  users: SerializedResult<Map<number, tl.TypeUser>>;
24
28
  chats: SerializedResult<Map<number, tl.TypeChat>>;
25
29
  hasMin: boolean;
26
30
  } | {
31
+ _mtcuteWorkerId: string;
27
32
  type: 'error';
28
33
  error: SerializedError;
29
34
  } | {
35
+ _mtcuteWorkerId: string;
30
36
  type: 'stop';
31
37
  } | {
38
+ _mtcuteWorkerId: string;
32
39
  type: 'conn_state';
33
40
  state: ConnectionState;
34
41
  } | {
42
+ _mtcuteWorkerId: string;
35
43
  type: 'log';
36
44
  color: number;
37
45
  level: number;
@@ -39,11 +47,13 @@ export type WorkerOutboundMessage = {
39
47
  fmt: string;
40
48
  args: unknown[];
41
49
  } | {
50
+ _mtcuteWorkerId: string;
42
51
  type: 'result';
43
52
  id: number;
44
53
  result?: SerializedResult<unknown>;
45
54
  error?: SerializedError;
46
55
  };
56
+ export declare const DEFAULT_WORKER_ID = "default";
47
57
  export type SomeWorker = NodeWorker | Worker | SharedWorker;
48
58
  export type SendFn = (message: WorkerInboundMessage) => void;
49
59
  export type ClientMessageHandler = (message: WorkerOutboundMessage) => void;
@@ -3,6 +3,7 @@ import { Worker as NodeWorker } from 'node:worker_threads';
3
3
  import { ConnectionState } from '../client.types.js';
4
4
  import { SerializedError } from './errors.js';
5
5
  export type WorkerInboundMessage = {
6
+ _mtcuteWorkerId: string;
6
7
  type: 'invoke';
7
8
  id: number;
8
9
  target: 'custom' | 'client' | 'storage' | 'storage-self' | 'storage-peers' | 'app-config';
@@ -11,27 +12,34 @@ export type WorkerInboundMessage = {
11
12
  void: boolean;
12
13
  withAbort: boolean;
13
14
  } | {
15
+ _mtcuteWorkerId: string;
14
16
  type: 'abort';
15
17
  id: number;
16
18
  };
17
19
  export type WorkerOutboundMessage = {
20
+ _mtcuteWorkerId: string;
18
21
  type: 'server_update';
19
22
  update: SerializedResult<tl.TypeUpdates>;
20
23
  } | {
24
+ _mtcuteWorkerId: string;
21
25
  type: 'update';
22
26
  update: SerializedResult<tl.TypeUpdate>;
23
27
  users: SerializedResult<Map<number, tl.TypeUser>>;
24
28
  chats: SerializedResult<Map<number, tl.TypeChat>>;
25
29
  hasMin: boolean;
26
30
  } | {
31
+ _mtcuteWorkerId: string;
27
32
  type: 'error';
28
33
  error: SerializedError;
29
34
  } | {
35
+ _mtcuteWorkerId: string;
30
36
  type: 'stop';
31
37
  } | {
38
+ _mtcuteWorkerId: string;
32
39
  type: 'conn_state';
33
40
  state: ConnectionState;
34
41
  } | {
42
+ _mtcuteWorkerId: string;
35
43
  type: 'log';
36
44
  color: number;
37
45
  level: number;
@@ -39,11 +47,13 @@ export type WorkerOutboundMessage = {
39
47
  fmt: string;
40
48
  args: unknown[];
41
49
  } | {
50
+ _mtcuteWorkerId: string;
42
51
  type: 'result';
43
52
  id: number;
44
53
  result?: SerializedResult<unknown>;
45
54
  error?: SerializedError;
46
55
  };
56
+ export declare const DEFAULT_WORKER_ID = "default";
47
57
  export type SomeWorker = NodeWorker | Worker | SharedWorker;
48
58
  export type SendFn = (message: WorkerInboundMessage) => void;
49
59
  export type ClientMessageHandler = (message: WorkerOutboundMessage) => void;
@@ -1,4 +1,5 @@
1
1
  import Long from "long";
2
+ const DEFAULT_WORKER_ID = "default";
2
3
  function serializeResult(result) {
3
4
  if (ArrayBuffer.isView(result)) return result;
4
5
  if (Array.isArray(result)) {
@@ -53,6 +54,7 @@ function deserializeResult(result) {
53
54
  return result;
54
55
  }
55
56
  export {
57
+ DEFAULT_WORKER_ID,
56
58
  deserializeResult,
57
59
  serializeResult
58
60
  };
@@ -10,7 +10,9 @@ const protocol = require("./protocol.cjs");
10
10
  class TelegramWorker {
11
11
  constructor(params) {
12
12
  this.params = params;
13
- this.broadcast = this.registerWorker((message, respond) => {
13
+ this.workerId = params.workerId ?? protocol.DEFAULT_WORKER_ID;
14
+ const [broadcast, cleanup] = this.registerWorker((message, respond) => {
15
+ if (message._mtcuteWorkerId !== this.workerId) return;
14
16
  switch (message.type) {
15
17
  case "invoke":
16
18
  this.onInvoke(message, respond);
@@ -24,9 +26,12 @@ class TelegramWorker {
24
26
  }
25
27
  }
26
28
  });
29
+ this.broadcast = broadcast;
30
+ this._cleanup = cleanup;
27
31
  const client = params.client;
28
32
  this.client = client;
29
33
  client.log.mgr.handler = (color, level, tag, fmt, args) => this.broadcast({
34
+ _mtcuteWorkerId: this.workerId,
30
35
  type: "log",
31
36
  color,
32
37
  level,
@@ -36,20 +41,26 @@ class TelegramWorker {
36
41
  });
37
42
  client.onError.add(
38
43
  (err) => this.broadcast({
44
+ _mtcuteWorkerId: this.workerId,
39
45
  type: "error",
40
46
  error: errors.serializeError(err)
41
47
  })
42
48
  );
43
49
  client.onConnectionState.add(
44
50
  (state) => this.broadcast({
51
+ _mtcuteWorkerId: this.workerId,
45
52
  type: "conn_state",
46
53
  state
47
54
  })
48
55
  );
49
- client.stopSignal.addEventListener("abort", () => this.broadcast({ type: "stop" }));
56
+ client.stopSignal.addEventListener("abort", () => this.broadcast({
57
+ _mtcuteWorkerId: this.workerId,
58
+ type: "stop"
59
+ }));
50
60
  if (client.updates) {
51
61
  client.onRawUpdate.add(
52
62
  ({ update, peers }) => this.broadcast({
63
+ _mtcuteWorkerId: this.workerId,
53
64
  type: "update",
54
65
  update: protocol.serializeResult(update),
55
66
  users: protocol.serializeResult(peers.users),
@@ -60,6 +71,7 @@ class TelegramWorker {
60
71
  } else {
61
72
  client.onServerUpdate.add(
62
73
  (update) => this.broadcast({
74
+ _mtcuteWorkerId: this.workerId,
63
75
  type: "server_update",
64
76
  update: protocol.serializeResult(update)
65
77
  })
@@ -68,7 +80,9 @@ class TelegramWorker {
68
80
  }
69
81
  client;
70
82
  broadcast;
83
+ _cleanup;
71
84
  pendingAborts = /* @__PURE__ */ new Map();
85
+ workerId;
72
86
  onInvoke(msg, respond) {
73
87
  let target;
74
88
  switch (msg.target) {
@@ -92,6 +106,7 @@ class TelegramWorker {
92
106
  break;
93
107
  default: {
94
108
  respond({
109
+ _mtcuteWorkerId: this.workerId,
95
110
  type: "result",
96
111
  id: msg.id,
97
112
  error: new Error(`Unknown target ${msg.target}`)
@@ -102,6 +117,7 @@ class TelegramWorker {
102
117
  const method = target[msg.method];
103
118
  if (!method) {
104
119
  respond({
120
+ _mtcuteWorkerId: this.workerId,
105
121
  type: "result",
106
122
  id: msg.id,
107
123
  error: new Error(`Method ${msg.method} not found on ${msg.target}`)
@@ -128,6 +144,7 @@ class TelegramWorker {
128
144
  }
129
145
  if (msg.void) return;
130
146
  respond({
147
+ _mtcuteWorkerId: this.workerId,
131
148
  type: "result",
132
149
  id: msg.id,
133
150
  result: protocol.serializeResult(res)
@@ -137,11 +154,15 @@ class TelegramWorker {
137
154
  this.pendingAborts.delete(msg.id);
138
155
  }
139
156
  respond({
157
+ _mtcuteWorkerId: this.workerId,
140
158
  type: "result",
141
159
  id: msg.id,
142
160
  error: errors.serializeError(err)
143
161
  });
144
162
  });
145
163
  }
164
+ destroy() {
165
+ this._cleanup?.();
166
+ }
146
167
  }
147
168
  exports.TelegramWorker = TelegramWorker;
@@ -2,14 +2,18 @@ import { BaseTelegramClient } from '../base.js';
2
2
  import { RespondFn, WorkerCustomMethods, WorkerMessageHandler } from './protocol.js';
3
3
  export interface TelegramWorkerOptions<T extends WorkerCustomMethods> {
4
4
  client: BaseTelegramClient;
5
+ workerId?: string;
5
6
  customMethods?: T;
6
7
  }
7
8
  export declare abstract class TelegramWorker<T extends WorkerCustomMethods> {
8
9
  readonly params: TelegramWorkerOptions<T>;
9
10
  readonly client: BaseTelegramClient;
10
11
  readonly broadcast: RespondFn;
11
- abstract registerWorker(handler: WorkerMessageHandler): RespondFn;
12
+ private _cleanup;
13
+ abstract registerWorker(handler: WorkerMessageHandler): [RespondFn, VoidFunction];
12
14
  readonly pendingAborts: Map<number, AbortController>;
15
+ readonly workerId: string;
13
16
  constructor(params: TelegramWorkerOptions<T>);
14
17
  private onInvoke;
18
+ destroy(): void;
15
19
  }
@@ -2,14 +2,18 @@ import { BaseTelegramClient } from '../base.js';
2
2
  import { RespondFn, WorkerCustomMethods, WorkerMessageHandler } from './protocol.js';
3
3
  export interface TelegramWorkerOptions<T extends WorkerCustomMethods> {
4
4
  client: BaseTelegramClient;
5
+ workerId?: string;
5
6
  customMethods?: T;
6
7
  }
7
8
  export declare abstract class TelegramWorker<T extends WorkerCustomMethods> {
8
9
  readonly params: TelegramWorkerOptions<T>;
9
10
  readonly client: BaseTelegramClient;
10
11
  readonly broadcast: RespondFn;
11
- abstract registerWorker(handler: WorkerMessageHandler): RespondFn;
12
+ private _cleanup;
13
+ abstract registerWorker(handler: WorkerMessageHandler): [RespondFn, VoidFunction];
12
14
  readonly pendingAborts: Map<number, AbortController>;
15
+ readonly workerId: string;
13
16
  constructor(params: TelegramWorkerOptions<T>);
14
17
  private onInvoke;
18
+ destroy(): void;
15
19
  }
@@ -1,9 +1,11 @@
1
1
  import { serializeError } from "./errors.js";
2
- import { serializeResult, deserializeResult } from "./protocol.js";
2
+ import { DEFAULT_WORKER_ID, serializeResult, deserializeResult } from "./protocol.js";
3
3
  class TelegramWorker {
4
4
  constructor(params) {
5
5
  this.params = params;
6
- this.broadcast = this.registerWorker((message, respond) => {
6
+ this.workerId = params.workerId ?? DEFAULT_WORKER_ID;
7
+ const [broadcast, cleanup] = this.registerWorker((message, respond) => {
8
+ if (message._mtcuteWorkerId !== this.workerId) return;
7
9
  switch (message.type) {
8
10
  case "invoke":
9
11
  this.onInvoke(message, respond);
@@ -17,9 +19,12 @@ class TelegramWorker {
17
19
  }
18
20
  }
19
21
  });
22
+ this.broadcast = broadcast;
23
+ this._cleanup = cleanup;
20
24
  const client = params.client;
21
25
  this.client = client;
22
26
  client.log.mgr.handler = (color, level, tag, fmt, args) => this.broadcast({
27
+ _mtcuteWorkerId: this.workerId,
23
28
  type: "log",
24
29
  color,
25
30
  level,
@@ -29,20 +34,26 @@ class TelegramWorker {
29
34
  });
30
35
  client.onError.add(
31
36
  (err) => this.broadcast({
37
+ _mtcuteWorkerId: this.workerId,
32
38
  type: "error",
33
39
  error: serializeError(err)
34
40
  })
35
41
  );
36
42
  client.onConnectionState.add(
37
43
  (state) => this.broadcast({
44
+ _mtcuteWorkerId: this.workerId,
38
45
  type: "conn_state",
39
46
  state
40
47
  })
41
48
  );
42
- client.stopSignal.addEventListener("abort", () => this.broadcast({ type: "stop" }));
49
+ client.stopSignal.addEventListener("abort", () => this.broadcast({
50
+ _mtcuteWorkerId: this.workerId,
51
+ type: "stop"
52
+ }));
43
53
  if (client.updates) {
44
54
  client.onRawUpdate.add(
45
55
  ({ update, peers }) => this.broadcast({
56
+ _mtcuteWorkerId: this.workerId,
46
57
  type: "update",
47
58
  update: serializeResult(update),
48
59
  users: serializeResult(peers.users),
@@ -53,6 +64,7 @@ class TelegramWorker {
53
64
  } else {
54
65
  client.onServerUpdate.add(
55
66
  (update) => this.broadcast({
67
+ _mtcuteWorkerId: this.workerId,
56
68
  type: "server_update",
57
69
  update: serializeResult(update)
58
70
  })
@@ -61,7 +73,9 @@ class TelegramWorker {
61
73
  }
62
74
  client;
63
75
  broadcast;
76
+ _cleanup;
64
77
  pendingAborts = /* @__PURE__ */ new Map();
78
+ workerId;
65
79
  onInvoke(msg, respond) {
66
80
  let target;
67
81
  switch (msg.target) {
@@ -85,6 +99,7 @@ class TelegramWorker {
85
99
  break;
86
100
  default: {
87
101
  respond({
102
+ _mtcuteWorkerId: this.workerId,
88
103
  type: "result",
89
104
  id: msg.id,
90
105
  error: new Error(`Unknown target ${msg.target}`)
@@ -95,6 +110,7 @@ class TelegramWorker {
95
110
  const method = target[msg.method];
96
111
  if (!method) {
97
112
  respond({
113
+ _mtcuteWorkerId: this.workerId,
98
114
  type: "result",
99
115
  id: msg.id,
100
116
  error: new Error(`Method ${msg.method} not found on ${msg.target}`)
@@ -121,6 +137,7 @@ class TelegramWorker {
121
137
  }
122
138
  if (msg.void) return;
123
139
  respond({
140
+ _mtcuteWorkerId: this.workerId,
124
141
  type: "result",
125
142
  id: msg.id,
126
143
  result: serializeResult(res)
@@ -130,12 +147,16 @@ class TelegramWorker {
130
147
  this.pendingAborts.delete(msg.id);
131
148
  }
132
149
  respond({
150
+ _mtcuteWorkerId: this.workerId,
133
151
  type: "result",
134
152
  id: msg.id,
135
153
  error: serializeError(err)
136
154
  });
137
155
  });
138
156
  }
157
+ destroy() {
158
+ this._cleanup?.();
159
+ }
139
160
  }
140
161
  export {
141
162
  TelegramWorker
@@ -29,7 +29,7 @@ class MtprotoSession {
29
29
  _authKey;
30
30
  _authKeyTemp;
31
31
  _authKeyTempSecondary;
32
- _timeOffset = performance.timeOrigin;
32
+ _timeOffset = Math.floor(performance.timeOrigin / 1e3);
33
33
  _lastMessageId = Long.ZERO;
34
34
  _seqNo = 0;
35
35
  /// state ///
@@ -22,7 +22,7 @@ class MtprotoSession {
22
22
  _authKey;
23
23
  _authKeyTemp;
24
24
  _authKeyTempSecondary;
25
- _timeOffset = performance.timeOrigin;
25
+ _timeOffset = Math.floor(performance.timeOrigin / 1e3);
26
26
  _lastMessageId = Long.ZERO;
27
27
  _seqNo = 0;
28
28
  /// state ///
@@ -32,25 +32,27 @@ class DcConnectionManager {
32
32
  this.isPrimary = isPrimary;
33
33
  this._log = this.manager._log.create("dc-manager");
34
34
  this._log.prefix = `[DC ${dcId}] `;
35
+ const managerParams = this.manager.params;
35
36
  const baseConnectionParams = () => ({
36
- crypto: this.manager.params.crypto,
37
+ crypto: managerParams.crypto,
37
38
  initConnection: this.manager._initConnectionParams,
38
39
  transport: this.manager._transport,
39
40
  dc: this._dcs.media,
40
- testMode: this.manager.params.testMode,
41
+ testMode: managerParams.testMode,
41
42
  reconnectionStrategy: this.manager._reconnectionStrategy,
42
- layer: this.manager.params.layer,
43
- disableUpdates: this.manager.params.disableUpdates,
44
- readerMap: this.manager.params.readerMap,
45
- writerMap: this.manager.params.writerMap,
46
- usePfs: this.manager.params.usePfs,
43
+ layer: managerParams.layer,
44
+ disableUpdates: managerParams.disableUpdates,
45
+ readerMap: managerParams.readerMap,
46
+ writerMap: managerParams.writerMap,
47
+ usePfs: managerParams.usePfs,
47
48
  isMainConnection: false,
48
49
  isMainDcConnection: this.isPrimary,
49
- inactivityTimeout: this.manager.params.inactivityTimeout ?? 6e4,
50
- enableErrorReporting: this.manager.params.enableErrorReporting,
51
- salts: this._salts,
52
- platform: this.manager.params.platform,
53
- pingInterval: this.manager.params.pingInterval ?? 6e4
50
+ inactivityTimeout: managerParams.inactivityTimeout ?? 6e4,
51
+ enableErrorReporting: managerParams.enableErrorReporting,
52
+ // NB: we need a separate salts manager for each pfs connection, because server salts are auth_key-bound
53
+ salts: managerParams.usePfs ? new serverSalt.ServerSaltManager() : this._salts,
54
+ platform: managerParams.platform,
55
+ pingInterval: managerParams.pingInterval ?? 6e4
54
56
  });
55
57
  const mainParams = baseConnectionParams();
56
58
  mainParams.isMainConnection = true;
@@ -70,19 +72,19 @@ class DcConnectionManager {
70
72
  this.main = new multiSessionConnection.MultiSessionConnection(mainParams, mainCount, this._log, "MAIN");
71
73
  this.upload = new multiSessionConnection.MultiSessionConnection(
72
74
  baseConnectionParams(),
73
- this.manager._connectionCount("upload", this.dcId, this.manager.params.isPremium),
75
+ this.manager._connectionCount("upload", this.dcId, managerParams.isPremium),
74
76
  this._log,
75
77
  "UPLOAD"
76
78
  );
77
79
  this.download = new multiSessionConnection.MultiSessionConnection(
78
80
  baseConnectionParams(),
79
- this.manager._connectionCount("download", this.dcId, this.manager.params.isPremium),
81
+ this.manager._connectionCount("download", this.dcId, managerParams.isPremium),
80
82
  this._log,
81
83
  "DOWNLOAD"
82
84
  );
83
85
  this.downloadSmall = new multiSessionConnection.MultiSessionConnection(
84
86
  baseConnectionParams(),
85
- this.manager._connectionCount("downloadSmall", this.dcId, this.manager.params.isPremium),
87
+ this.manager._connectionCount("downloadSmall", this.dcId, managerParams.isPremium),
86
88
  this._log,
87
89
  "DOWNLOAD_SMALL"
88
90
  );
@@ -135,6 +137,9 @@ class DcConnectionManager {
135
137
  });
136
138
  });
137
139
  connection.onFutureSalts.add((salts) => {
140
+ if (this.manager.params.usePfs && kind !== "main") {
141
+ return;
142
+ }
138
143
  Promise.resolve(this.manager._storage.salts.store(this.dcId, salts)).catch(
139
144
  (e) => this.manager.params.emitError(e)
140
145
  );
@@ -220,7 +225,7 @@ class NetworkManager {
220
225
  _: "initConnection",
221
226
  deviceModel,
222
227
  systemVersion: "1.0",
223
- appVersion: "0.25.0",
228
+ appVersion: "0.25.2",
224
229
  systemLangCode: "en",
225
230
  langPack: "",
226
231
  // "langPacks are for official apps only"
@@ -296,9 +301,7 @@ class NetworkManager {
296
301
  async _getOtherDc(dcId) {
297
302
  if (!this._dcConnections.has(dcId)) {
298
303
  if (this._dcCreationPromise.has(dcId)) {
299
- this._log.debug("waiting for dc %d to be created", dcId);
300
304
  await this._dcCreationPromise.get(dcId);
301
- this._log.debug("dc %d was created", dcId);
302
305
  return utils.asNonNull(this._dcConnections.get(dcId));
303
306
  }
304
307
  const promise = new utils.Deferred();
@@ -313,7 +316,9 @@ class NetworkManager {
313
316
  this._dcConnections.set(dcId, dc);
314
317
  promise.resolve();
315
318
  this._dcCreationPromise.delete(dcId);
319
+ this._log.debug("dc %d was created", dcId);
316
320
  } catch (e) {
321
+ this._log.debug("dc %d was not created: %e", dcId, e);
317
322
  promise.reject(e);
318
323
  this._dcCreationPromise.delete(dcId);
319
324
  return this._getOtherDc(dcId);
@@ -25,25 +25,27 @@ class DcConnectionManager {
25
25
  this.isPrimary = isPrimary;
26
26
  this._log = this.manager._log.create("dc-manager");
27
27
  this._log.prefix = `[DC ${dcId}] `;
28
+ const managerParams = this.manager.params;
28
29
  const baseConnectionParams = () => ({
29
- crypto: this.manager.params.crypto,
30
+ crypto: managerParams.crypto,
30
31
  initConnection: this.manager._initConnectionParams,
31
32
  transport: this.manager._transport,
32
33
  dc: this._dcs.media,
33
- testMode: this.manager.params.testMode,
34
+ testMode: managerParams.testMode,
34
35
  reconnectionStrategy: this.manager._reconnectionStrategy,
35
- layer: this.manager.params.layer,
36
- disableUpdates: this.manager.params.disableUpdates,
37
- readerMap: this.manager.params.readerMap,
38
- writerMap: this.manager.params.writerMap,
39
- usePfs: this.manager.params.usePfs,
36
+ layer: managerParams.layer,
37
+ disableUpdates: managerParams.disableUpdates,
38
+ readerMap: managerParams.readerMap,
39
+ writerMap: managerParams.writerMap,
40
+ usePfs: managerParams.usePfs,
40
41
  isMainConnection: false,
41
42
  isMainDcConnection: this.isPrimary,
42
- inactivityTimeout: this.manager.params.inactivityTimeout ?? 6e4,
43
- enableErrorReporting: this.manager.params.enableErrorReporting,
44
- salts: this._salts,
45
- platform: this.manager.params.platform,
46
- pingInterval: this.manager.params.pingInterval ?? 6e4
43
+ inactivityTimeout: managerParams.inactivityTimeout ?? 6e4,
44
+ enableErrorReporting: managerParams.enableErrorReporting,
45
+ // NB: we need a separate salts manager for each pfs connection, because server salts are auth_key-bound
46
+ salts: managerParams.usePfs ? new ServerSaltManager() : this._salts,
47
+ platform: managerParams.platform,
48
+ pingInterval: managerParams.pingInterval ?? 6e4
47
49
  });
48
50
  const mainParams = baseConnectionParams();
49
51
  mainParams.isMainConnection = true;
@@ -63,19 +65,19 @@ class DcConnectionManager {
63
65
  this.main = new MultiSessionConnection(mainParams, mainCount, this._log, "MAIN");
64
66
  this.upload = new MultiSessionConnection(
65
67
  baseConnectionParams(),
66
- this.manager._connectionCount("upload", this.dcId, this.manager.params.isPremium),
68
+ this.manager._connectionCount("upload", this.dcId, managerParams.isPremium),
67
69
  this._log,
68
70
  "UPLOAD"
69
71
  );
70
72
  this.download = new MultiSessionConnection(
71
73
  baseConnectionParams(),
72
- this.manager._connectionCount("download", this.dcId, this.manager.params.isPremium),
74
+ this.manager._connectionCount("download", this.dcId, managerParams.isPremium),
73
75
  this._log,
74
76
  "DOWNLOAD"
75
77
  );
76
78
  this.downloadSmall = new MultiSessionConnection(
77
79
  baseConnectionParams(),
78
- this.manager._connectionCount("downloadSmall", this.dcId, this.manager.params.isPremium),
80
+ this.manager._connectionCount("downloadSmall", this.dcId, managerParams.isPremium),
79
81
  this._log,
80
82
  "DOWNLOAD_SMALL"
81
83
  );
@@ -128,6 +130,9 @@ class DcConnectionManager {
128
130
  });
129
131
  });
130
132
  connection.onFutureSalts.add((salts) => {
133
+ if (this.manager.params.usePfs && kind !== "main") {
134
+ return;
135
+ }
131
136
  Promise.resolve(this.manager._storage.salts.store(this.dcId, salts)).catch(
132
137
  (e) => this.manager.params.emitError(e)
133
138
  );
@@ -213,7 +218,7 @@ class NetworkManager {
213
218
  _: "initConnection",
214
219
  deviceModel,
215
220
  systemVersion: "1.0",
216
- appVersion: "0.25.0",
221
+ appVersion: "0.25.2",
217
222
  systemLangCode: "en",
218
223
  langPack: "",
219
224
  // "langPacks are for official apps only"
@@ -289,9 +294,7 @@ class NetworkManager {
289
294
  async _getOtherDc(dcId) {
290
295
  if (!this._dcConnections.has(dcId)) {
291
296
  if (this._dcCreationPromise.has(dcId)) {
292
- this._log.debug("waiting for dc %d to be created", dcId);
293
297
  await this._dcCreationPromise.get(dcId);
294
- this._log.debug("dc %d was created", dcId);
295
298
  return asNonNull(this._dcConnections.get(dcId));
296
299
  }
297
300
  const promise = new Deferred();
@@ -306,7 +309,9 @@ class NetworkManager {
306
309
  this._dcConnections.set(dcId, dc);
307
310
  promise.resolve();
308
311
  this._dcCreationPromise.delete(dcId);
312
+ this._log.debug("dc %d was created", dcId);
309
313
  } catch (e) {
314
+ this._log.debug("dc %d was not created: %e", dcId, e);
310
315
  promise.reject(e);
311
316
  this._dcCreationPromise.delete(dcId);
312
317
  return this._getOtherDc(dcId);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mtcute/core",
3
3
  "type": "module",
4
- "version": "0.25.0",
4
+ "version": "0.25.2",
5
5
  "description": "Type-safe library for MTProto (Telegram API)",
6
6
  "license": "MIT",
7
7
  "scripts": {},
package/worker.cjs CHANGED
@@ -9,6 +9,7 @@ const port = require("./highlevel/worker/port.cjs");
9
9
  const protocol = require("./highlevel/worker/protocol.cjs");
10
10
  const worker = require("./highlevel/worker/worker.cjs");
11
11
  exports.TelegramWorkerPort = port.TelegramWorkerPort;
12
+ exports.DEFAULT_WORKER_ID = protocol.DEFAULT_WORKER_ID;
12
13
  exports.deserializeResult = protocol.deserializeResult;
13
14
  exports.serializeResult = protocol.serializeResult;
14
15
  exports.TelegramWorker = worker.TelegramWorker;
package/worker.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { TelegramWorkerPort } from "./highlevel/worker/port.js";
2
- import { deserializeResult, serializeResult } from "./highlevel/worker/protocol.js";
2
+ import { DEFAULT_WORKER_ID, deserializeResult, serializeResult } from "./highlevel/worker/protocol.js";
3
3
  import { TelegramWorker } from "./highlevel/worker/worker.js";
4
4
  export {
5
+ DEFAULT_WORKER_ID,
5
6
  TelegramWorker,
6
7
  TelegramWorkerPort,
7
8
  deserializeResult,