@openclaw/matrix 2026.1.29

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.
Files changed (67) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/index.ts +18 -0
  3. package/openclaw.plugin.json +11 -0
  4. package/package.json +36 -0
  5. package/src/actions.ts +185 -0
  6. package/src/channel.directory.test.ts +56 -0
  7. package/src/channel.ts +417 -0
  8. package/src/config-schema.ts +62 -0
  9. package/src/directory-live.ts +175 -0
  10. package/src/group-mentions.ts +61 -0
  11. package/src/matrix/accounts.test.ts +83 -0
  12. package/src/matrix/accounts.ts +63 -0
  13. package/src/matrix/actions/client.ts +53 -0
  14. package/src/matrix/actions/messages.ts +120 -0
  15. package/src/matrix/actions/pins.ts +70 -0
  16. package/src/matrix/actions/reactions.ts +84 -0
  17. package/src/matrix/actions/room.ts +88 -0
  18. package/src/matrix/actions/summary.ts +77 -0
  19. package/src/matrix/actions/types.ts +84 -0
  20. package/src/matrix/actions.ts +15 -0
  21. package/src/matrix/active-client.ts +11 -0
  22. package/src/matrix/client/config.ts +165 -0
  23. package/src/matrix/client/create-client.ts +127 -0
  24. package/src/matrix/client/logging.ts +35 -0
  25. package/src/matrix/client/runtime.ts +4 -0
  26. package/src/matrix/client/shared.ts +169 -0
  27. package/src/matrix/client/storage.ts +131 -0
  28. package/src/matrix/client/types.ts +34 -0
  29. package/src/matrix/client.test.ts +57 -0
  30. package/src/matrix/client.ts +9 -0
  31. package/src/matrix/credentials.ts +103 -0
  32. package/src/matrix/deps.ts +57 -0
  33. package/src/matrix/format.test.ts +34 -0
  34. package/src/matrix/format.ts +22 -0
  35. package/src/matrix/index.ts +11 -0
  36. package/src/matrix/monitor/allowlist.ts +58 -0
  37. package/src/matrix/monitor/auto-join.ts +68 -0
  38. package/src/matrix/monitor/direct.ts +105 -0
  39. package/src/matrix/monitor/events.ts +103 -0
  40. package/src/matrix/monitor/handler.ts +645 -0
  41. package/src/matrix/monitor/index.ts +279 -0
  42. package/src/matrix/monitor/location.ts +83 -0
  43. package/src/matrix/monitor/media.test.ts +103 -0
  44. package/src/matrix/monitor/media.ts +113 -0
  45. package/src/matrix/monitor/mentions.ts +31 -0
  46. package/src/matrix/monitor/replies.ts +96 -0
  47. package/src/matrix/monitor/room-info.ts +58 -0
  48. package/src/matrix/monitor/rooms.ts +43 -0
  49. package/src/matrix/monitor/threads.ts +64 -0
  50. package/src/matrix/monitor/types.ts +39 -0
  51. package/src/matrix/poll-types.test.ts +22 -0
  52. package/src/matrix/poll-types.ts +157 -0
  53. package/src/matrix/probe.ts +70 -0
  54. package/src/matrix/send/client.ts +63 -0
  55. package/src/matrix/send/formatting.ts +92 -0
  56. package/src/matrix/send/media.ts +220 -0
  57. package/src/matrix/send/targets.test.ts +102 -0
  58. package/src/matrix/send/targets.ts +144 -0
  59. package/src/matrix/send/types.ts +109 -0
  60. package/src/matrix/send.test.ts +172 -0
  61. package/src/matrix/send.ts +255 -0
  62. package/src/onboarding.ts +432 -0
  63. package/src/outbound.ts +53 -0
  64. package/src/resolve-targets.ts +89 -0
  65. package/src/runtime.ts +14 -0
  66. package/src/tool-actions.ts +160 -0
  67. package/src/types.ts +95 -0
@@ -0,0 +1,84 @@
1
+ import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
+
3
+ export const MsgType = {
4
+ Text: "m.text",
5
+ } as const;
6
+
7
+ export const RelationType = {
8
+ Replace: "m.replace",
9
+ Annotation: "m.annotation",
10
+ } as const;
11
+
12
+ export const EventType = {
13
+ RoomMessage: "m.room.message",
14
+ RoomPinnedEvents: "m.room.pinned_events",
15
+ RoomTopic: "m.room.topic",
16
+ Reaction: "m.reaction",
17
+ } as const;
18
+
19
+ export type RoomMessageEventContent = {
20
+ msgtype: string;
21
+ body: string;
22
+ "m.new_content"?: RoomMessageEventContent;
23
+ "m.relates_to"?: {
24
+ rel_type?: string;
25
+ event_id?: string;
26
+ "m.in_reply_to"?: { event_id?: string };
27
+ };
28
+ };
29
+
30
+ export type ReactionEventContent = {
31
+ "m.relates_to": {
32
+ rel_type: string;
33
+ event_id: string;
34
+ key: string;
35
+ };
36
+ };
37
+
38
+ export type RoomPinnedEventsEventContent = {
39
+ pinned: string[];
40
+ };
41
+
42
+ export type RoomTopicEventContent = {
43
+ topic?: string;
44
+ };
45
+
46
+ export type MatrixRawEvent = {
47
+ event_id: string;
48
+ sender: string;
49
+ type: string;
50
+ origin_server_ts: number;
51
+ content: Record<string, unknown>;
52
+ unsigned?: {
53
+ redacted_because?: unknown;
54
+ };
55
+ };
56
+
57
+ export type MatrixActionClientOpts = {
58
+ client?: MatrixClient;
59
+ timeoutMs?: number;
60
+ };
61
+
62
+ export type MatrixMessageSummary = {
63
+ eventId?: string;
64
+ sender?: string;
65
+ body?: string;
66
+ msgtype?: string;
67
+ timestamp?: number;
68
+ relatesTo?: {
69
+ relType?: string;
70
+ eventId?: string;
71
+ key?: string;
72
+ };
73
+ };
74
+
75
+ export type MatrixReactionSummary = {
76
+ key: string;
77
+ count: number;
78
+ users: string[];
79
+ };
80
+
81
+ export type MatrixActionClient = {
82
+ client: MatrixClient;
83
+ stopOnDone: boolean;
84
+ };
@@ -0,0 +1,15 @@
1
+ export type {
2
+ MatrixActionClientOpts,
3
+ MatrixMessageSummary,
4
+ MatrixReactionSummary,
5
+ } from "./actions/types.js";
6
+ export {
7
+ sendMatrixMessage,
8
+ editMatrixMessage,
9
+ deleteMatrixMessage,
10
+ readMatrixMessages,
11
+ } from "./actions/messages.js";
12
+ export { listMatrixReactions, removeMatrixReactions } from "./actions/reactions.js";
13
+ export { pinMatrixMessage, unpinMatrixMessage, listMatrixPins } from "./actions/pins.js";
14
+ export { getMatrixMemberInfo, getMatrixRoomInfo } from "./actions/room.js";
15
+ export { reactMatrixMessage } from "./send.js";
@@ -0,0 +1,11 @@
1
+ import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
+
3
+ let activeClient: MatrixClient | null = null;
4
+
5
+ export function setActiveMatrixClient(client: MatrixClient | null): void {
6
+ activeClient = client;
7
+ }
8
+
9
+ export function getActiveMatrixClient(): MatrixClient | null {
10
+ return activeClient;
11
+ }
@@ -0,0 +1,165 @@
1
+ import { MatrixClient } from "@vector-im/matrix-bot-sdk";
2
+
3
+ import type { CoreConfig } from "../types.js";
4
+ import { getMatrixRuntime } from "../../runtime.js";
5
+ import { ensureMatrixSdkLoggingConfigured } from "./logging.js";
6
+ import type { MatrixAuth, MatrixResolvedConfig } from "./types.js";
7
+
8
+ function clean(value?: string): string {
9
+ return value?.trim() ?? "";
10
+ }
11
+
12
+ export function resolveMatrixConfig(
13
+ cfg: CoreConfig = getMatrixRuntime().config.loadConfig() as CoreConfig,
14
+ env: NodeJS.ProcessEnv = process.env,
15
+ ): MatrixResolvedConfig {
16
+ const matrix = cfg.channels?.matrix ?? {};
17
+ const homeserver = clean(matrix.homeserver) || clean(env.MATRIX_HOMESERVER);
18
+ const userId = clean(matrix.userId) || clean(env.MATRIX_USER_ID);
19
+ const accessToken =
20
+ clean(matrix.accessToken) || clean(env.MATRIX_ACCESS_TOKEN) || undefined;
21
+ const password = clean(matrix.password) || clean(env.MATRIX_PASSWORD) || undefined;
22
+ const deviceName =
23
+ clean(matrix.deviceName) || clean(env.MATRIX_DEVICE_NAME) || undefined;
24
+ const initialSyncLimit =
25
+ typeof matrix.initialSyncLimit === "number"
26
+ ? Math.max(0, Math.floor(matrix.initialSyncLimit))
27
+ : undefined;
28
+ const encryption = matrix.encryption ?? false;
29
+ return {
30
+ homeserver,
31
+ userId,
32
+ accessToken,
33
+ password,
34
+ deviceName,
35
+ initialSyncLimit,
36
+ encryption,
37
+ };
38
+ }
39
+
40
+ export async function resolveMatrixAuth(params?: {
41
+ cfg?: CoreConfig;
42
+ env?: NodeJS.ProcessEnv;
43
+ }): Promise<MatrixAuth> {
44
+ const cfg = params?.cfg ?? (getMatrixRuntime().config.loadConfig() as CoreConfig);
45
+ const env = params?.env ?? process.env;
46
+ const resolved = resolveMatrixConfig(cfg, env);
47
+ if (!resolved.homeserver) {
48
+ throw new Error("Matrix homeserver is required (matrix.homeserver)");
49
+ }
50
+
51
+ const {
52
+ loadMatrixCredentials,
53
+ saveMatrixCredentials,
54
+ credentialsMatchConfig,
55
+ touchMatrixCredentials,
56
+ } = await import("../credentials.js");
57
+
58
+ const cached = loadMatrixCredentials(env);
59
+ const cachedCredentials =
60
+ cached &&
61
+ credentialsMatchConfig(cached, {
62
+ homeserver: resolved.homeserver,
63
+ userId: resolved.userId || "",
64
+ })
65
+ ? cached
66
+ : null;
67
+
68
+ // If we have an access token, we can fetch userId via whoami if not provided
69
+ if (resolved.accessToken) {
70
+ let userId = resolved.userId;
71
+ if (!userId) {
72
+ // Fetch userId from access token via whoami
73
+ ensureMatrixSdkLoggingConfigured();
74
+ const tempClient = new MatrixClient(resolved.homeserver, resolved.accessToken);
75
+ const whoami = await tempClient.getUserId();
76
+ userId = whoami;
77
+ // Save the credentials with the fetched userId
78
+ saveMatrixCredentials({
79
+ homeserver: resolved.homeserver,
80
+ userId,
81
+ accessToken: resolved.accessToken,
82
+ });
83
+ } else if (cachedCredentials && cachedCredentials.accessToken === resolved.accessToken) {
84
+ touchMatrixCredentials(env);
85
+ }
86
+ return {
87
+ homeserver: resolved.homeserver,
88
+ userId,
89
+ accessToken: resolved.accessToken,
90
+ deviceName: resolved.deviceName,
91
+ initialSyncLimit: resolved.initialSyncLimit,
92
+ encryption: resolved.encryption,
93
+ };
94
+ }
95
+
96
+ if (cachedCredentials) {
97
+ touchMatrixCredentials(env);
98
+ return {
99
+ homeserver: cachedCredentials.homeserver,
100
+ userId: cachedCredentials.userId,
101
+ accessToken: cachedCredentials.accessToken,
102
+ deviceName: resolved.deviceName,
103
+ initialSyncLimit: resolved.initialSyncLimit,
104
+ encryption: resolved.encryption,
105
+ };
106
+ }
107
+
108
+ if (!resolved.userId) {
109
+ throw new Error(
110
+ "Matrix userId is required when no access token is configured (matrix.userId)",
111
+ );
112
+ }
113
+
114
+ if (!resolved.password) {
115
+ throw new Error(
116
+ "Matrix password is required when no access token is configured (matrix.password)",
117
+ );
118
+ }
119
+
120
+ // Login with password using HTTP API
121
+ const loginResponse = await fetch(`${resolved.homeserver}/_matrix/client/v3/login`, {
122
+ method: "POST",
123
+ headers: { "Content-Type": "application/json" },
124
+ body: JSON.stringify({
125
+ type: "m.login.password",
126
+ identifier: { type: "m.id.user", user: resolved.userId },
127
+ password: resolved.password,
128
+ initial_device_display_name: resolved.deviceName ?? "OpenClaw Gateway",
129
+ }),
130
+ });
131
+
132
+ if (!loginResponse.ok) {
133
+ const errorText = await loginResponse.text();
134
+ throw new Error(`Matrix login failed: ${errorText}`);
135
+ }
136
+
137
+ const login = (await loginResponse.json()) as {
138
+ access_token?: string;
139
+ user_id?: string;
140
+ device_id?: string;
141
+ };
142
+
143
+ const accessToken = login.access_token?.trim();
144
+ if (!accessToken) {
145
+ throw new Error("Matrix login did not return an access token");
146
+ }
147
+
148
+ const auth: MatrixAuth = {
149
+ homeserver: resolved.homeserver,
150
+ userId: login.user_id ?? resolved.userId,
151
+ accessToken,
152
+ deviceName: resolved.deviceName,
153
+ initialSyncLimit: resolved.initialSyncLimit,
154
+ encryption: resolved.encryption,
155
+ };
156
+
157
+ saveMatrixCredentials({
158
+ homeserver: auth.homeserver,
159
+ userId: auth.userId,
160
+ accessToken: auth.accessToken,
161
+ deviceId: login.device_id,
162
+ });
163
+
164
+ return auth;
165
+ }
@@ -0,0 +1,127 @@
1
+ import fs from "node:fs";
2
+
3
+ import {
4
+ LogService,
5
+ MatrixClient,
6
+ SimpleFsStorageProvider,
7
+ RustSdkCryptoStorageProvider,
8
+ } from "@vector-im/matrix-bot-sdk";
9
+ import type { IStorageProvider, ICryptoStorageProvider } from "@vector-im/matrix-bot-sdk";
10
+
11
+ import { ensureMatrixSdkLoggingConfigured } from "./logging.js";
12
+ import {
13
+ maybeMigrateLegacyStorage,
14
+ resolveMatrixStoragePaths,
15
+ writeStorageMeta,
16
+ } from "./storage.js";
17
+
18
+ function sanitizeUserIdList(input: unknown, label: string): string[] {
19
+ if (input == null) return [];
20
+ if (!Array.isArray(input)) {
21
+ LogService.warn(
22
+ "MatrixClientLite",
23
+ `Expected ${label} list to be an array, got ${typeof input}`,
24
+ );
25
+ return [];
26
+ }
27
+ const filtered = input.filter(
28
+ (entry): entry is string => typeof entry === "string" && entry.trim().length > 0,
29
+ );
30
+ if (filtered.length !== input.length) {
31
+ LogService.warn(
32
+ "MatrixClientLite",
33
+ `Dropping ${input.length - filtered.length} invalid ${label} entries from sync payload`,
34
+ );
35
+ }
36
+ return filtered;
37
+ }
38
+
39
+ export async function createMatrixClient(params: {
40
+ homeserver: string;
41
+ userId: string;
42
+ accessToken: string;
43
+ encryption?: boolean;
44
+ localTimeoutMs?: number;
45
+ accountId?: string | null;
46
+ }): Promise<MatrixClient> {
47
+ ensureMatrixSdkLoggingConfigured();
48
+ const env = process.env;
49
+
50
+ // Create storage provider
51
+ const storagePaths = resolveMatrixStoragePaths({
52
+ homeserver: params.homeserver,
53
+ userId: params.userId,
54
+ accessToken: params.accessToken,
55
+ accountId: params.accountId,
56
+ env,
57
+ });
58
+ maybeMigrateLegacyStorage({ storagePaths, env });
59
+ fs.mkdirSync(storagePaths.rootDir, { recursive: true });
60
+ const storage: IStorageProvider = new SimpleFsStorageProvider(storagePaths.storagePath);
61
+
62
+ // Create crypto storage if encryption is enabled
63
+ let cryptoStorage: ICryptoStorageProvider | undefined;
64
+ if (params.encryption) {
65
+ fs.mkdirSync(storagePaths.cryptoPath, { recursive: true });
66
+
67
+ try {
68
+ const { StoreType } = await import("@matrix-org/matrix-sdk-crypto-nodejs");
69
+ cryptoStorage = new RustSdkCryptoStorageProvider(
70
+ storagePaths.cryptoPath,
71
+ StoreType.Sqlite,
72
+ );
73
+ } catch (err) {
74
+ LogService.warn("MatrixClientLite", "Failed to initialize crypto storage, E2EE disabled:", err);
75
+ }
76
+ }
77
+
78
+ writeStorageMeta({
79
+ storagePaths,
80
+ homeserver: params.homeserver,
81
+ userId: params.userId,
82
+ accountId: params.accountId,
83
+ });
84
+
85
+ const client = new MatrixClient(
86
+ params.homeserver,
87
+ params.accessToken,
88
+ storage,
89
+ cryptoStorage,
90
+ );
91
+
92
+ if (client.crypto) {
93
+ const originalUpdateSyncData = client.crypto.updateSyncData.bind(client.crypto);
94
+ client.crypto.updateSyncData = async (
95
+ toDeviceMessages,
96
+ otkCounts,
97
+ unusedFallbackKeyAlgs,
98
+ changedDeviceLists,
99
+ leftDeviceLists,
100
+ ) => {
101
+ const safeChanged = sanitizeUserIdList(changedDeviceLists, "changed device list");
102
+ const safeLeft = sanitizeUserIdList(leftDeviceLists, "left device list");
103
+ try {
104
+ return await originalUpdateSyncData(
105
+ toDeviceMessages,
106
+ otkCounts,
107
+ unusedFallbackKeyAlgs,
108
+ safeChanged,
109
+ safeLeft,
110
+ );
111
+ } catch (err) {
112
+ const message = typeof err === "string" ? err : err instanceof Error ? err.message : "";
113
+ if (message.includes("Expect value to be String")) {
114
+ LogService.warn(
115
+ "MatrixClientLite",
116
+ "Ignoring malformed device list entries during crypto sync",
117
+ message,
118
+ );
119
+ return;
120
+ }
121
+ throw err;
122
+ }
123
+ };
124
+ }
125
+
126
+ return client;
127
+ }
@@ -0,0 +1,35 @@
1
+ import { ConsoleLogger, LogService } from "@vector-im/matrix-bot-sdk";
2
+
3
+ let matrixSdkLoggingConfigured = false;
4
+ const matrixSdkBaseLogger = new ConsoleLogger();
5
+
6
+ function shouldSuppressMatrixHttpNotFound(
7
+ module: string,
8
+ messageOrObject: unknown[],
9
+ ): boolean {
10
+ if (module !== "MatrixHttpClient") return false;
11
+ return messageOrObject.some((entry) => {
12
+ if (!entry || typeof entry !== "object") return false;
13
+ return (entry as { errcode?: string }).errcode === "M_NOT_FOUND";
14
+ });
15
+ }
16
+
17
+ export function ensureMatrixSdkLoggingConfigured(): void {
18
+ if (matrixSdkLoggingConfigured) return;
19
+ matrixSdkLoggingConfigured = true;
20
+
21
+ LogService.setLogger({
22
+ trace: (module, ...messageOrObject) =>
23
+ matrixSdkBaseLogger.trace(module, ...messageOrObject),
24
+ debug: (module, ...messageOrObject) =>
25
+ matrixSdkBaseLogger.debug(module, ...messageOrObject),
26
+ info: (module, ...messageOrObject) =>
27
+ matrixSdkBaseLogger.info(module, ...messageOrObject),
28
+ warn: (module, ...messageOrObject) =>
29
+ matrixSdkBaseLogger.warn(module, ...messageOrObject),
30
+ error: (module, ...messageOrObject) => {
31
+ if (shouldSuppressMatrixHttpNotFound(module, messageOrObject)) return;
32
+ matrixSdkBaseLogger.error(module, ...messageOrObject);
33
+ },
34
+ });
35
+ }
@@ -0,0 +1,4 @@
1
+ export function isBunRuntime(): boolean {
2
+ const versions = process.versions as { bun?: string };
3
+ return typeof versions.bun === "string";
4
+ }
@@ -0,0 +1,169 @@
1
+ import { LogService } from "@vector-im/matrix-bot-sdk";
2
+ import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
3
+
4
+ import type { CoreConfig } from "../types.js";
5
+ import { createMatrixClient } from "./create-client.js";
6
+ import { resolveMatrixAuth } from "./config.js";
7
+ import { DEFAULT_ACCOUNT_KEY } from "./storage.js";
8
+ import type { MatrixAuth } from "./types.js";
9
+
10
+ type SharedMatrixClientState = {
11
+ client: MatrixClient;
12
+ key: string;
13
+ started: boolean;
14
+ cryptoReady: boolean;
15
+ };
16
+
17
+ let sharedClientState: SharedMatrixClientState | null = null;
18
+ let sharedClientPromise: Promise<SharedMatrixClientState> | null = null;
19
+ let sharedClientStartPromise: Promise<void> | null = null;
20
+
21
+ function buildSharedClientKey(auth: MatrixAuth, accountId?: string | null): string {
22
+ return [
23
+ auth.homeserver,
24
+ auth.userId,
25
+ auth.accessToken,
26
+ auth.encryption ? "e2ee" : "plain",
27
+ accountId ?? DEFAULT_ACCOUNT_KEY,
28
+ ].join("|");
29
+ }
30
+
31
+ async function createSharedMatrixClient(params: {
32
+ auth: MatrixAuth;
33
+ timeoutMs?: number;
34
+ accountId?: string | null;
35
+ }): Promise<SharedMatrixClientState> {
36
+ const client = await createMatrixClient({
37
+ homeserver: params.auth.homeserver,
38
+ userId: params.auth.userId,
39
+ accessToken: params.auth.accessToken,
40
+ encryption: params.auth.encryption,
41
+ localTimeoutMs: params.timeoutMs,
42
+ accountId: params.accountId,
43
+ });
44
+ return {
45
+ client,
46
+ key: buildSharedClientKey(params.auth, params.accountId),
47
+ started: false,
48
+ cryptoReady: false,
49
+ };
50
+ }
51
+
52
+ async function ensureSharedClientStarted(params: {
53
+ state: SharedMatrixClientState;
54
+ timeoutMs?: number;
55
+ initialSyncLimit?: number;
56
+ encryption?: boolean;
57
+ }): Promise<void> {
58
+ if (params.state.started) return;
59
+ if (sharedClientStartPromise) {
60
+ await sharedClientStartPromise;
61
+ return;
62
+ }
63
+ sharedClientStartPromise = (async () => {
64
+ const client = params.state.client;
65
+
66
+ // Initialize crypto if enabled
67
+ if (params.encryption && !params.state.cryptoReady) {
68
+ try {
69
+ const joinedRooms = await client.getJoinedRooms();
70
+ if (client.crypto) {
71
+ await client.crypto.prepare(joinedRooms);
72
+ params.state.cryptoReady = true;
73
+ }
74
+ } catch (err) {
75
+ LogService.warn("MatrixClientLite", "Failed to prepare crypto:", err);
76
+ }
77
+ }
78
+
79
+ await client.start();
80
+ params.state.started = true;
81
+ })();
82
+ try {
83
+ await sharedClientStartPromise;
84
+ } finally {
85
+ sharedClientStartPromise = null;
86
+ }
87
+ }
88
+
89
+ export async function resolveSharedMatrixClient(
90
+ params: {
91
+ cfg?: CoreConfig;
92
+ env?: NodeJS.ProcessEnv;
93
+ timeoutMs?: number;
94
+ auth?: MatrixAuth;
95
+ startClient?: boolean;
96
+ accountId?: string | null;
97
+ } = {},
98
+ ): Promise<MatrixClient> {
99
+ const auth = params.auth ?? (await resolveMatrixAuth({ cfg: params.cfg, env: params.env }));
100
+ const key = buildSharedClientKey(auth, params.accountId);
101
+ const shouldStart = params.startClient !== false;
102
+
103
+ if (sharedClientState?.key === key) {
104
+ if (shouldStart) {
105
+ await ensureSharedClientStarted({
106
+ state: sharedClientState,
107
+ timeoutMs: params.timeoutMs,
108
+ initialSyncLimit: auth.initialSyncLimit,
109
+ encryption: auth.encryption,
110
+ });
111
+ }
112
+ return sharedClientState.client;
113
+ }
114
+
115
+ if (sharedClientPromise) {
116
+ const pending = await sharedClientPromise;
117
+ if (pending.key === key) {
118
+ if (shouldStart) {
119
+ await ensureSharedClientStarted({
120
+ state: pending,
121
+ timeoutMs: params.timeoutMs,
122
+ initialSyncLimit: auth.initialSyncLimit,
123
+ encryption: auth.encryption,
124
+ });
125
+ }
126
+ return pending.client;
127
+ }
128
+ pending.client.stop();
129
+ sharedClientState = null;
130
+ sharedClientPromise = null;
131
+ }
132
+
133
+ sharedClientPromise = createSharedMatrixClient({
134
+ auth,
135
+ timeoutMs: params.timeoutMs,
136
+ accountId: params.accountId,
137
+ });
138
+ try {
139
+ const created = await sharedClientPromise;
140
+ sharedClientState = created;
141
+ if (shouldStart) {
142
+ await ensureSharedClientStarted({
143
+ state: created,
144
+ timeoutMs: params.timeoutMs,
145
+ initialSyncLimit: auth.initialSyncLimit,
146
+ encryption: auth.encryption,
147
+ });
148
+ }
149
+ return created.client;
150
+ } finally {
151
+ sharedClientPromise = null;
152
+ }
153
+ }
154
+
155
+ export async function waitForMatrixSync(_params: {
156
+ client: MatrixClient;
157
+ timeoutMs?: number;
158
+ abortSignal?: AbortSignal;
159
+ }): Promise<void> {
160
+ // @vector-im/matrix-bot-sdk handles sync internally in start()
161
+ // This is kept for API compatibility but is essentially a no-op now
162
+ }
163
+
164
+ export function stopSharedClient(): void {
165
+ if (sharedClientState) {
166
+ sharedClientState.client.stop();
167
+ sharedClientState = null;
168
+ }
169
+ }