From c503506fb02b8b268f450e74e8e300794eae061d Mon Sep 17 00:00:00 2001 From: Aryan-Verma-999 Date: Tue, 2 Dec 2025 03:34:28 +0530 Subject: [PATCH 1/6] feat: Matrix Integration for EmbeddedChat --- README.md | 14 + packages/api/package.json | 3 +- packages/api/src/EmbeddedChatApi.ts | 59 +-- packages/api/src/IChatProvider.ts | 31 ++ packages/api/src/MatrixProvider.ts | 363 ++++++++++++++++++ packages/api/src/index.ts | 2 + .../react/src/stories/MatrixMode.stories.js | 21 + .../ChatInput/ChatInputFormattingToolbar.js | 6 +- packages/react/src/views/EmbeddedChat.js | 13 +- yarn.lock | 143 ++++++- 10 files changed, 618 insertions(+), 37 deletions(-) create mode 100644 packages/api/src/IChatProvider.ts create mode 100644 packages/api/src/MatrixProvider.ts create mode 100644 packages/react/src/stories/MatrixMode.stories.js diff --git a/README.md b/README.md index 455b2fa897..86a30425de 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,20 @@ Test credentials: Installation and usage documentation could be found here [EmbeddedChat installation and usage](https://rocketchat.github.io/EmbeddedChat/docs/docs/Usage/embeddedchat_setup) +### Matrix Support (Experimental) + +EmbeddedChat now supports connecting to a Matrix homeserver. + +To use Matrix mode, set the `mode` prop to `'matrix'` and provide the `host` and `roomId`. + +```jsx + +``` + ## Development ### Local Setup diff --git a/packages/api/package.json b/packages/api/package.json index a05ca8e630..6d18201304 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -24,6 +24,7 @@ }, "dependencies": { "@embeddedchat/auth": "workspace:^", - "@rocket.chat/sdk": "^1.0.0-alpha.42" + "@rocket.chat/sdk": "^1.0.0-alpha.42", + "matrix-js-sdk": "^39.3.0-rc.0" } } diff --git a/packages/api/src/EmbeddedChatApi.ts b/packages/api/src/EmbeddedChatApi.ts index 46690e24e6..bcc499cba6 100644 --- a/packages/api/src/EmbeddedChatApi.ts +++ b/packages/api/src/EmbeddedChatApi.ts @@ -6,10 +6,11 @@ import { RocketChatAuth, ApiError, } from "@embeddedchat/auth"; +import { IChatProvider } from "./IChatProvider"; // mutliple typing status can come at the same time they should be processed in order. let typingHandlerLock = 0; -export default class EmbeddedChatApi { +export default class EmbeddedChatApi implements IChatProvider { host: string; rid: string; rcClient: Rocketchat; @@ -73,21 +74,21 @@ export default class EmbeddedChatApi { const payload = acsCode ? JSON.stringify({ - serviceName: "google", - accessToken: tokens.access_token, - idToken: tokens.id_token, - expiresIn: 3600, - totp: { - code: acsPayload, - }, - }) + serviceName: "google", + accessToken: tokens.access_token, + idToken: tokens.id_token, + expiresIn: 3600, + totp: { + code: acsPayload, + }, + }) : JSON.stringify({ - serviceName: "google", - accessToken: tokens.access_token, - idToken: tokens.id_token, - expiresIn: 3600, - scope: "profile", - }); + serviceName: "google", + accessToken: tokens.access_token, + idToken: tokens.id_token, + expiresIn: 3600, + scope: "profile", + }); try { const req = await fetch(`${this.host}/api/v1/login`, { @@ -363,7 +364,7 @@ export default class EmbeddedChatApi { typingHandlerLock = 0; }, 2000); // eslint-disable-next-line no-empty - while (typingHandlerLock) {} + while (typingHandlerLock) { } typingHandlerLock = 1; // move user to front if typing else remove it. const idx = this.typingUsers.indexOf(typingUser); @@ -555,9 +556,9 @@ export default class EmbeddedChatApi { query?: object | undefined; field?: object | undefined; } = { - query: undefined, - field: undefined, - }, + query: undefined, + field: undefined, + }, isChannelPrivate = false ) { const roomType = isChannelPrivate ? "groups" : "channels"; @@ -594,10 +595,10 @@ export default class EmbeddedChatApi { field?: object | undefined; offset?: number; } = { - query: undefined, - field: undefined, - offset: 50, - }, + query: undefined, + field: undefined, + offset: 50, + }, isChannelPrivate = false ) { const roomType = isChannelPrivate ? "groups" : "channels"; @@ -745,13 +746,13 @@ export default class EmbeddedChatApi { const messageObj = typeof message === "string" ? { - rid: this.rid, - msg: message, - } + rid: this.rid, + msg: message, + } : { - ...message, - rid: this.rid, - }; + ...message, + rid: this.rid, + }; if (threadId) { messageObj.tmid = threadId; } diff --git a/packages/api/src/IChatProvider.ts b/packages/api/src/IChatProvider.ts new file mode 100644 index 0000000000..9ae30e4554 --- /dev/null +++ b/packages/api/src/IChatProvider.ts @@ -0,0 +1,31 @@ +import { RocketChatAuth } from "@embeddedchat/auth"; + +export interface IChatProvider { + sendTypingStatus(username: string, typing: boolean): Promise; + + addMessageListener(callback: (message: any) => void): void; + removeMessageListener(callback: (message: any) => void): void; + addMessageDeleteListener(callback: (messageId: string) => void): void; + removeMessageDeleteListener(callback: (messageId: string) => void): void; + addTypingStatusListener(callback: (users: string[]) => void): void; + removeTypingStatusListener(callback: (users: string[]) => void): void; + addActionTriggeredListener(callback: (data: any) => void): void; + removeActionTriggeredListener(callback: (data: any) => void): void; + addUiInteractionListener(callback: (data: any) => void): void; + removeUiInteractionListener(callback: (data: any) => void): void; + + login(userOrEmail: string, password: string, code?: string): Promise; + logout(): Promise; + autoLogin(auth: { flow: "PASSWORD" | "OAUTH" | "TOKEN"; credentials: any }): Promise; + googleSSOLogin(signIn: Function, acsCode: string): Promise; + + getRCAppInfo(): Promise; + updateUserUsername(userid: string, username: string): Promise; + channelInfo(): Promise; + getRoomInfo(): Promise; + permissionInfo(): Promise; + + setAuth(auth: RocketChatAuth): void; + getAuth(): RocketChatAuth; + getHost(): string; +} diff --git a/packages/api/src/MatrixProvider.ts b/packages/api/src/MatrixProvider.ts new file mode 100644 index 0000000000..6e198a78cd --- /dev/null +++ b/packages/api/src/MatrixProvider.ts @@ -0,0 +1,363 @@ +import * as sdk from "matrix-js-sdk"; +import { IChatProvider } from "./IChatProvider"; +import { RocketChatAuth } from "@embeddedchat/auth"; + +class MatrixAuth extends RocketChatAuth { + private onAuthChangeCb: ((user: any) => void) | null = null; + currentUser: any | null = null; + + constructor(config: any) { + super(config); + } + + async onAuthChange(cb: (user: any) => void): Promise { + this.onAuthChangeCb = cb; + if (this.currentUser) { + cb(this.currentUser); + } + } + + notifyAuthChange(user: any) { + this.currentUser = user; + if (this.onAuthChangeCb) { + this.onAuthChangeCb(user); + } + } +} + +export default class MatrixProvider implements IChatProvider { + private client: any; + private host: string; + private roomId: string; + private auth: MatrixAuth; + private onMessageCallbacks: ((message: any) => void)[] = []; + + constructor(host: string, roomId: string) { + this.host = host; + this.roomId = roomId; + this.auth = new MatrixAuth({ + host: this.host, + deleteToken: async () => { }, + getToken: async () => "", + saveToken: async () => { }, + }); + } + + async connect(): Promise { + if (!this.client) { + this.client = sdk.createClient({ + baseUrl: this.host, + }); + } + + // Map Matrix events to EmbeddedChat callbacks + this.client.removeAllListeners("Room.timeline"); + this.client.on("Room.timeline", (event: any, room: any, toStartOfTimeline: boolean) => { + if (event.getType() !== "m.room.message" && event.getType() !== "m.room.encrypted") { + return; + } + if (room.roomId !== this.roomId) { + return; + } + const isEncrypted = event.getType() === "m.room.encrypted"; + const text = isEncrypted ? "⚠️ [Encrypted Message]" : event.getContent().body; + + // Convert Matrix event to RC message format + const message = { + _id: event.getId(), + msg: text, + md: [ + { + type: "PARAGRAPH", + value: [ + { + type: "PLAIN_TEXT", + value: text, + }, + ], + }, + ], + ts: new Date(event.getTs()).toISOString(), + u: { + _id: event.getSender(), + username: event.getSender(), + }, + rid: room.roomId, + }; + this.onMessageCallbacks.forEach(cb => cb(message)); + }); + + if (!this.client.clientRunning) { + await this.client.startClient({ initialSyncLimit: 10 }); + } + + await new Promise((resolve) => { + const state = this.client.getSyncState(); + if (state === "PREPARED" || state === "SYNCING") { + resolve(); + } else { + const checkSync = (state: any) => { + if (state === "PREPARED" || state === "SYNCING") { + this.client.removeListener("Sync", checkSync); + resolve(); + } + }; + this.client.on("Sync", checkSync); + + // Timeout after 5 seconds to prevent hanging + setTimeout(() => { + this.client.removeListener("Sync", checkSync); + resolve(); + }, 5000); + } + }); + + let room = this.client.getRoom(this.roomId); + if (!room) { + try { + await this.client.joinRoom(this.roomId); + + // Wait for room to appear in store + let retries = 0; + while (!room && retries < 20) { + await new Promise(r => setTimeout(r, 1000)); + room = this.client.getRoom(this.roomId); + retries++; + } + } catch (error) { + console.error(`MatrixProvider: Failed to join room ${this.roomId}`, error); + } + } + } + + async login(userOrEmail: string, password: string, code?: string): Promise { + if (!this.client) { + this.client = sdk.createClient({ + baseUrl: this.host, + }); + } + const response = await this.client.login("m.login.password", { + identifier: { + type: "m.id.user", + user: userOrEmail, + }, + password: password, + }); + + // Ensure we connect (start client and sync) after login + await this.connect(); + + const user = { + username: response.user_id, + _id: response.user_id, + name: response.user_id, + avatarUrl: "", // Placeholder + roles: [] + }; + + // Notify auth change to update UI + this.auth.notifyAuthChange({ me: user }); + + return { status: "success", me: user }; + } + + async close(): Promise { + if (this.client) { + this.client.stopClient(); + } + this.auth.notifyAuthChange(null); + } + + async getStarredMessages(): Promise { + return { messages: [] }; + } + + async getAllFiles(): Promise { + return { files: [] }; + } + + async getMessages(anonymousMode: boolean, options?: any, isChannelPrivate?: boolean): Promise { + if (!this.client) return { messages: [], count: 0 }; + const room = this.client.getRoom(this.roomId); + if (!room) { + return { messages: [], count: 0 }; + } + + const events = room.getLiveTimeline().getEvents(); + + // Reverse events to match Rocket.Chat's newest-first expectation + const messages = events + .slice() // Create a copy before reversing + .reverse() + .filter((event: any) => event.getType() === "m.room.message" || event.getType() === "m.room.encrypted") + .map((event: any) => { + const isEncrypted = event.getType() === "m.room.encrypted"; + const text = isEncrypted ? "⚠️ [Encrypted Message]" : event.getContent().body; + return { + _id: event.getId(), + msg: text, + md: [ + { + type: "PARAGRAPH", + value: [ + { + type: "PLAIN_TEXT", + value: text, + }, + ], + }, + ], + ts: new Date(event.getTs()).toISOString(), + u: { + _id: event.getSender(), + username: event.getSender(), + }, + rid: room.roomId, + }; + }); + + return { messages, count: messages.length, success: true }; + } + + async channelInfo(): Promise { + if (!this.client) return {}; + const room = this.client.getRoom(this.roomId); + if (!room) return {}; + return { + success: true, + room: { + _id: room.roomId, + name: room.name || "Matrix Room", + t: "c", + } + }; + } + + async getRoomInfo(): Promise { + if (!this.client) return {}; + const room = this.client.getRoom(this.roomId); + if (!room) return {}; + return { + success: true, + room: { + _id: room.roomId, + name: room.name, + t: "c", + } + }; + } + + async sendMessage(message: any, threadId?: string): Promise { + const content = { + msgtype: "m.text", + body: typeof message === "string" ? message : message.msg, + }; + const response = await this.client.sendEvent(this.roomId, "m.room.message", content); + return { _id: response.event_id }; + } + + async getOlderMessages(anonymousMode: boolean, options?: any, isChannelPrivate?: boolean): Promise { + return { messages: [] }; + } + + async getThreadMessages(tmid: string, isChannelPrivate?: boolean): Promise { + return { messages: [] }; + } + + async deleteMessage(msgId: string): Promise { + return {}; + } + + async updateMessage(msgId: string, text: string): Promise { + return {}; + } + + async getChannelRoles(isChannelPrivate?: boolean): Promise { + return []; + } + + async getUsersInRole(role: string): Promise { + return []; + } + + async getUserRoles(): Promise { + return []; + } + + async sendTypingStatus(username: string, typing: boolean): Promise { + await this.client.sendTyping(this.roomId, typing, 3000); + } + + addMessageListener(callback: (message: any) => void): void { + this.onMessageCallbacks.push(callback); + } + + removeMessageListener(callback: (message: any) => void): void { + this.onMessageCallbacks = this.onMessageCallbacks.filter(c => c !== callback); + } + + addMessageDeleteListener(callback: (messageId: string) => void): void { + } + + removeMessageDeleteListener(callback: (messageId: string) => void): void { + } + + addTypingStatusListener(callback: (users: string[]) => void): void { + } + + removeTypingStatusListener(callback: (users: string[]) => void): void { + } + + addActionTriggeredListener(callback: (data: any) => void): void { + } + + removeActionTriggeredListener(callback: (data: any) => void): void { + } + + addUiInteractionListener(callback: (data: any) => void): void { + } + + removeUiInteractionListener(callback: (data: any) => void): void { + } + + async logout(): Promise { + if (this.client) { + await this.client.logout(); + } + this.auth.notifyAuthChange(null); + } + + async autoLogin(auth: { flow: "PASSWORD" | "OAUTH" | "TOKEN"; credentials: any }): Promise { + } + + async googleSSOLogin(signIn: Function, acsCode: string): Promise { + } + + async getRCAppInfo(): Promise { + return null; + } + + async updateUserUsername(userid: string, username: string): Promise { + return {}; + } + + async permissionInfo(): Promise { + return []; + } + + setAuth(auth: RocketChatAuth): void { + // We ignore external auth setting for now as we manage our own MatrixAuth + } + + getAuth(): RocketChatAuth { + return this.auth; + } + + getHost(): string { + return this.host; + } + + async getMessageLimit(): Promise { + return { value: 5000 }; // Default limit + } +} diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index b4ff83a1c6..046d30f34d 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1 +1,3 @@ export { default as EmbeddedChatApi } from "./EmbeddedChatApi"; +export { default as MatrixProvider } from "./MatrixProvider"; +export * from "./IChatProvider"; diff --git a/packages/react/src/stories/MatrixMode.stories.js b/packages/react/src/stories/MatrixMode.stories.js new file mode 100644 index 0000000000..f8f1923883 --- /dev/null +++ b/packages/react/src/stories/MatrixMode.stories.js @@ -0,0 +1,21 @@ +import { EmbeddedChat } from '..'; + +export default { + title: 'EmbeddedChat/Matrix', + component: EmbeddedChat, +}; + +export const MatrixMode = { + args: { + mode: 'matrix', + host: 'https://matrix.org', // Replace with your Matrix homeserver URL + roomId: '!mLGtEDRtJGxNKvXHPH:matrix.org', // Replace with a public room ID (e.g., Matrix HQ) + channelName: 'Matrix Room', + headerColor: 'white', + toastBarPosition: 'bottom right', + showRoles: true, + enableThreads: true, + hideHeader: false, + dark: false, + }, +}; diff --git a/packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js b/packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js index 5d8c20a600..a930009255 100644 --- a/packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js +++ b/packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js @@ -116,6 +116,7 @@ const ChatInputFormattingToolbar = ({ audio: ( formatter.find((item) => item.name === name)) .map((item) => isPopoverOpen && popOverItems.includes('formatter') ? ( - <> + {item.name} - + ) : ( { const { isClosable = false, - setClosableState = () => {}, + setClosableState = () => { }, width = '100%', height = '95vh', host = 'http://localhost:3000', @@ -58,6 +58,7 @@ const EmbeddedChat = (props) => { secure = false, dark = false, remoteOpt = false, + mode = 'rocketchat', // 'rocketchat' or 'matrix' } = config; const hasMounted = useRef(false); @@ -90,6 +91,9 @@ const EmbeddedChat = (props) => { } const initializeRCInstance = useCallback(() => { + if (mode === 'matrix') { + return new MatrixProvider(host, roomId); + } const newRCInstance = new EmbeddedChatApi(host, roomId, { getToken, deleteToken, @@ -97,7 +101,7 @@ const EmbeddedChat = (props) => { }); return newRCInstance; - }, [host, roomId, getToken, deleteToken, saveToken]); + }, [host, roomId, getToken, deleteToken, saveToken, mode]); const [RCInstance, setRCInstance] = useState(() => initializeRCInstance()); @@ -138,7 +142,7 @@ const EmbeddedChat = (props) => { if (user) { RCInstance.connect() .then(() => { - console.log(`Connected to RocketChat ${RCInstance.host}`); + console.log(`Connected to chat provider ${RCInstance.host}`); const { me } = user; setAuthenticatedAvatarUrl(me.avatarUrl); setAuthenticatedUsername(me.username); @@ -288,6 +292,7 @@ EmbeddedChat.propTypes = { style: PropTypes.object, hideHeader: PropTypes.bool, dark: PropTypes.bool, + mode: PropTypes.oneOf(['rocketchat', 'matrix']), }; export default memo(EmbeddedChat); diff --git a/yarn.lock b/yarn.lock index 23b67cfd11..c0d992066c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2293,6 +2293,7 @@ __metadata: dependencies: "@embeddedchat/auth": "workspace:^" "@rocket.chat/sdk": ^1.0.0-alpha.42 + matrix-js-sdk: ^39.3.0-rc.0 parcel: ^2.10.3 rollup: ^3.23.0 rollup-plugin-dts: ^6.0.1 @@ -4600,6 +4601,13 @@ __metadata: languageName: node linkType: hard +"@matrix-org/matrix-sdk-crypto-wasm@npm:^15.3.0": + version: 15.3.0 + resolution: "@matrix-org/matrix-sdk-crypto-wasm@npm:15.3.0" + checksum: 1c84eefd49ccb2e76b3163846409d314a12986e8bc8d42b6342021904b6831efe455925bbc1d8e46c36a1baaf848a0b1763b65cd31cf18d9315e3828a6a8a938 + languageName: node + linkType: hard + "@mdx-js/react@npm:^2.1.5": version: 2.3.0 resolution: "@mdx-js/react@npm:2.3.0" @@ -9482,6 +9490,13 @@ __metadata: languageName: node linkType: hard +"@types/events@npm:^3.0.0": + version: 3.0.3 + resolution: "@types/events@npm:3.0.3" + checksum: 50af9312fab001fd6bd4bb3ff65830f940877e6778de140a92481a0d9bf5f4853d44ec758a8800ef60e0598ac43ed1b5688116a3c65906ae54e989278d6c7c82 + languageName: node + linkType: hard + "@types/express-serve-static-core@npm:^4.17.33": version: 4.17.41 resolution: "@types/express-serve-static-core@npm:4.17.41" @@ -11013,6 +11028,13 @@ __metadata: languageName: node linkType: hard +"another-json@npm:^0.2.0": + version: 0.2.0 + resolution: "another-json@npm:0.2.0" + checksum: b1d27bd5d7a35364ff2e8eb66e65ef9a53f3ab84f7a4ae38495bf14ba0b346654f107487b4f2f247dbe6a22a8fb4ca2a94330738176516b78494e641fce05178 + languageName: node + linkType: hard + "anser@npm:^1.4.9": version: 1.4.10 resolution: "anser@npm:1.4.10" @@ -12096,6 +12118,13 @@ __metadata: languageName: node linkType: hard +"base-x@npm:^5.0.0": + version: 5.0.1 + resolution: "base-x@npm:5.0.1" + checksum: 6e4f847ef842e0a71c6b6020a6ec482a2a5e727f5a98534dbfd5d5a4e8afbc0d1bdf1fd57174b3f0455d107f10a932c3c7710bec07e2878f80178607f8f605c8 + languageName: node + linkType: hard + "base64-js@npm:^1.0.2, base64-js@npm:^1.1.2, base64-js@npm:^1.2.3, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" @@ -12523,6 +12552,15 @@ __metadata: languageName: node linkType: hard +"bs58@npm:^6.0.0": + version: 6.0.0 + resolution: "bs58@npm:6.0.0" + dependencies: + base-x: ^5.0.0 + checksum: 820334f9513bba6195136dfc9dfbd1f5aded6c7864639f3ee7b63c2d9d6f9f2813b9949b1f6beb9c161237be2a461097444c2ff587c8c3b824fe18878fa22448 + languageName: node + linkType: hard + "bser@npm:2.1.1": version: 2.1.1 resolution: "bser@npm:2.1.1" @@ -13739,7 +13777,7 @@ __metadata: languageName: node linkType: hard -"content-type@npm:~1.0.4, content-type@npm:~1.0.5": +"content-type@npm:^1.0.4, content-type@npm:~1.0.4, content-type@npm:~1.0.5": version: 1.0.5 resolution: "content-type@npm:1.0.5" checksum: 566271e0a251642254cde0f845f9dd4f9856e52d988f4eb0d0dcffbb7a1f8ec98de7a5215fc628f3bce30fe2fb6fd2bc064b562d721658c59b544e2d34ea2766 @@ -19930,6 +19968,13 @@ __metadata: languageName: node linkType: hard +"is-network-error@npm:^1.1.0": + version: 1.3.0 + resolution: "is-network-error@npm:1.3.0" + checksum: 56dc0b8ed9c0bb72202058f172ad0c3121cf68772e8cbba343d3775f6e2ec7877d423cbcea45f4cedcd345de8693de1b52dfe0c6fc15d652c4aa98c2abf0185a + languageName: node + linkType: hard + "is-number-object@npm:^1.0.4": version: 1.0.7 resolution: "is-number-object@npm:1.0.7" @@ -21419,6 +21464,13 @@ __metadata: languageName: node linkType: hard +"jwt-decode@npm:^4.0.0": + version: 4.0.0 + resolution: "jwt-decode@npm:4.0.0" + checksum: 390e2edcb31a92e86c8cbdd1edeea4c0d62acd371f8a8f0a8878e499390c0ecf4c658b365c4e941e4ef37d0170e4ca650aaa49f99a45c0b9695a235b210154b0 + languageName: node + linkType: hard + "keyv@npm:^4.5.3": version: 4.5.4 resolution: "keyv@npm:4.5.4" @@ -22164,6 +22216,13 @@ __metadata: languageName: node linkType: hard +"loglevel@npm:^1.9.2": + version: 1.9.2 + resolution: "loglevel@npm:1.9.2" + checksum: 896c67b90a507bfcfc1e9a4daa7bf789a441dd70d95cd13b998d6dd46233a3bfadfb8fadb07250432bbfb53bf61e95f2520f9b11f9d3175cc460e5c251eca0af + languageName: node + linkType: hard + "loose-envify@npm:^1.0.0, loose-envify@npm:^1.1.0, loose-envify@npm:^1.3.1, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" @@ -22453,6 +22512,45 @@ __metadata: languageName: node linkType: hard +"matrix-events-sdk@npm:0.0.1": + version: 0.0.1 + resolution: "matrix-events-sdk@npm:0.0.1" + checksum: 18b61a1a2cd9947e5cec4f9332186f5609e3a47c6c6e775b306e9af0e12babcdb2f68459133c4d44ebc5a7e5ee60e7528813dfccb80d8a8b55668338cd81ee49 + languageName: node + linkType: hard + +"matrix-js-sdk@npm:^39.3.0-rc.0": + version: 39.3.0-rc.0 + resolution: "matrix-js-sdk@npm:39.3.0-rc.0" + dependencies: + "@babel/runtime": ^7.12.5 + "@matrix-org/matrix-sdk-crypto-wasm": ^15.3.0 + another-json: ^0.2.0 + bs58: ^6.0.0 + content-type: ^1.0.4 + jwt-decode: ^4.0.0 + loglevel: ^1.9.2 + matrix-events-sdk: 0.0.1 + matrix-widget-api: ^1.14.0 + oidc-client-ts: ^3.0.1 + p-retry: 7 + sdp-transform: ^3.0.0 + unhomoglyph: ^1.0.6 + uuid: 13 + checksum: c3279836d0d30c9ea174140979dabc36ba0772023ee156e138a042e940b33f692738a32ca265f235bd2fb1095746f1b8a635e7eb3234c59de023a6f8b66a3c65 + languageName: node + linkType: hard + +"matrix-widget-api@npm:^1.14.0": + version: 1.15.0 + resolution: "matrix-widget-api@npm:1.15.0" + dependencies: + "@types/events": ^3.0.0 + events: ^3.2.0 + checksum: bd77ade78987ebfd202bae45204f3b43f8d7baa53b7df3910843dfecf8c5d66eada2a644a1a5b0ab5d4307847815b69d9d6c27981c97a0d82417d88ec8df1b8f + languageName: node + linkType: hard + "maxmin@npm:^2.1.0": version: 2.1.0 resolution: "maxmin@npm:2.1.0" @@ -24711,6 +24809,15 @@ __metadata: languageName: node linkType: hard +"oidc-client-ts@npm:^3.0.1": + version: 3.4.1 + resolution: "oidc-client-ts@npm:3.4.1" + dependencies: + jwt-decode: ^4.0.0 + checksum: 3c0298e11a5ae89131c4a0a77699dabc7470e68a7128d19a30321a629e0fb0a777138eb14c0c68e01fd645b56b19b55f26cb2f6a6de2b832e79bdc0eb764a1e1 + languageName: node + linkType: hard + "on-finished@npm:2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" @@ -25085,6 +25192,15 @@ __metadata: languageName: node linkType: hard +"p-retry@npm:7": + version: 7.1.0 + resolution: "p-retry@npm:7.1.0" + dependencies: + is-network-error: ^1.1.0 + checksum: 5f8b34218d0041adac2d9133e116d18a9ff6e61f55390910cb63dbc9bfb4088de8ce36a6da3dad48878ef1bf9b565b883a2ef5ff4733cb8cc3c8eab82d9b2e6d + languageName: node + linkType: hard + "p-timeout@npm:^3.2.0": version: 3.2.0 resolution: "p-timeout@npm:3.2.0" @@ -28530,6 +28646,15 @@ __metadata: languageName: node linkType: hard +"sdp-transform@npm:^3.0.0": + version: 3.0.0 + resolution: "sdp-transform@npm:3.0.0" + bin: + sdp-verify: checker.js + checksum: a9bd43d7135ec3b0db36cf3a7896abd18ffe19811dd59d4ca271c4ba8ebb679c2fc930708d4b3e8f0a08dbc513d1e888e6971b51ab04cfec5c4c58d6ec895bc6 + languageName: node + linkType: hard + "semver@npm:2 || 3 || 4 || 5, semver@npm:^5.3.0, semver@npm:^5.5.0, semver@npm:^5.6.0, semver@npm:^5.7.1": version: 5.7.2 resolution: "semver@npm:5.7.2" @@ -31110,6 +31235,13 @@ __metadata: languageName: node linkType: hard +"unhomoglyph@npm:^1.0.6": + version: 1.0.6 + resolution: "unhomoglyph@npm:1.0.6" + checksum: 2401fa3f8129fb1093d9ae59680b1dc8e395016e48401f9e706861a6edd28cafc60b0ac7e001cc127ee544942ed16236881b969afd856c28ebfe5d77afa1f0c2 + languageName: node + linkType: hard + "unicode-canonical-property-names-ecmascript@npm:^2.0.0": version: 2.0.0 resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.0" @@ -31545,6 +31677,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:13": + version: 13.0.0 + resolution: "uuid@npm:13.0.0" + bin: + uuid: dist-node/bin/uuid + checksum: 7510ee1ab371be5339ef26ff8cabc2f4a2c60640ff880652968f758072f53bd4f4af1c8b0e671a8c9bb29ef926a24dec3ef0e3861d78183b39291a85743a9f96 + languageName: node + linkType: hard + "uuid@npm:8.3.2, uuid@npm:^8.0.0, uuid@npm:^8.3.2, uuid@npm:~8.3.2": version: 8.3.2 resolution: "uuid@npm:8.3.2" From 38e3876b43136730b06f7eccf42b34a4a69f6fdc Mon Sep 17 00:00:00 2001 From: Aryan-Verma-999 Date: Sat, 6 Dec 2025 19:08:45 +0530 Subject: [PATCH 2/6] chore: format code with prettier --- packages/api/src/EmbeddedChatApi.ts | 56 +- packages/api/src/IChatProvider.ts | 49 +- packages/api/src/MatrixProvider.ts | 705 +++++++++--------- .../react/src/stories/MatrixMode.stories.js | 28 +- packages/react/src/views/EmbeddedChat.js | 2 +- 5 files changed, 437 insertions(+), 403 deletions(-) diff --git a/packages/api/src/EmbeddedChatApi.ts b/packages/api/src/EmbeddedChatApi.ts index bcc499cba6..1995d6fb49 100644 --- a/packages/api/src/EmbeddedChatApi.ts +++ b/packages/api/src/EmbeddedChatApi.ts @@ -74,21 +74,21 @@ export default class EmbeddedChatApi implements IChatProvider { const payload = acsCode ? JSON.stringify({ - serviceName: "google", - accessToken: tokens.access_token, - idToken: tokens.id_token, - expiresIn: 3600, - totp: { - code: acsPayload, - }, - }) + serviceName: "google", + accessToken: tokens.access_token, + idToken: tokens.id_token, + expiresIn: 3600, + totp: { + code: acsPayload, + }, + }) : JSON.stringify({ - serviceName: "google", - accessToken: tokens.access_token, - idToken: tokens.id_token, - expiresIn: 3600, - scope: "profile", - }); + serviceName: "google", + accessToken: tokens.access_token, + idToken: tokens.id_token, + expiresIn: 3600, + scope: "profile", + }); try { const req = await fetch(`${this.host}/api/v1/login`, { @@ -364,7 +364,7 @@ export default class EmbeddedChatApi implements IChatProvider { typingHandlerLock = 0; }, 2000); // eslint-disable-next-line no-empty - while (typingHandlerLock) { } + while (typingHandlerLock) {} typingHandlerLock = 1; // move user to front if typing else remove it. const idx = this.typingUsers.indexOf(typingUser); @@ -556,9 +556,9 @@ export default class EmbeddedChatApi implements IChatProvider { query?: object | undefined; field?: object | undefined; } = { - query: undefined, - field: undefined, - }, + query: undefined, + field: undefined, + }, isChannelPrivate = false ) { const roomType = isChannelPrivate ? "groups" : "channels"; @@ -595,10 +595,10 @@ export default class EmbeddedChatApi implements IChatProvider { field?: object | undefined; offset?: number; } = { - query: undefined, - field: undefined, - offset: 50, - }, + query: undefined, + field: undefined, + offset: 50, + }, isChannelPrivate = false ) { const roomType = isChannelPrivate ? "groups" : "channels"; @@ -746,13 +746,13 @@ export default class EmbeddedChatApi implements IChatProvider { const messageObj = typeof message === "string" ? { - rid: this.rid, - msg: message, - } + rid: this.rid, + msg: message, + } : { - ...message, - rid: this.rid, - }; + ...message, + rid: this.rid, + }; if (threadId) { messageObj.tmid = threadId; } diff --git a/packages/api/src/IChatProvider.ts b/packages/api/src/IChatProvider.ts index 9ae30e4554..5b86764564 100644 --- a/packages/api/src/IChatProvider.ts +++ b/packages/api/src/IChatProvider.ts @@ -1,31 +1,34 @@ import { RocketChatAuth } from "@embeddedchat/auth"; export interface IChatProvider { - sendTypingStatus(username: string, typing: boolean): Promise; + sendTypingStatus(username: string, typing: boolean): Promise; - addMessageListener(callback: (message: any) => void): void; - removeMessageListener(callback: (message: any) => void): void; - addMessageDeleteListener(callback: (messageId: string) => void): void; - removeMessageDeleteListener(callback: (messageId: string) => void): void; - addTypingStatusListener(callback: (users: string[]) => void): void; - removeTypingStatusListener(callback: (users: string[]) => void): void; - addActionTriggeredListener(callback: (data: any) => void): void; - removeActionTriggeredListener(callback: (data: any) => void): void; - addUiInteractionListener(callback: (data: any) => void): void; - removeUiInteractionListener(callback: (data: any) => void): void; + addMessageListener(callback: (message: any) => void): void; + removeMessageListener(callback: (message: any) => void): void; + addMessageDeleteListener(callback: (messageId: string) => void): void; + removeMessageDeleteListener(callback: (messageId: string) => void): void; + addTypingStatusListener(callback: (users: string[]) => void): void; + removeTypingStatusListener(callback: (users: string[]) => void): void; + addActionTriggeredListener(callback: (data: any) => void): void; + removeActionTriggeredListener(callback: (data: any) => void): void; + addUiInteractionListener(callback: (data: any) => void): void; + removeUiInteractionListener(callback: (data: any) => void): void; - login(userOrEmail: string, password: string, code?: string): Promise; - logout(): Promise; - autoLogin(auth: { flow: "PASSWORD" | "OAUTH" | "TOKEN"; credentials: any }): Promise; - googleSSOLogin(signIn: Function, acsCode: string): Promise; + login(userOrEmail: string, password: string, code?: string): Promise; + logout(): Promise; + autoLogin(auth: { + flow: "PASSWORD" | "OAUTH" | "TOKEN"; + credentials: any; + }): Promise; + googleSSOLogin(signIn: Function, acsCode: string): Promise; - getRCAppInfo(): Promise; - updateUserUsername(userid: string, username: string): Promise; - channelInfo(): Promise; - getRoomInfo(): Promise; - permissionInfo(): Promise; + getRCAppInfo(): Promise; + updateUserUsername(userid: string, username: string): Promise; + channelInfo(): Promise; + getRoomInfo(): Promise; + permissionInfo(): Promise; - setAuth(auth: RocketChatAuth): void; - getAuth(): RocketChatAuth; - getHost(): string; + setAuth(auth: RocketChatAuth): void; + getAuth(): RocketChatAuth; + getHost(): string; } diff --git a/packages/api/src/MatrixProvider.ts b/packages/api/src/MatrixProvider.ts index 6e198a78cd..62d76e9a3f 100644 --- a/packages/api/src/MatrixProvider.ts +++ b/packages/api/src/MatrixProvider.ts @@ -3,361 +3,392 @@ import { IChatProvider } from "./IChatProvider"; import { RocketChatAuth } from "@embeddedchat/auth"; class MatrixAuth extends RocketChatAuth { - private onAuthChangeCb: ((user: any) => void) | null = null; - currentUser: any | null = null; + private onAuthChangeCb: ((user: any) => void) | null = null; + currentUser: any | null = null; - constructor(config: any) { - super(config); - } + constructor(config: any) { + super(config); + } - async onAuthChange(cb: (user: any) => void): Promise { - this.onAuthChangeCb = cb; - if (this.currentUser) { - cb(this.currentUser); - } + async onAuthChange(cb: (user: any) => void): Promise { + this.onAuthChangeCb = cb; + if (this.currentUser) { + cb(this.currentUser); } + } - notifyAuthChange(user: any) { - this.currentUser = user; - if (this.onAuthChangeCb) { - this.onAuthChangeCb(user); - } + notifyAuthChange(user: any) { + this.currentUser = user; + if (this.onAuthChangeCb) { + this.onAuthChangeCb(user); } + } } export default class MatrixProvider implements IChatProvider { - private client: any; - private host: string; - private roomId: string; - private auth: MatrixAuth; - private onMessageCallbacks: ((message: any) => void)[] = []; - - constructor(host: string, roomId: string) { - this.host = host; - this.roomId = roomId; - this.auth = new MatrixAuth({ - host: this.host, - deleteToken: async () => { }, - getToken: async () => "", - saveToken: async () => { }, - }); - } - - async connect(): Promise { - if (!this.client) { - this.client = sdk.createClient({ - baseUrl: this.host, - }); - } - - // Map Matrix events to EmbeddedChat callbacks - this.client.removeAllListeners("Room.timeline"); - this.client.on("Room.timeline", (event: any, room: any, toStartOfTimeline: boolean) => { - if (event.getType() !== "m.room.message" && event.getType() !== "m.room.encrypted") { - return; - } - if (room.roomId !== this.roomId) { - return; - } - const isEncrypted = event.getType() === "m.room.encrypted"; - const text = isEncrypted ? "⚠️ [Encrypted Message]" : event.getContent().body; - - // Convert Matrix event to RC message format - const message = { - _id: event.getId(), - msg: text, - md: [ - { - type: "PARAGRAPH", - value: [ - { - type: "PLAIN_TEXT", - value: text, - }, - ], - }, - ], - ts: new Date(event.getTs()).toISOString(), - u: { - _id: event.getSender(), - username: event.getSender(), - }, - rid: room.roomId, - }; - this.onMessageCallbacks.forEach(cb => cb(message)); - }); - - if (!this.client.clientRunning) { - await this.client.startClient({ initialSyncLimit: 10 }); + private client: any; + private host: string; + private roomId: string; + private auth: MatrixAuth; + private onMessageCallbacks: ((message: any) => void)[] = []; + + constructor(host: string, roomId: string) { + this.host = host; + this.roomId = roomId; + this.auth = new MatrixAuth({ + host: this.host, + deleteToken: async () => {}, + getToken: async () => "", + saveToken: async () => {}, + }); + } + + async connect(): Promise { + if (!this.client) { + this.client = sdk.createClient({ + baseUrl: this.host, + }); + } + + // Map Matrix events to EmbeddedChat callbacks + this.client.removeAllListeners("Room.timeline"); + this.client.on( + "Room.timeline", + (event: any, room: any, toStartOfTimeline: boolean) => { + if ( + event.getType() !== "m.room.message" && + event.getType() !== "m.room.encrypted" + ) { + return; } - - await new Promise((resolve) => { - const state = this.client.getSyncState(); - if (state === "PREPARED" || state === "SYNCING") { - resolve(); - } else { - const checkSync = (state: any) => { - if (state === "PREPARED" || state === "SYNCING") { - this.client.removeListener("Sync", checkSync); - resolve(); - } - }; - this.client.on("Sync", checkSync); - - // Timeout after 5 seconds to prevent hanging - setTimeout(() => { - this.client.removeListener("Sync", checkSync); - resolve(); - }, 5000); - } - }); - - let room = this.client.getRoom(this.roomId); - if (!room) { - try { - await this.client.joinRoom(this.roomId); - - // Wait for room to appear in store - let retries = 0; - while (!room && retries < 20) { - await new Promise(r => setTimeout(r, 1000)); - room = this.client.getRoom(this.roomId); - retries++; - } - } catch (error) { - console.error(`MatrixProvider: Failed to join room ${this.roomId}`, error); - } + if (room.roomId !== this.roomId) { + return; } - } - - async login(userOrEmail: string, password: string, code?: string): Promise { - if (!this.client) { - this.client = sdk.createClient({ - baseUrl: this.host, - }); - } - const response = await this.client.login("m.login.password", { - identifier: { - type: "m.id.user", - user: userOrEmail, + const isEncrypted = event.getType() === "m.room.encrypted"; + const text = isEncrypted + ? "⚠️ [Encrypted Message]" + : event.getContent().body; + + // Convert Matrix event to RC message format + const message = { + _id: event.getId(), + msg: text, + md: [ + { + type: "PARAGRAPH", + value: [ + { + type: "PLAIN_TEXT", + value: text, + }, + ], }, - password: password, - }); - - // Ensure we connect (start client and sync) after login - await this.connect(); - - const user = { - username: response.user_id, - _id: response.user_id, - name: response.user_id, - avatarUrl: "", // Placeholder - roles: [] + ], + ts: new Date(event.getTs()).toISOString(), + u: { + _id: event.getSender(), + username: event.getSender(), + }, + rid: room.roomId, }; - - // Notify auth change to update UI - this.auth.notifyAuthChange({ me: user }); - - return { status: "success", me: user }; - } - - async close(): Promise { - if (this.client) { - this.client.stopClient(); - } - this.auth.notifyAuthChange(null); - } - - async getStarredMessages(): Promise { - return { messages: [] }; - } - - async getAllFiles(): Promise { - return { files: [] }; - } - - async getMessages(anonymousMode: boolean, options?: any, isChannelPrivate?: boolean): Promise { - if (!this.client) return { messages: [], count: 0 }; - const room = this.client.getRoom(this.roomId); - if (!room) { - return { messages: [], count: 0 }; - } - - const events = room.getLiveTimeline().getEvents(); - - // Reverse events to match Rocket.Chat's newest-first expectation - const messages = events - .slice() // Create a copy before reversing - .reverse() - .filter((event: any) => event.getType() === "m.room.message" || event.getType() === "m.room.encrypted") - .map((event: any) => { - const isEncrypted = event.getType() === "m.room.encrypted"; - const text = isEncrypted ? "⚠️ [Encrypted Message]" : event.getContent().body; - return { - _id: event.getId(), - msg: text, - md: [ - { - type: "PARAGRAPH", - value: [ - { - type: "PLAIN_TEXT", - value: text, - }, - ], - }, - ], - ts: new Date(event.getTs()).toISOString(), - u: { - _id: event.getSender(), - username: event.getSender(), - }, - rid: room.roomId, - }; - }); - - return { messages, count: messages.length, success: true }; - } - - async channelInfo(): Promise { - if (!this.client) return {}; - const room = this.client.getRoom(this.roomId); - if (!room) return {}; - return { - success: true, - room: { - _id: room.roomId, - name: room.name || "Matrix Room", - t: "c", - } + this.onMessageCallbacks.forEach((cb) => cb(message)); + } + ); + + if (!this.client.clientRunning) { + await this.client.startClient({ initialSyncLimit: 10 }); + } + + await new Promise((resolve) => { + const state = this.client.getSyncState(); + if (state === "PREPARED" || state === "SYNCING") { + resolve(); + } else { + const checkSync = (state: any) => { + if (state === "PREPARED" || state === "SYNCING") { + this.client.removeListener("Sync", checkSync); + resolve(); + } }; - } - - async getRoomInfo(): Promise { - if (!this.client) return {}; - const room = this.client.getRoom(this.roomId); - if (!room) return {}; + this.client.on("Sync", checkSync); + + // Timeout after 5 seconds to prevent hanging + setTimeout(() => { + this.client.removeListener("Sync", checkSync); + resolve(); + }, 5000); + } + }); + + let room = this.client.getRoom(this.roomId); + if (!room) { + try { + await this.client.joinRoom(this.roomId); + + // Wait for room to appear in store + let retries = 0; + while (!room && retries < 20) { + await new Promise((r) => setTimeout(r, 1000)); + room = this.client.getRoom(this.roomId); + retries++; + } + } catch (error) { + console.error( + `MatrixProvider: Failed to join room ${this.roomId}`, + error + ); + } + } + } + + async login( + userOrEmail: string, + password: string, + code?: string + ): Promise { + if (!this.client) { + this.client = sdk.createClient({ + baseUrl: this.host, + }); + } + const response = await this.client.login("m.login.password", { + identifier: { + type: "m.id.user", + user: userOrEmail, + }, + password: password, + }); + + // Ensure we connect (start client and sync) after login + await this.connect(); + + const user = { + username: response.user_id, + _id: response.user_id, + name: response.user_id, + avatarUrl: "", // Placeholder + roles: [], + }; + + // Notify auth change to update UI + this.auth.notifyAuthChange({ me: user }); + + return { status: "success", me: user }; + } + + async close(): Promise { + if (this.client) { + this.client.stopClient(); + } + this.auth.notifyAuthChange(null); + } + + async getStarredMessages(): Promise { + return { messages: [] }; + } + + async getAllFiles(): Promise { + return { files: [] }; + } + + async getMessages( + anonymousMode: boolean, + options?: any, + isChannelPrivate?: boolean + ): Promise { + if (!this.client) return { messages: [], count: 0 }; + const room = this.client.getRoom(this.roomId); + if (!room) { + return { messages: [], count: 0 }; + } + + const events = room.getLiveTimeline().getEvents(); + + // Reverse events to match Rocket.Chat's newest-first expectation + const messages = events + .slice() // Create a copy before reversing + .reverse() + .filter( + (event: any) => + event.getType() === "m.room.message" || + event.getType() === "m.room.encrypted" + ) + .map((event: any) => { + const isEncrypted = event.getType() === "m.room.encrypted"; + const text = isEncrypted + ? "⚠️ [Encrypted Message]" + : event.getContent().body; return { - success: true, - room: { - _id: room.roomId, - name: room.name, - t: "c", - } - }; - } - - async sendMessage(message: any, threadId?: string): Promise { - const content = { - msgtype: "m.text", - body: typeof message === "string" ? message : message.msg, + _id: event.getId(), + msg: text, + md: [ + { + type: "PARAGRAPH", + value: [ + { + type: "PLAIN_TEXT", + value: text, + }, + ], + }, + ], + ts: new Date(event.getTs()).toISOString(), + u: { + _id: event.getSender(), + username: event.getSender(), + }, + rid: room.roomId, }; - const response = await this.client.sendEvent(this.roomId, "m.room.message", content); - return { _id: response.event_id }; - } + }); + + return { messages, count: messages.length, success: true }; + } + + async channelInfo(): Promise { + if (!this.client) return {}; + const room = this.client.getRoom(this.roomId); + if (!room) return {}; + return { + success: true, + room: { + _id: room.roomId, + name: room.name || "Matrix Room", + t: "c", + }, + }; + } + + async getRoomInfo(): Promise { + if (!this.client) return {}; + const room = this.client.getRoom(this.roomId); + if (!room) return {}; + return { + success: true, + room: { + _id: room.roomId, + name: room.name, + t: "c", + }, + }; + } + + async sendMessage(message: any, threadId?: string): Promise { + const content = { + msgtype: "m.text", + body: typeof message === "string" ? message : message.msg, + }; + const response = await this.client.sendEvent( + this.roomId, + "m.room.message", + content + ); + return { _id: response.event_id }; + } + + async getOlderMessages( + anonymousMode: boolean, + options?: any, + isChannelPrivate?: boolean + ): Promise { + return { messages: [] }; + } + + async getThreadMessages( + tmid: string, + isChannelPrivate?: boolean + ): Promise { + return { messages: [] }; + } + + async deleteMessage(msgId: string): Promise { + return {}; + } + + async updateMessage(msgId: string, text: string): Promise { + return {}; + } + + async getChannelRoles(isChannelPrivate?: boolean): Promise { + return []; + } + + async getUsersInRole(role: string): Promise { + return []; + } + + async getUserRoles(): Promise { + return []; + } + + async sendTypingStatus(username: string, typing: boolean): Promise { + await this.client.sendTyping(this.roomId, typing, 3000); + } + + addMessageListener(callback: (message: any) => void): void { + this.onMessageCallbacks.push(callback); + } + + removeMessageListener(callback: (message: any) => void): void { + this.onMessageCallbacks = this.onMessageCallbacks.filter( + (c) => c !== callback + ); + } + + addMessageDeleteListener(callback: (messageId: string) => void): void {} + + removeMessageDeleteListener(callback: (messageId: string) => void): void {} + + addTypingStatusListener(callback: (users: string[]) => void): void {} + + removeTypingStatusListener(callback: (users: string[]) => void): void {} + + addActionTriggeredListener(callback: (data: any) => void): void {} + + removeActionTriggeredListener(callback: (data: any) => void): void {} + + addUiInteractionListener(callback: (data: any) => void): void {} + + removeUiInteractionListener(callback: (data: any) => void): void {} + + async logout(): Promise { + if (this.client) { + await this.client.logout(); + } + this.auth.notifyAuthChange(null); + } + + async autoLogin(auth: { + flow: "PASSWORD" | "OAUTH" | "TOKEN"; + credentials: any; + }): Promise {} + + async googleSSOLogin(signIn: Function, acsCode: string): Promise {} + + async getRCAppInfo(): Promise { + return null; + } + + async updateUserUsername(userid: string, username: string): Promise { + return {}; + } - async getOlderMessages(anonymousMode: boolean, options?: any, isChannelPrivate?: boolean): Promise { - return { messages: [] }; - } + async permissionInfo(): Promise { + return []; + } - async getThreadMessages(tmid: string, isChannelPrivate?: boolean): Promise { - return { messages: [] }; - } + setAuth(auth: RocketChatAuth): void { + // We ignore external auth setting for now as we manage our own MatrixAuth + } - async deleteMessage(msgId: string): Promise { - return {}; - } + getAuth(): RocketChatAuth { + return this.auth; + } - async updateMessage(msgId: string, text: string): Promise { - return {}; - } + getHost(): string { + return this.host; + } - async getChannelRoles(isChannelPrivate?: boolean): Promise { - return []; - } - - async getUsersInRole(role: string): Promise { - return []; - } - - async getUserRoles(): Promise { - return []; - } - - async sendTypingStatus(username: string, typing: boolean): Promise { - await this.client.sendTyping(this.roomId, typing, 3000); - } - - addMessageListener(callback: (message: any) => void): void { - this.onMessageCallbacks.push(callback); - } - - removeMessageListener(callback: (message: any) => void): void { - this.onMessageCallbacks = this.onMessageCallbacks.filter(c => c !== callback); - } - - addMessageDeleteListener(callback: (messageId: string) => void): void { - } - - removeMessageDeleteListener(callback: (messageId: string) => void): void { - } - - addTypingStatusListener(callback: (users: string[]) => void): void { - } - - removeTypingStatusListener(callback: (users: string[]) => void): void { - } - - addActionTriggeredListener(callback: (data: any) => void): void { - } - - removeActionTriggeredListener(callback: (data: any) => void): void { - } - - addUiInteractionListener(callback: (data: any) => void): void { - } - - removeUiInteractionListener(callback: (data: any) => void): void { - } - - async logout(): Promise { - if (this.client) { - await this.client.logout(); - } - this.auth.notifyAuthChange(null); - } - - async autoLogin(auth: { flow: "PASSWORD" | "OAUTH" | "TOKEN"; credentials: any }): Promise { - } - - async googleSSOLogin(signIn: Function, acsCode: string): Promise { - } - - async getRCAppInfo(): Promise { - return null; - } - - async updateUserUsername(userid: string, username: string): Promise { - return {}; - } - - async permissionInfo(): Promise { - return []; - } - - setAuth(auth: RocketChatAuth): void { - // We ignore external auth setting for now as we manage our own MatrixAuth - } - - getAuth(): RocketChatAuth { - return this.auth; - } - - getHost(): string { - return this.host; - } - - async getMessageLimit(): Promise { - return { value: 5000 }; // Default limit - } + async getMessageLimit(): Promise { + return { value: 5000 }; // Default limit + } } diff --git a/packages/react/src/stories/MatrixMode.stories.js b/packages/react/src/stories/MatrixMode.stories.js index f8f1923883..520de5ff36 100644 --- a/packages/react/src/stories/MatrixMode.stories.js +++ b/packages/react/src/stories/MatrixMode.stories.js @@ -1,21 +1,21 @@ import { EmbeddedChat } from '..'; export default { - title: 'EmbeddedChat/Matrix', - component: EmbeddedChat, + title: 'EmbeddedChat/Matrix', + component: EmbeddedChat, }; export const MatrixMode = { - args: { - mode: 'matrix', - host: 'https://matrix.org', // Replace with your Matrix homeserver URL - roomId: '!mLGtEDRtJGxNKvXHPH:matrix.org', // Replace with a public room ID (e.g., Matrix HQ) - channelName: 'Matrix Room', - headerColor: 'white', - toastBarPosition: 'bottom right', - showRoles: true, - enableThreads: true, - hideHeader: false, - dark: false, - }, + args: { + mode: 'matrix', + host: 'https://matrix.org', // Replace with your Matrix homeserver URL + roomId: '!mLGtEDRtJGxNKvXHPH:matrix.org', // Replace with a public room ID (e.g., Matrix HQ) + channelName: 'Matrix Room', + headerColor: 'white', + toastBarPosition: 'bottom right', + showRoles: true, + enableThreads: true, + hideHeader: false, + dark: false, + }, }; diff --git a/packages/react/src/views/EmbeddedChat.js b/packages/react/src/views/EmbeddedChat.js index d40393d862..50b584fe0f 100644 --- a/packages/react/src/views/EmbeddedChat.js +++ b/packages/react/src/views/EmbeddedChat.js @@ -34,7 +34,7 @@ const EmbeddedChat = (props) => { const { isClosable = false, - setClosableState = () => { }, + setClosableState = () => {}, width = '100%', height = '95vh', host = 'http://localhost:3000', From 3ae0e3267bcf7934e03c2cd7aa78a62fe94616a3 Mon Sep 17 00:00:00 2001 From: Aryan-Verma-999 Date: Wed, 10 Dec 2025 13:39:48 +0530 Subject: [PATCH 3/6] fix: resolve duplicate messages and add display names in Matrix mode --- packages/api/src/MatrixProvider.ts | 27 ++++++++++++++++--- .../react/src/stories/MatrixMode.stories.js | 2 +- .../react/src/views/ChatInput/ChatInput.js | 10 +++++-- packages/react/src/views/EmbeddedChat.js | 2 ++ 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/packages/api/src/MatrixProvider.ts b/packages/api/src/MatrixProvider.ts index 62d76e9a3f..f34b87deab 100644 --- a/packages/api/src/MatrixProvider.ts +++ b/packages/api/src/MatrixProvider.ts @@ -55,6 +55,10 @@ export default class MatrixProvider implements IChatProvider { this.client.on( "Room.timeline", (event: any, room: any, toStartOfTimeline: boolean) => { + // Ignore historical messages to prevent duplicates + if (toStartOfTimeline) { + return; + } if ( event.getType() !== "m.room.message" && event.getType() !== "m.room.encrypted" @@ -64,11 +68,17 @@ export default class MatrixProvider implements IChatProvider { if (room.roomId !== this.roomId) { return; } + const isEncrypted = event.getType() === "m.room.encrypted"; const text = isEncrypted ? "⚠️ [Encrypted Message]" : event.getContent().body; + // Get display name from room member + const senderId = event.getSender(); + const member = room.getMember(senderId); + const displayName = member?.name || senderId; + // Convert Matrix event to RC message format const message = { _id: event.getId(), @@ -86,11 +96,13 @@ export default class MatrixProvider implements IChatProvider { ], ts: new Date(event.getTs()).toISOString(), u: { - _id: event.getSender(), - username: event.getSender(), + _id: senderId, + username: displayName, + name: displayName, }, rid: room.roomId, }; + this.onMessageCallbacks.forEach((cb) => cb(message)); } ); @@ -218,6 +230,12 @@ export default class MatrixProvider implements IChatProvider { const text = isEncrypted ? "⚠️ [Encrypted Message]" : event.getContent().body; + + // Get display name from room member + const senderId = event.getSender(); + const member = room.getMember(senderId); + const displayName = member?.name || senderId; + return { _id: event.getId(), msg: text, @@ -234,8 +252,9 @@ export default class MatrixProvider implements IChatProvider { ], ts: new Date(event.getTs()).toISOString(), u: { - _id: event.getSender(), - username: event.getSender(), + _id: senderId, + username: displayName, + name: displayName, }, rid: room.roomId, }; diff --git a/packages/react/src/stories/MatrixMode.stories.js b/packages/react/src/stories/MatrixMode.stories.js index 520de5ff36..598d2a645c 100644 --- a/packages/react/src/stories/MatrixMode.stories.js +++ b/packages/react/src/stories/MatrixMode.stories.js @@ -9,7 +9,7 @@ export const MatrixMode = { args: { mode: 'matrix', host: 'https://matrix.org', // Replace with your Matrix homeserver URL - roomId: '!mLGtEDRtJGxNKvXHPH:matrix.org', // Replace with a public room ID (e.g., Matrix HQ) + roomId: '', // Replace with a public room ID (e.g., Matrix HQ) channelName: 'Matrix Room', headerColor: 'white', toastBarPosition: 'bottom right', diff --git a/packages/react/src/views/ChatInput/ChatInput.js b/packages/react/src/views/ChatInput/ChatInput.js index 998e9007cd..06738238c4 100644 --- a/packages/react/src/views/ChatInput/ChatInput.js +++ b/packages/react/src/views/ChatInput/ChatInput.js @@ -321,7 +321,10 @@ const ChatInput = ({ scrollToBottom }) => { pendingMessage.tmid = threadId; } - upsertMessage(pendingMessage, ECOptions.enableThreads); + // Skip optimistic rendering for Matrix - it provides real-time updates via Room.timeline + if (ECOptions.mode !== 'matrix') { + upsertMessage(pendingMessage, ECOptions.enableThreads); + } const res = await RCInstance.sendMessage( { @@ -333,7 +336,10 @@ const ChatInput = ({ scrollToBottom }) => { if (res.success) { clearQuoteMessages(); - replaceMessage(pendingMessage, res.message); + // In Matrix mode, don't replace - the message will come via Room.timeline + if (ECOptions.mode !== 'matrix') { + replaceMessage(pendingMessage, res.message); + } } }; diff --git a/packages/react/src/views/EmbeddedChat.js b/packages/react/src/views/EmbeddedChat.js index 50b584fe0f..7f207960f6 100644 --- a/packages/react/src/views/EmbeddedChat.js +++ b/packages/react/src/views/EmbeddedChat.js @@ -202,6 +202,7 @@ const EmbeddedChat = (props) => { showUsername, hideHeader, anonymousMode, + mode, }), [ enableThreads, @@ -218,6 +219,7 @@ const EmbeddedChat = (props) => { showUsername, hideHeader, anonymousMode, + mode, ] ); From 711dea19bc9da5a4917246726a6b0a67527d9b9a Mon Sep 17 00:00:00 2001 From: Aryan-Verma-999 Date: Fri, 12 Dec 2025 15:21:10 +0530 Subject: [PATCH 4/6] fix: return error object on login failure for toast display Previously, Matrix login failures would throw exceptions that weren't caught by the UI error handling. Now returns error in Rocket.Chat format ({ error: 403 }) to trigger proper toast notifications. --- packages/api/src/MatrixProvider.ts | 76 +++++++++++++++++------------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/packages/api/src/MatrixProvider.ts b/packages/api/src/MatrixProvider.ts index f34b87deab..e0fe845b1c 100644 --- a/packages/api/src/MatrixProvider.ts +++ b/packages/api/src/MatrixProvider.ts @@ -37,9 +37,9 @@ export default class MatrixProvider implements IChatProvider { this.roomId = roomId; this.auth = new MatrixAuth({ host: this.host, - deleteToken: async () => {}, + deleteToken: async () => { }, getToken: async () => "", - saveToken: async () => {}, + saveToken: async () => { }, }); } @@ -163,29 +163,39 @@ export default class MatrixProvider implements IChatProvider { baseUrl: this.host, }); } - const response = await this.client.login("m.login.password", { - identifier: { - type: "m.id.user", - user: userOrEmail, - }, - password: password, - }); - // Ensure we connect (start client and sync) after login - await this.connect(); - - const user = { - username: response.user_id, - _id: response.user_id, - name: response.user_id, - avatarUrl: "", // Placeholder - roles: [], - }; - - // Notify auth change to update UI - this.auth.notifyAuthChange({ me: user }); + try { + const response = await this.client.login("m.login.password", { + identifier: { + type: "m.id.user", + user: userOrEmail, + }, + password: password, + }); - return { status: "success", me: user }; + // Ensure we connect (start client and sync) after login + await this.connect(); + + const user = { + username: response.user_id, + _id: response.user_id, + name: response.user_id, + avatarUrl: "", // Placeholder + roles: [], + }; + + // Notify auth change to update UI + this.auth.notifyAuthChange({ me: user }); + + return { status: "success", me: user }; + } catch (error: any) { + // Return error in Rocket.Chat format for toast notifications + console.error("Matrix login failed:", error); + return { + error: error.httpStatus === 403 ? 403 : "Unauthorized", + message: error.message || "Invalid username or password" + }; + } } async close(): Promise { @@ -353,21 +363,21 @@ export default class MatrixProvider implements IChatProvider { ); } - addMessageDeleteListener(callback: (messageId: string) => void): void {} + addMessageDeleteListener(callback: (messageId: string) => void): void { } - removeMessageDeleteListener(callback: (messageId: string) => void): void {} + removeMessageDeleteListener(callback: (messageId: string) => void): void { } - addTypingStatusListener(callback: (users: string[]) => void): void {} + addTypingStatusListener(callback: (users: string[]) => void): void { } - removeTypingStatusListener(callback: (users: string[]) => void): void {} + removeTypingStatusListener(callback: (users: string[]) => void): void { } - addActionTriggeredListener(callback: (data: any) => void): void {} + addActionTriggeredListener(callback: (data: any) => void): void { } - removeActionTriggeredListener(callback: (data: any) => void): void {} + removeActionTriggeredListener(callback: (data: any) => void): void { } - addUiInteractionListener(callback: (data: any) => void): void {} + addUiInteractionListener(callback: (data: any) => void): void { } - removeUiInteractionListener(callback: (data: any) => void): void {} + removeUiInteractionListener(callback: (data: any) => void): void { } async logout(): Promise { if (this.client) { @@ -379,9 +389,9 @@ export default class MatrixProvider implements IChatProvider { async autoLogin(auth: { flow: "PASSWORD" | "OAUTH" | "TOKEN"; credentials: any; - }): Promise {} + }): Promise { } - async googleSSOLogin(signIn: Function, acsCode: string): Promise {} + async googleSSOLogin(signIn: Function, acsCode: string): Promise { } async getRCAppInfo(): Promise { return null; From 83eabf058c9db13705480e2185939870e2d509a6 Mon Sep 17 00:00:00 2001 From: Aryan-Verma-999 Date: Fri, 12 Dec 2025 18:35:02 +0530 Subject: [PATCH 5/6] feat: Improve error handling and add stub methods for Matrix mode - Add proper error returns for channelInfo/getRoomInfo for toast messages - Add try-catch to sendMessage for error handling - Add stub methods (star, pin, react, etc.) to prevent UI errors - Fix edit message to show toast instead of triggering logout - Add null-safety checks in connect() method --- packages/api/src/MatrixProvider.ts | 162 ++++++++++++++++-- .../react/src/views/ChatInput/ChatInput.js | 13 +- 2 files changed, 154 insertions(+), 21 deletions(-) diff --git a/packages/api/src/MatrixProvider.ts b/packages/api/src/MatrixProvider.ts index e0fe845b1c..135b9f14b4 100644 --- a/packages/api/src/MatrixProvider.ts +++ b/packages/api/src/MatrixProvider.ts @@ -112,13 +112,19 @@ export default class MatrixProvider implements IChatProvider { } await new Promise((resolve) => { + if (!this.client) { + resolve(); + return; + } const state = this.client.getSyncState(); if (state === "PREPARED" || state === "SYNCING") { resolve(); } else { const checkSync = (state: any) => { if (state === "PREPARED" || state === "SYNCING") { - this.client.removeListener("Sync", checkSync); + if (this.client) { + this.client.removeListener("Sync", checkSync); + } resolve(); } }; @@ -126,12 +132,19 @@ export default class MatrixProvider implements IChatProvider { // Timeout after 5 seconds to prevent hanging setTimeout(() => { - this.client.removeListener("Sync", checkSync); + if (this.client) { + this.client.removeListener("Sync", checkSync); + } resolve(); }, 5000); } }); + if (!this.client) { + console.log('Matrix: client became null during sync wait'); + return; + } + let room = this.client.getRoom(this.roomId); if (!room) { try { @@ -139,9 +152,9 @@ export default class MatrixProvider implements IChatProvider { // Wait for room to appear in store let retries = 0; - while (!room && retries < 20) { + while (!room && retries < 20 && this.client) { await new Promise((r) => setTimeout(r, 1000)); - room = this.client.getRoom(this.roomId); + room = this.client?.getRoom(this.roomId); retries++; } } catch (error) { @@ -274,9 +287,21 @@ export default class MatrixProvider implements IChatProvider { } async channelInfo(): Promise { - if (!this.client) return {}; + if (!this.client) { + return { + success: false, + errorType: 'error-room-not-found', + error: 'Not connected to Matrix server' + }; + } const room = this.client.getRoom(this.roomId); - if (!room) return {}; + if (!room) { + return { + success: false, + errorType: 'error-room-not-found', + error: `Room ${this.roomId} not found` + }; + } return { success: true, room: { @@ -288,9 +313,21 @@ export default class MatrixProvider implements IChatProvider { } async getRoomInfo(): Promise { - if (!this.client) return {}; + if (!this.client) { + return { + success: false, + errorType: 'error-room-not-found', + error: 'Not connected to Matrix server' + }; + } const room = this.client.getRoom(this.roomId); - if (!room) return {}; + if (!room) { + return { + success: false, + errorType: 'error-room-not-found', + error: `Room ${this.roomId} not found` + }; + } return { success: true, room: { @@ -306,12 +343,17 @@ export default class MatrixProvider implements IChatProvider { msgtype: "m.text", body: typeof message === "string" ? message : message.msg, }; - const response = await this.client.sendEvent( - this.roomId, - "m.room.message", - content - ); - return { _id: response.event_id }; + try { + const response = await this.client.sendEvent( + this.roomId, + "m.room.message", + content + ); + return { success: true, message: { _id: response.event_id } }; + } catch (error: any) { + console.error("Matrix sendMessage failed:", error); + return { success: false, error: error.message }; + } } async getOlderMessages( @@ -330,11 +372,36 @@ export default class MatrixProvider implements IChatProvider { } async deleteMessage(msgId: string): Promise { - return {}; + // Matrix message deletion not implemented + return { success: false, error: "Message deletion not supported in Matrix mode" }; } async updateMessage(msgId: string, text: string): Promise { - return {}; + // Matrix message editing not implemented + return { success: false, error: "Message editing not supported in Matrix mode" }; + } + + async starMessage(msgId: string): Promise { + // Matrix doesn't have native starring - could use room account data + return { success: true }; // Silently succeed for now + } + + async unstarMessage(msgId: string): Promise { + return { success: true }; + } + + async pinMessage(msgId: string): Promise { + // Matrix has m.room.pinned_events state event + return { success: true }; // Stub for now + } + + async unpinMessage(msgId: string): Promise { + return { success: true }; + } + + async reactToMessage(emoji: string, msgId: string, shouldReact: boolean): Promise { + // Matrix supports reactions via m.reaction + return { success: true }; // Stub for now } async getChannelRoles(isChannelPrivate?: boolean): Promise { @@ -420,4 +487,67 @@ export default class MatrixProvider implements IChatProvider { async getMessageLimit(): Promise { return { value: 5000 }; // Default limit } + + // Additional methods for toast compatibility + async reportMessage(messageId: string, description: string): Promise { + // Matrix doesn't have built-in message reporting + return { success: false, error: "Message reporting not supported in Matrix mode" }; + } + + async getSearchMessages(text: string): Promise { + // Matrix search would require server-side search API + return { messages: [] }; + } + + async me(): Promise { + if (!this.client) return {}; + const userId = this.client.getUserId(); + if (!userId) return {}; + return { + _id: userId, + username: userId, + name: userId, + }; + } + + async userData(username: string): Promise { + // Matrix user profile lookup + return { user: null }; + } + + async getUserStatus(userId: string): Promise { + // Matrix presence API + return { status: "online" }; + } + + async findOrCreateInvite(): Promise { + // Matrix room invite link + return { url: "", expires: new Date().toISOString() }; + } + + async getAllImages(): Promise { + return { files: [] }; + } + + async execCommand(command: { command: string; params: string }): Promise { + // Matrix doesn't have slash commands in the same way + return { success: false, error: "Commands not supported in Matrix mode" }; + } + + async getChannelMembers(isChannelPrivate?: boolean): Promise { + if (!this.client) return { members: [] }; + const room = this.client.getRoom(this.roomId); + if (!room) return { members: [] }; + + const members = room.getJoinedMembers().map((member: any) => ({ + _id: member.userId, + username: member.name || member.userId, + name: member.name || member.userId, + })); + return { members }; + } + + async getCommandsList(): Promise { + return { commands: [] }; + } } diff --git a/packages/react/src/views/ChatInput/ChatInput.js b/packages/react/src/views/ChatInput/ChatInput.js index 06738238c4..79f0041289 100644 --- a/packages/react/src/views/ChatInput/ChatInput.js +++ b/packages/react/src/views/ChatInput/ChatInput.js @@ -354,7 +354,10 @@ const ChatInput = ({ scrollToBottom }) => { message.replace(/\n/g, '\\n') ); if (!res.success) { - handleSendError('Error editing message, login again'); + dispatchToastMessage({ + type: 'error', + message: res.error || 'Message editing not supported', + }); } }; @@ -547,8 +550,8 @@ const ChatInput = ({ scrollToBottom }) => { editMessage.msg || editMessage.attachments ? 'Editing Message' : isChannelReadOnly - ? 'This room is read only' - : undefined + ? 'This room is read only' + : undefined } iconName={ editMessage.msg || editMessage.attachments ? 'edit' : undefined @@ -613,8 +616,8 @@ const ChatInput = ({ scrollToBottom }) => { ? isChannelArchived ? 'Room archived' : canSendMsg - ? `Message #${channelInfo.name}` - : 'This room is read only' + ? `Message #${channelInfo.name}` + : 'This room is read only' : 'Sign in to chat' } css={css` From 7705e94970aa177e6054b1c67cf079b8fd488fb9 Mon Sep 17 00:00:00 2001 From: Aryan-Verma-999 Date: Fri, 12 Dec 2025 18:45:27 +0530 Subject: [PATCH 6/6] chore: format code with prettier --- packages/api/src/MatrixProvider.ts | 70 ++++++++++++------- .../react/src/views/ChatInput/ChatInput.js | 8 +-- 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/packages/api/src/MatrixProvider.ts b/packages/api/src/MatrixProvider.ts index 135b9f14b4..3da5eaca30 100644 --- a/packages/api/src/MatrixProvider.ts +++ b/packages/api/src/MatrixProvider.ts @@ -37,9 +37,9 @@ export default class MatrixProvider implements IChatProvider { this.roomId = roomId; this.auth = new MatrixAuth({ host: this.host, - deleteToken: async () => { }, + deleteToken: async () => {}, getToken: async () => "", - saveToken: async () => { }, + saveToken: async () => {}, }); } @@ -141,7 +141,7 @@ export default class MatrixProvider implements IChatProvider { }); if (!this.client) { - console.log('Matrix: client became null during sync wait'); + console.log("Matrix: client became null during sync wait"); return; } @@ -206,7 +206,7 @@ export default class MatrixProvider implements IChatProvider { console.error("Matrix login failed:", error); return { error: error.httpStatus === 403 ? 403 : "Unauthorized", - message: error.message || "Invalid username or password" + message: error.message || "Invalid username or password", }; } } @@ -290,16 +290,16 @@ export default class MatrixProvider implements IChatProvider { if (!this.client) { return { success: false, - errorType: 'error-room-not-found', - error: 'Not connected to Matrix server' + errorType: "error-room-not-found", + error: "Not connected to Matrix server", }; } const room = this.client.getRoom(this.roomId); if (!room) { return { success: false, - errorType: 'error-room-not-found', - error: `Room ${this.roomId} not found` + errorType: "error-room-not-found", + error: `Room ${this.roomId} not found`, }; } return { @@ -316,16 +316,16 @@ export default class MatrixProvider implements IChatProvider { if (!this.client) { return { success: false, - errorType: 'error-room-not-found', - error: 'Not connected to Matrix server' + errorType: "error-room-not-found", + error: "Not connected to Matrix server", }; } const room = this.client.getRoom(this.roomId); if (!room) { return { success: false, - errorType: 'error-room-not-found', - error: `Room ${this.roomId} not found` + errorType: "error-room-not-found", + error: `Room ${this.roomId} not found`, }; } return { @@ -373,12 +373,18 @@ export default class MatrixProvider implements IChatProvider { async deleteMessage(msgId: string): Promise { // Matrix message deletion not implemented - return { success: false, error: "Message deletion not supported in Matrix mode" }; + return { + success: false, + error: "Message deletion not supported in Matrix mode", + }; } async updateMessage(msgId: string, text: string): Promise { // Matrix message editing not implemented - return { success: false, error: "Message editing not supported in Matrix mode" }; + return { + success: false, + error: "Message editing not supported in Matrix mode", + }; } async starMessage(msgId: string): Promise { @@ -399,7 +405,11 @@ export default class MatrixProvider implements IChatProvider { return { success: true }; } - async reactToMessage(emoji: string, msgId: string, shouldReact: boolean): Promise { + async reactToMessage( + emoji: string, + msgId: string, + shouldReact: boolean + ): Promise { // Matrix supports reactions via m.reaction return { success: true }; // Stub for now } @@ -430,21 +440,21 @@ export default class MatrixProvider implements IChatProvider { ); } - addMessageDeleteListener(callback: (messageId: string) => void): void { } + addMessageDeleteListener(callback: (messageId: string) => void): void {} - removeMessageDeleteListener(callback: (messageId: string) => void): void { } + removeMessageDeleteListener(callback: (messageId: string) => void): void {} - addTypingStatusListener(callback: (users: string[]) => void): void { } + addTypingStatusListener(callback: (users: string[]) => void): void {} - removeTypingStatusListener(callback: (users: string[]) => void): void { } + removeTypingStatusListener(callback: (users: string[]) => void): void {} - addActionTriggeredListener(callback: (data: any) => void): void { } + addActionTriggeredListener(callback: (data: any) => void): void {} - removeActionTriggeredListener(callback: (data: any) => void): void { } + removeActionTriggeredListener(callback: (data: any) => void): void {} - addUiInteractionListener(callback: (data: any) => void): void { } + addUiInteractionListener(callback: (data: any) => void): void {} - removeUiInteractionListener(callback: (data: any) => void): void { } + removeUiInteractionListener(callback: (data: any) => void): void {} async logout(): Promise { if (this.client) { @@ -456,9 +466,9 @@ export default class MatrixProvider implements IChatProvider { async autoLogin(auth: { flow: "PASSWORD" | "OAUTH" | "TOKEN"; credentials: any; - }): Promise { } + }): Promise {} - async googleSSOLogin(signIn: Function, acsCode: string): Promise { } + async googleSSOLogin(signIn: Function, acsCode: string): Promise {} async getRCAppInfo(): Promise { return null; @@ -491,7 +501,10 @@ export default class MatrixProvider implements IChatProvider { // Additional methods for toast compatibility async reportMessage(messageId: string, description: string): Promise { // Matrix doesn't have built-in message reporting - return { success: false, error: "Message reporting not supported in Matrix mode" }; + return { + success: false, + error: "Message reporting not supported in Matrix mode", + }; } async getSearchMessages(text: string): Promise { @@ -529,7 +542,10 @@ export default class MatrixProvider implements IChatProvider { return { files: [] }; } - async execCommand(command: { command: string; params: string }): Promise { + async execCommand(command: { + command: string; + params: string; + }): Promise { // Matrix doesn't have slash commands in the same way return { success: false, error: "Commands not supported in Matrix mode" }; } diff --git a/packages/react/src/views/ChatInput/ChatInput.js b/packages/react/src/views/ChatInput/ChatInput.js index 79f0041289..fa5b5e1e70 100644 --- a/packages/react/src/views/ChatInput/ChatInput.js +++ b/packages/react/src/views/ChatInput/ChatInput.js @@ -550,8 +550,8 @@ const ChatInput = ({ scrollToBottom }) => { editMessage.msg || editMessage.attachments ? 'Editing Message' : isChannelReadOnly - ? 'This room is read only' - : undefined + ? 'This room is read only' + : undefined } iconName={ editMessage.msg || editMessage.attachments ? 'edit' : undefined @@ -616,8 +616,8 @@ const ChatInput = ({ scrollToBottom }) => { ? isChannelArchived ? 'Room archived' : canSendMsg - ? `Message #${channelInfo.name}` - : 'This room is read only' + ? `Message #${channelInfo.name}` + : 'This room is read only' : 'Sign in to chat' } css={css`