@mtcute/web 0.28.2 → 0.29.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 CHANGED
@@ -31,16 +31,16 @@ You can also use this package with web workers to offload most of the heavy lift
31
31
  // worker.ts
32
32
  import { BaseTelegramClient, TelegramWorker } from '@mtcute/web'
33
33
 
34
- // main.ts
35
- import { TelegramClient, TelegramWorkerPort } from '@mtcute/web'
36
-
37
34
  const client = new BaseTelegramClient({
38
35
  apiId: 12345,
39
36
  apiHash: 'abcdef',
40
37
  storage: 'my-account'
41
38
  })
42
39
 
43
- new TelegramWorker({ client })
40
+ new TelegramWorker({ client }).mount()
41
+
42
+ // main.ts
43
+ import { TelegramClient, TelegramWorkerPort } from '@mtcute/web'
44
44
 
45
45
  const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' }) // or SharedWorker
46
46
  const port = new TelegramWorkerPort({ worker })
@@ -49,3 +49,5 @@ const tg = new TelegramClient({ client: port })
49
49
  const self = await tg.start()
50
50
  console.log(`✨ logged in as ${user.displayName}`)
51
51
  ```
52
+
53
+ `port.destroy()` only releases that port. Use `port.unsafeForceDestroy()` if you need to tear down the shared worker-side client.
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@mtcute/web",
3
3
  "type": "module",
4
- "version": "0.28.2",
4
+ "version": "0.29.0",
5
5
  "description": "Meta-package for the web platform",
6
6
  "license": "MIT",
7
7
  "dependencies": {
8
- "@mtcute/core": "^0.28.2",
9
- "@mtcute/wasm": "^0.27.8",
10
- "@fuman/net": "0.0.19"
8
+ "@mtcute/core": "^0.29.0",
9
+ "@mtcute/wasm": "^0.29.0",
10
+ "@fuman/net": "0.0.19",
11
+ "@fuman/utils": "0.0.19"
11
12
  },
12
13
  "exports": {
13
14
  ".": {
package/worker.cjs CHANGED
@@ -5,57 +5,86 @@ if (typeof globalThis !== "undefined" && !globalThis._MTCUTE_CJS_DEPRECATION_WAR
5
5
  }
6
6
  "use strict";
7
7
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
8
+ const utils = require("@fuman/utils");
8
9
  const worker_js = require("@mtcute/core/worker.js");
9
10
  const platform$1 = require("./platform.cjs");
10
11
  let _broadcast;
11
12
  const _sharedHandlers = /* @__PURE__ */ new Set();
13
+ function isWorkerInboundMessage(message) {
14
+ if (!message || typeof message !== "object") return false;
15
+ const data = message;
16
+ if (typeof data._mtcuteWorkerId !== "string") return false;
17
+ if (typeof data.connectionId !== "string") return false;
18
+ return true;
19
+ }
20
+ function setupSharedWorker() {
21
+ if (_broadcast) return _broadcast;
22
+ utils.unsafeCastType(self);
23
+ const ports = /* @__PURE__ */ new Map();
24
+ const knownConnectionIds = /* @__PURE__ */ new WeakMap();
25
+ const addConnection = (port, connectionId) => {
26
+ ports.set(connectionId, port);
27
+ const known = knownConnectionIds.get(port);
28
+ if (known) {
29
+ known.add(connectionId);
30
+ return;
31
+ }
32
+ knownConnectionIds.set(port, /* @__PURE__ */ new Set([connectionId]));
33
+ };
34
+ const cleanupConnection = (port, connectionId) => {
35
+ ports.delete(connectionId);
36
+ const known = knownConnectionIds.get(port);
37
+ if (!known) return;
38
+ known.delete(connectionId);
39
+ if (!known.size) {
40
+ knownConnectionIds.delete(port);
41
+ }
42
+ };
43
+ const broadcast = (message) => {
44
+ if ("connectionId" in message) {
45
+ const port = ports.get(message.connectionId);
46
+ if (!port) return;
47
+ port.postMessage(message);
48
+ if (message.type === "connection_expired") {
49
+ cleanupConnection(port, message.connectionId);
50
+ }
51
+ return;
52
+ }
53
+ for (const port of ports.values()) {
54
+ port.postMessage(message);
55
+ }
56
+ };
57
+ self.onconnect = (event) => {
58
+ const port = event.ports[0];
59
+ const respond = (message) => {
60
+ port.postMessage(message);
61
+ if ("connectionId" in message && message.type === "connection_expired") {
62
+ cleanupConnection(port, message.connectionId);
63
+ }
64
+ };
65
+ port.addEventListener("message", (message) => {
66
+ const data = message.data;
67
+ if (!isWorkerInboundMessage(data)) return;
68
+ if (data.type === "connect") {
69
+ addConnection(port, data.connectionId);
70
+ } else if (data.type === "release") {
71
+ cleanupConnection(port, data.connectionId);
72
+ }
73
+ for (const handler of _sharedHandlers) {
74
+ handler(data, respond);
75
+ }
76
+ });
77
+ port.start();
78
+ };
79
+ _broadcast = broadcast;
80
+ return broadcast;
81
+ }
12
82
  class TelegramWorker extends worker_js.TelegramWorker {
13
83
  registerWorker(handler) {
14
84
  if (typeof SharedWorkerGlobalScope !== "undefined" && self instanceof SharedWorkerGlobalScope) {
15
- if (!_broadcast) {
16
- const connections = [];
17
- const broadcast = (message) => {
18
- for (const port of connections) {
19
- port.postMessage(message);
20
- }
21
- };
22
- self.onconnect = (event) => {
23
- const port = event.ports[0];
24
- connections.push(port);
25
- const respond = port.postMessage.bind(port);
26
- const onClose = () => {
27
- port.close();
28
- const idx = connections.indexOf(port);
29
- if (idx >= 0) {
30
- connections.splice(connections.indexOf(port), 1);
31
- }
32
- };
33
- const onTimeout = () => {
34
- console.warn("some connection timed out!");
35
- respond({ __type__: "timeout" });
36
- onClose();
37
- };
38
- let timeout = setTimeout(onTimeout, 6e4);
39
- port.addEventListener("message", (message) => {
40
- if (message.data.__type__ === "close") {
41
- onClose();
42
- return;
43
- }
44
- if (message.data.__type__ === "ping") {
45
- clearTimeout(timeout);
46
- timeout = setTimeout(onTimeout, 6e4);
47
- return;
48
- }
49
- for (const handler2 of _sharedHandlers) {
50
- handler2(message.data, respond);
51
- }
52
- });
53
- port.start();
54
- };
55
- _broadcast = broadcast;
56
- }
85
+ const broadcast = setupSharedWorker();
57
86
  _sharedHandlers.add(handler);
58
- return [_broadcast, () => _sharedHandlers.delete(handler)];
87
+ return [broadcast, () => _sharedHandlers.delete(handler)];
59
88
  }
60
89
  if (typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope) {
61
90
  const respond = self.postMessage.bind(self);
@@ -76,12 +105,11 @@ class TelegramWorkerPort extends worker_js.TelegramWorkerPort {
76
105
  });
77
106
  }
78
107
  connectToWorker(worker, handler) {
79
- if (worker instanceof Worker) {
108
+ if (worker instanceof Worker || worker instanceof MessagePort) {
80
109
  const send = worker.postMessage.bind(worker);
81
- const messageHandler = (ev) => {
82
- handler(ev.data);
83
- };
110
+ const messageHandler = (ev) => handler(ev.data);
84
111
  worker.addEventListener("message", messageHandler);
112
+ if (worker instanceof MessagePort) worker.start();
85
113
  return [
86
114
  send,
87
115
  () => {
@@ -91,27 +119,14 @@ class TelegramWorkerPort extends worker_js.TelegramWorkerPort {
91
119
  }
92
120
  if (worker instanceof SharedWorker) {
93
121
  const send = worker.port.postMessage.bind(worker.port);
94
- const pingInterval = setInterval(() => {
95
- worker.port.postMessage({ __type__: "ping" });
96
- }, 1e4);
97
122
  const messageHandler = (ev) => {
98
- if (ev.data.__type__ === "timeout") {
99
- location.reload();
100
- return;
101
- }
102
123
  handler(ev.data);
103
124
  };
104
125
  worker.port.addEventListener("message", messageHandler);
105
126
  worker.port.start();
106
- let cancelBeforeExit;
107
127
  const close = () => {
108
- clearInterval(pingInterval);
109
- worker.port.postMessage({ __type__: "close" });
110
128
  worker.port.removeEventListener("message", messageHandler);
111
- worker.port.close();
112
- cancelBeforeExit();
113
129
  };
114
- cancelBeforeExit = platform.beforeExit(close);
115
130
  return [send, close];
116
131
  }
117
132
  throw new Error("Only workers and shared workers are supported");
package/worker.js CHANGED
@@ -1,54 +1,83 @@
1
+ import { unsafeCastType } from "@fuman/utils";
1
2
  import { TelegramWorker as TelegramWorker$1, TelegramWorkerPort as TelegramWorkerPort$1 } from "@mtcute/core/worker.js";
2
3
  import { WebPlatform } from "./platform.js";
3
4
  let _broadcast;
4
5
  const _sharedHandlers = /* @__PURE__ */ new Set();
6
+ function isWorkerInboundMessage(message) {
7
+ if (!message || typeof message !== "object") return false;
8
+ const data = message;
9
+ if (typeof data._mtcuteWorkerId !== "string") return false;
10
+ if (typeof data.connectionId !== "string") return false;
11
+ return true;
12
+ }
13
+ function setupSharedWorker() {
14
+ if (_broadcast) return _broadcast;
15
+ unsafeCastType(self);
16
+ const ports = /* @__PURE__ */ new Map();
17
+ const knownConnectionIds = /* @__PURE__ */ new WeakMap();
18
+ const addConnection = (port, connectionId) => {
19
+ ports.set(connectionId, port);
20
+ const known = knownConnectionIds.get(port);
21
+ if (known) {
22
+ known.add(connectionId);
23
+ return;
24
+ }
25
+ knownConnectionIds.set(port, /* @__PURE__ */ new Set([connectionId]));
26
+ };
27
+ const cleanupConnection = (port, connectionId) => {
28
+ ports.delete(connectionId);
29
+ const known = knownConnectionIds.get(port);
30
+ if (!known) return;
31
+ known.delete(connectionId);
32
+ if (!known.size) {
33
+ knownConnectionIds.delete(port);
34
+ }
35
+ };
36
+ const broadcast = (message) => {
37
+ if ("connectionId" in message) {
38
+ const port = ports.get(message.connectionId);
39
+ if (!port) return;
40
+ port.postMessage(message);
41
+ if (message.type === "connection_expired") {
42
+ cleanupConnection(port, message.connectionId);
43
+ }
44
+ return;
45
+ }
46
+ for (const port of ports.values()) {
47
+ port.postMessage(message);
48
+ }
49
+ };
50
+ self.onconnect = (event) => {
51
+ const port = event.ports[0];
52
+ const respond = (message) => {
53
+ port.postMessage(message);
54
+ if ("connectionId" in message && message.type === "connection_expired") {
55
+ cleanupConnection(port, message.connectionId);
56
+ }
57
+ };
58
+ port.addEventListener("message", (message) => {
59
+ const data = message.data;
60
+ if (!isWorkerInboundMessage(data)) return;
61
+ if (data.type === "connect") {
62
+ addConnection(port, data.connectionId);
63
+ } else if (data.type === "release") {
64
+ cleanupConnection(port, data.connectionId);
65
+ }
66
+ for (const handler of _sharedHandlers) {
67
+ handler(data, respond);
68
+ }
69
+ });
70
+ port.start();
71
+ };
72
+ _broadcast = broadcast;
73
+ return broadcast;
74
+ }
5
75
  class TelegramWorker extends TelegramWorker$1 {
6
76
  registerWorker(handler) {
7
77
  if (typeof SharedWorkerGlobalScope !== "undefined" && self instanceof SharedWorkerGlobalScope) {
8
- if (!_broadcast) {
9
- const connections = [];
10
- const broadcast = (message) => {
11
- for (const port of connections) {
12
- port.postMessage(message);
13
- }
14
- };
15
- self.onconnect = (event) => {
16
- const port = event.ports[0];
17
- connections.push(port);
18
- const respond = port.postMessage.bind(port);
19
- const onClose = () => {
20
- port.close();
21
- const idx = connections.indexOf(port);
22
- if (idx >= 0) {
23
- connections.splice(connections.indexOf(port), 1);
24
- }
25
- };
26
- const onTimeout = () => {
27
- console.warn("some connection timed out!");
28
- respond({ __type__: "timeout" });
29
- onClose();
30
- };
31
- let timeout = setTimeout(onTimeout, 6e4);
32
- port.addEventListener("message", (message) => {
33
- if (message.data.__type__ === "close") {
34
- onClose();
35
- return;
36
- }
37
- if (message.data.__type__ === "ping") {
38
- clearTimeout(timeout);
39
- timeout = setTimeout(onTimeout, 6e4);
40
- return;
41
- }
42
- for (const handler2 of _sharedHandlers) {
43
- handler2(message.data, respond);
44
- }
45
- });
46
- port.start();
47
- };
48
- _broadcast = broadcast;
49
- }
78
+ const broadcast = setupSharedWorker();
50
79
  _sharedHandlers.add(handler);
51
- return [_broadcast, () => _sharedHandlers.delete(handler)];
80
+ return [broadcast, () => _sharedHandlers.delete(handler)];
52
81
  }
53
82
  if (typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope) {
54
83
  const respond = self.postMessage.bind(self);
@@ -69,12 +98,11 @@ class TelegramWorkerPort extends TelegramWorkerPort$1 {
69
98
  });
70
99
  }
71
100
  connectToWorker(worker, handler) {
72
- if (worker instanceof Worker) {
101
+ if (worker instanceof Worker || worker instanceof MessagePort) {
73
102
  const send = worker.postMessage.bind(worker);
74
- const messageHandler = (ev) => {
75
- handler(ev.data);
76
- };
103
+ const messageHandler = (ev) => handler(ev.data);
77
104
  worker.addEventListener("message", messageHandler);
105
+ if (worker instanceof MessagePort) worker.start();
78
106
  return [
79
107
  send,
80
108
  () => {
@@ -84,27 +112,14 @@ class TelegramWorkerPort extends TelegramWorkerPort$1 {
84
112
  }
85
113
  if (worker instanceof SharedWorker) {
86
114
  const send = worker.port.postMessage.bind(worker.port);
87
- const pingInterval = setInterval(() => {
88
- worker.port.postMessage({ __type__: "ping" });
89
- }, 1e4);
90
115
  const messageHandler = (ev) => {
91
- if (ev.data.__type__ === "timeout") {
92
- location.reload();
93
- return;
94
- }
95
116
  handler(ev.data);
96
117
  };
97
118
  worker.port.addEventListener("message", messageHandler);
98
119
  worker.port.start();
99
- let cancelBeforeExit;
100
120
  const close = () => {
101
- clearInterval(pingInterval);
102
- worker.port.postMessage({ __type__: "close" });
103
121
  worker.port.removeEventListener("message", messageHandler);
104
- worker.port.close();
105
- cancelBeforeExit();
106
122
  };
107
- cancelBeforeExit = platform.beforeExit(close);
108
123
  return [send, close];
109
124
  }
110
125
  throw new Error("Only workers and shared workers are supported");
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};